Java防盗链实现的核心思路
为什么需要防盗链
在Web应用中,静态资源如图片、视频、CSS、JS等若未做保护,可能被第三方站点直接引用,造成带宽损耗和安全隐患。防盗链通过校验资源请求的来源,确保资源只面向合法页面。对于Java后端,常见思路是通过 Referer 验证、签名URL、以及结合CDN的策略实现。通过这套方法,站点可以有效降低资源被越权访问的风险。
在实现时,兼容性与易用性是需要考虑的两个要点。Referer头在某些请求中可能被客户端或中间代理移除,导致误判,因此需要结合资源类型、允许的域名白名单、以及必要的回退策略来设计一个鲁棒的方案。
常见实现路径
最直接的方式是在服务器端对 Referer 头进行严格校验,对不在白名单中的请求返回 403;同时对图片、样式、脚本等静态资源可结合缓存策略以最小化性能影响。
另一种思路是使用<签名URL或带有有效期的令牌,只有携带正确签名和在有效期内的请求才可访问资源,通常与 CDN 结合实现更高的命中率和更低的后端压力。
基于Referer头的Java实现:Servlet Filter
原理和设计要点
Referer 验证的核心在于比对请求头中的来源域名与资源的合法加载地址。核心要点包括建立一个白名单域名集合、对静态资源设定适用范围、以及对空 Referer 做合理处理以提高兼容性。
实现应具备高效判断和可维护性,尽可能将域名对比从字符串查找提升到哈希集合,在高并发场景也能快速返回正确的状态码。
示例代码:实现一个简单的防盗链Filter
下面的示例展示了一个基础的防盗链 Filter,按域名白名单进行 Referer 校验,并对非法请求返回 403。请根据实际部署调整白名单和资源路径匹配逻辑。
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.Set;
import java.util.HashSet;public class RefererFilter implements Filter {private Set<String> allowedHosts = new HashSet<>();@Overridepublic void init(FilterConfig config) {// 真实场景应从配置加载allowedHosts.add("example.com");allowedHosts.add("static.example.com");}@Overridepublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;String referer = request.getHeader("Referer");if (referer == null || !isAllowed(referer)) {response.setStatus(HttpServletResponse.SC_FORBIDDEN);return;}chain.doFilter(req, res);}private boolean isAllowed(String referer) {// 提取域名部分,避免路径干扰try {String domain = new java.net.URI(referer).getHost();return allowedHosts.contains(domain);} catch (Exception e) {return false;}}@Overridepublic void destroy() {}
}在生产部署中,请结合具体资源的路径匹配和误拦截的应对策略来完善该实现。
白名单策略与资源类型的细分
路径和扩展名规则
对于不同的资源类型,应设定不同的校验策略。图片和视频等大文件通常需要更严格的 Referer 校验和更稳健的回退策略,而小型静态资源如 CSS/JS 的防盗链要求也要尽量降低误拦截。

一个常见做法是按资源路径前缀进行分组,仅对 /images、/videos 等目录开启防盗链,对其他目录保持宽松,以降低对正常业务的影响。
动态域名允许列表的维护
为便于运维,可以将白名单集中管理,通过配置中心或环境变量注入,确保在我们部署靶向防盗链策略时快速生效。
另外,在资源加载域名变化较多时,引入二级域名别名如 cdn1.yourdomain.com、cdn2.yourdomain.com,实现轮询加载和更高的容错性。
# application.properties 示例
security.referer.whitelist=example.com,static.example.com
security.referer.resources=/images,/videos
签名URL与CDN的组合防盗链方案
签名参数与有效期设计
签名 URL 通过在资源请求中附带一个<时间戳和签名字符串来实现短时有效访问。合理设置 expiry,避免长期有效的风控漏洞。
使用签名的好处在于资源的合法性仅在请求方拥有签名时才成立,即使 Referer 被仿造,若缺少有效签名,资源仍不可访问。
Java端生成签名URL的示例
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.net.URLEncoder;public class SignedUrlUtil {private static final String SECRET = "your-very-secret-key";public static String generate(String baseUrl, String path, long expiryEpoch) throws Exception {String toSign = path + "|" + expiryEpoch;String signature = sign(toSign, SECRET);String encoded = String.format("%s?path=%s&expires=%d&signature=%s",baseUrl, URLEncoder.encode(path, "UTF-8"),expiryEpoch, URLEncoder.encode(signature, "UTF-8"));return encoded;}private static String sign(String data, String key) throws Exception {Mac hmac = Mac.getInstance("HmacSHA256");hmac.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));byte[] digest = hmac.doFinal(data.getBytes("UTF-8"));return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);}
}在生产环境中,签名逻辑应与资源分发端点保持同步,并对密钥进行安全管理。
在生产环境中,应对时效性与密钥管理,使用配置化密钥和定期轮换策略。测试时用不同 Referer 测试边界条件。
性能与安全的取舍考量
对服务器压力的影响
引入防盗链会带来额外的处理逻辑,对高并发场景应尽量采用缓存与异步化校验,避免阻塞主路径。借助 CDN 的边缘计算能力,可以将大量请求在CDN端完成基本校验。
另外,空 Referer 的处理策略也至关重要,若过于严格可能导致合法请求被拦截,需提供回退方案,如允许某些已知 UA 的请求或对特定资源放宽。
应对策略与最佳实践
将校验逻辑模块化,通过 Filter/Interceptor 实现统一入口,方便后续维护与扩展。
同时,监控和日志分析很关键,记录被拦截请求的来源和资源路径,帮助你调整白名单和规则。


