Spring Boot 实现图片防盗链教程(Referer 校验 + Token 签名校验)
本文将详细讲解两种防盗链实现方案,并提供完整代码示例。
方案一:Referer 校验
通过检查 HTTP 请求头中的 Referer
字段判断来源是否合法。
实现步骤
-
创建 Referer 拦截器
java@Component public class RefererInterceptor implements HandlerInterceptor { private final List<String> allowedDomains = Arrays.asList( "https://yourdomain.com", "https://trusted-site.com" ); @Override public 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
java@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private RefererInterceptor refererInterceptor; @Override public 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更安全)
javapublic class TokenUtil { private static final String SECRET_KEY = "your_secret_123!"; private static final long EXPIRE_SECONDS = 300; // 5分钟有效期 // 生成带签名的URL public 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 校验拦截器
java@Component public class TokenInterceptor implements HandlerInterceptor { @Override public 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; } }
-
注册拦截器
java@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private TokenInterceptor tokenInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenInterceptor) .addPathPatterns("/images/**"); } }
-
生成安全链接的 Controller
java@RestController public class ImageController { @GetMapping("/getImageUrl") public String getImageUrl(@RequestParam String imageName) { String imagePath = "/images/" + imageName; return TokenUtil.generateSignedUrl(imagePath); } }
-
前端使用示例
html<!-- 前端先请求获取合法链接 --> <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 | 无兼容问题 |
防盗链效果 | 可防普通盗链 | 可防高级盗链 |
增强方案:双验证结合
java
// 在拦截器中组合验证
public boolean preHandle(...) {
// 先验证 Referer
if (!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 进行访问频率限制
-