Spring Boot 实现图片防盗链教程(Referer 校验 + Token 签名校验)
本文将详细讲解两种防盗链实现方案,并提供完整代码示例。
方案一:Referer 校验
通过检查 HTTP 请求头中的 Referer
字段判断来源是否合法。
实现步骤
创建 Referer 拦截器
@Component public class RefererInterceptor implements HandlerInterceptor {private final List<String> allowedDomains = Arrays.asList("https://yourdomain.com", "https://trusted-site.com");@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获取 Referer 头String referer = request.getHeader("Referer");// 允许直接访问(如浏览器地址栏输入)if (referer == null) return true;// 验证 Referer 是否在白名单boolean isValid = allowedDomains.stream().anyMatch(domain -> referer.startsWith(domain));if (!isValid) {response.sendError(403, "Forbidden: Invalid Referer");return false;}return true;} }
注册拦截器到 Spring MVC
@Configuration public class WebConfig implements WebMvcConfigurer {@Autowiredprivate RefererInterceptor refererInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 拦截图片路径registry.addInterceptor(refererInterceptor).addPathPatterns("/images/**");} }
测试效果
合法访问:
<img src="https://yourdomain.com/images/cat.jpg">
盗链访问:
<img src="https://yourdomain.com/images/cat.jpg"
在其他网站使用时返回 403
方案二:Token 签名校验
通过动态生成的签名 token 验证请求合法性(更安全)。
实现原理
生成图片 URL 时添加参数:
/images/cat.jpg?t=时间戳&sign=签名
服务器验证签名和时间戳有效性
实现步骤
生成签名工具类(用非对称加密RSA更安全)
public class TokenUtil {private static final String SECRET_KEY = "your_secret_123!";private static final long EXPIRE_SECONDS = 300; // 5分钟有效期// 生成带签名的URLpublic static String generateSignedUrl(String imagePath) {long timestamp = System.currentTimeMillis() / 1000;String sign = generateSign(imagePath, timestamp);return imagePath + "?t=" + timestamp + "&sign=" + sign;}// 生成签名 (使用HMAC-SHA256)private static String generateSign(String path, long timestamp) {String data = path + "|" + timestamp;return HmacUtils.hmacSha256Hex(SECRET_KEY, data);}// 验证签名public static boolean verifySign(String path, long timestamp, String sign) {// 检查过期时间long current = System.currentTimeMillis() / 1000;if (current - timestamp > EXPIRE_SECONDS) return false;// 验证签名String expectedSign = generateSign(path, timestamp);return expectedSign.equals(sign);} }
创建 Token 校验拦截器
@Component public class TokenInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String imagePath = request.getRequestURI();String sign = request.getParameter("sign");String timestampStr = request.getParameter("t");// 参数缺失检查if (sign == null || timestampStr == null) {response.sendError(400, "Missing token parameters");return false;}try {long timestamp = Long.parseLong(timestampStr);if (!TokenUtil.verifySign(imagePath, timestamp, sign)) {response.sendError(403, "Invalid token");return false;}} catch (NumberFormatException e) {response.sendError(400, "Invalid timestamp format");return false;}return true;} }
注册拦截器
@Configuration public class WebConfig implements WebMvcConfigurer {@Autowiredprivate TokenInterceptor tokenInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tokenInterceptor).addPathPatterns("/images/**");} }
生成安全链接的 Controller
@RestController public class ImageController {@GetMapping("/getImageUrl")public String getImageUrl(@RequestParam String imageName) {String imagePath = "/images/" + imageName;return TokenUtil.generateSignedUrl(imagePath);} }
前端使用示例
<!-- 前端先请求获取合法链接 --> <script> fetch('/getImageUrl?imageName=cat.jpg').then(res => res.text()).then(url => {const img = document.createElement('img');img.src = url;document.body.appendChild(img);}); </script>
两种方案对比
特性 | Referer 校验 | Token 校验 |
---|---|---|
安全性 | 中(Referer 可伪造) | 高(需破解签名算法) |
实现复杂度 | 简单 | 中等 |
链接有效期 | 永久 | 可控制时效 |
跨浏览器支持 | 部分浏览器禁用 Referer | 无兼容问题 |
防盗链效果 | 可防普通盗链 | 可防高级盗链 |
增强方案:双验证结合
// 在拦截器中组合验证
public boolean preHandle(...) {// 先验证 Refererif (!refererValid) {// 再验证 Token(给合法合作方提供Token访问方式)if (!tokenValid) {response.sendError(403);return false;}}return true;
}
注意事项
Referer 校验的局限性:
浏览器可能不发送 Referer(如HTTPS->HTTP)
可通过
<meta name="referrer" content="no-referrer">
绕过
Token 校验最佳实践:
使用 HTTPS 防止 Token 被截获
定期轮换密钥
对 IP 进行访问频率限制