本文目录导读:

我来为您提供一个完整的Java防盗链实现案例,防盗链主要用于防止其他网站直接引用您的资源(如图片、视频、文件等)。
基于Spring Boot的防盗链实现
使用拦截器(Interceptor)实现
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
/**
* 防盗链拦截器
*/
@Component
public class AntiHotlinkInterceptor implements HandlerInterceptor {
// 允许访问的域名白名单
private static final List<String> ALLOWED_DOMAINS = Arrays.asList(
"www.example.com",
"example.com",
"localhost",
"127.0.0.1"
);
// 需要防盗链保护的路径
private static final List<String> PROTECTED_PATHS = Arrays.asList(
"/images/",
"/files/",
"/video/",
"/static/resources/"
);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String requestURI = request.getRequestURI();
// 检查是否需要防盗链保护
if (!isProtectedPath(requestURI)) {
return true; // 不需要保护,直接放行
}
// 获取Referer头信息
String referer = request.getHeader("Referer");
// 直接访问(无Referer)或Referer为空
if (referer == null || referer.isEmpty()) {
// 可以选择允许直接访问或拒绝
// 这里设为拒绝直接访问
handleBlockedRequest(response);
return false;
}
// 检查Referer是否在白名单中
if (!isAllowedReferer(referer)) {
handleBlockedRequest(response);
return false;
}
return true; // 验证通过,放行
}
/**
* 判断是否是需要保护的路径
*/
private boolean isProtectedPath(String path) {
return PROTECTED_PATHS.stream().anyMatch(path::contains);
}
/**
* 判断Referer是否合法
*/
private boolean isAllowedReferer(String referer) {
try {
java.net.URL url = new java.net.URL(referer);
String domain = url.getHost().toLowerCase();
return ALLOWED_DOMAINS.stream()
.anyMatch(domain::equalsIgnoreCase);
} catch (Exception e) {
return false; // URL解析失败视为非法请求
}
}
/**
* 处理被阻止的请求
*/
private void handleBlockedRequest(HttpServletResponse response)
throws Exception {
response.setContentType("text/html;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
// 可以返回一个提示图片或者错误信息
response.getWriter().write("""
<html>
<body style="text-align:center;padding:50px;">
<h1 style="color:red;">403 - 禁止盗链</h1>
<p>您的请求来源不合法,请通过正规渠道访问。</p>
<img src="/images/anti-hotlink.png" alt="禁止盗链"/>
</body>
</html>
""");
}
}
使用过滤器(Filter)实现
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URL;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 防盗链过滤器
*/
@WebFilter(urlPatterns = "/*", filterName = "antiHotlinkFilter")
public class AntiHotlinkFilter implements Filter {
// 使用Set存储允许的域名,并且支持动态更新
private Set<String> allowedDomains = ConcurrentHashMap.newKeySet();
// 需要保护的资源类型
private Set<String> protectedExtensions = ConcurrentHashMap.newKeySet();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化允许的域名
allowedDomains.add("localhost");
allowedDomains.add("127.0.0.1");
allowedDomains.add("www.example.com");
allowedDomains.add("example.com");
// 初始化需要保护的资源类型
protectedExtensions.add(".jpg");
protectedExtensions.add(".jpeg");
protectedExtensions.add(".png");
protectedExtensions.add(".gif");
protectedExtensions.add(".bmp");
protectedExtensions.add(".ico");
protectedExtensions.add(".mp4");
protectedExtensions.add(".avi");
protectedExtensions.add(".pdf");
protectedExtensions.add(".mp3");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String requestURI = httpRequest.getRequestURI();
// 检查是否为需要保护的资源
if (!isProtectedResource(requestURI)) {
chain.doFilter(request, response); // 非保护资源直接放行
return;
}
// 检查请求来源
String referer = httpRequest.getHeader("Referer");
// 允许同域名站内的请求没有Referer(直接访问)或来自允许的域名
if (isValidReferer(referer)) {
chain.doFilter(request, response);
} else {
// 非法请求,返回错误信息
respondWithError(httpResponse);
}
}
/**
* 判断是否是受保护的资源
*/
private boolean isProtectedResource(String uri) {
// 检查文件扩展名
int lastDotIndex = uri.lastIndexOf(".");
if (lastDotIndex == -1 || lastDotIndex == uri.length() - 1) {
return false;
}
String extension = uri.substring(lastDotIndex).toLowerCase();
return protectedExtensions.contains(extension);
}
/**
* 验证Referer是否合法
*/
private boolean isValidReferer(String referer) {
// 无Referer的请求(直接访问)默认允许
if (referer == null || referer.isEmpty()) {
return true;
}
try {
URL url = new URL(referer);
String host = url.getHost().toLowerCase();
return allowedDomains.contains(host);
} catch (Exception e) {
return false;
}
}
/**
* 返回错误响应
*/
private void respondWithError(HttpServletResponse response) throws IOException {
// 方式1:返回自定义图片
// response.getOutputStream().write(getBlockImageBytes());
// 方式2:返回错误状态和信息
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("{\"code\":403,\"message\":\"请求被拒绝:检测到盗链行为\"}");
}
@Override
public void destroy() {
// 清空资源
allowedDomains.clear();
protectedExtensions.clear();
}
}
Web MVC配置类
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web MVC配置
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册防盗链拦截器
registry.addInterceptor(new AntiHotlinkInterceptor())
// 指定拦截的路径
.addPathPatterns("/images/**", "/files/**", "/video/**")
// 排除不需要拦截的路径
.excludePathPatterns("/images/logo.png", "/favicon.ico");
}
}
前端HTML测试页面
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">防盗链测试页面</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.test-card {
background: white;
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
img, video {
max-width: 100%;
height: auto;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1 style="text-align:center;">防盗链功能测试页面</h1>
<div class="test-card">
<h2>正常访问(本站)</h2>
<p>此图片来自本站服务器,应该可以正常显示:</p>
<img src="/images/sample.jpg" alt="本地图片">
</div>
<div class="test-card">
<h2>测试视频资源</h2>
<p>此视频来自本站服务器:</p>
<video controls>
<source src="/video/sample.mp4" type="video/mp4">
您的浏览器不支持视频标签
</video>
</div>
<div class="test-card">
<h2>模拟外部引用</h2>
<p>以下按钮模拟外部网站请求本站资源(实际测试中会携带外部Referer):</p>
<button onclick="testExternalLink()">模拟外部链接点击</button>
<button onclick="testDirectAccess()">直接访问测试</button>
<div id="result"></div>
</div>
<script>
// 模拟外部链接引用
function testExternalLink() {
// 这里在实际场景中,请求会带有外部网站的Referer
var imgUrl = '/images/protected-image.png';
fetch(imgUrl, {
headers: {
'Referer': 'https://evil-site.com/page.html'
}
})
.then(response => {
if (response.status === 403) {
document.getElementById('result').innerHTML =
'<p style="color:red;">❌ 请求被拒�绝:检测到盗链行为(403)</p>';
} else {
document.getElementById('result').innerHTML =
'<p style="color:green;">✅ 请求成功</p>';
}
})
.catch(error => {
document.getElementById('result').innerHTML =
'<p style="color:red;">❌ 请求出错: ' + error.message + '</p>';
});
}
// 直接访问测试
function testDirectAccess() {
var img = new Image();
img.src = '/images/protected-image.png';
img.onload = function() {
document.getElementById('result').innerHTML =
'<p style="color:green;">✅ 直接访问成功(图片已加载)</p>';
};
img.onerror = function() {
document.getElementById('result').innerHTML =
'<p style="color:red;">❌ 直接访问被拒绝</p>';
};
}
</script>
</body>
</html>
高级防盗链实现(基于令牌)
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.concurrent.ConcurrentHashMap;
/**
* 基于令牌的防盗链工具类
*/
@Component
public class TokenBasedAntiHotlink {
// 存储有效的令牌(实际场景可能使用Redis等分布式存储)
private ConcurrentHashMap<String, Long> tokenStore = new ConcurrentHashMap<>();
// 令牌有效期(毫秒)
private static final long TOKEN_VALIDITY = 24 * 60 * 60 * 1000; // 24小时
// 加密密钥
private static final String SECRET_KEY = "your-secret-key-here";
/**
* 生成访问令牌
*/
public String generateToken(String resourcePath) {
long timestamp = System.currentTimeMillis();
String rawToken = resourcePath + timestamp + SECRET_KEY;
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(rawToken.getBytes());
String token = Base64.getUrlEncoder().withoutPadding()
.encodeToString(hash);
// 存储令牌和时间戳
tokenStore.put(token, timestamp);
return token + "." + timestamp;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("加密算法不可用");
}
}
/**
* 验证令牌
*/
public boolean validateToken(String token, String resourcePath) {
try {
// 解析令牌和时间戳
String[] parts = token.split("\\.");
if (parts.length != 2) {
return false;
}
String tokenPart = parts[0];
long timestamp = Long.parseLong(parts[1]);
// 检查是否过期
if (System.currentTimeMillis() - timestamp > TOKEN_VALIDITY) {
tokenStore.remove(tokenPart);
return false;
}
// 验证令牌是否存在
Long storedTimestamp = tokenStore.get(tokenPart);
if (storedTimestamp == null || storedTimestamp != timestamp) {
return false;
}
// 验证令牌与资源路径的关联
String expectedToken = generateHash(resourcePath + timestamp + SECRET_KEY);
if (!expectedToken.equals(tokenPart)) {
return false;
}
// 验证成功,移除令牌(一次性使用)
tokenStore.remove(tokenPart);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 生成哈希值
*/
private String generateHash(String input) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(input.getBytes());
return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
}
}
配置文件示例(application.yml)
# 应用程序配置
spring:
application:
name: anti-hotlink-demo
mvc:
static-path-pattern: /static/**
# 自定义防盗链配置
anti-hotlink:
# 是否启用防盗链
enabled: true
# 允许的域名列表(使用逗号分隔)
allowed-domains: >
www.example.com,
example.com,
localhost,
*.example.com
# 需要保护的路径模式
protected-paths:
- /images/**
- /files/**
- /video/**
- /static/**
# 排除的路径
excluded-paths:
- /images/logo.png
- /favicon.ico
# 拒绝行为
block-redirect-url: /error/403.html
block-message: 禁止外部链接访问本站资源
使用说明
主要特点:
- 多层防护:支持拦截器、过滤器、令牌多种方式
- 灵活配置:可以通过配置文件或代码设置白名单
- 多种保护:支持图片、视频、文档等多种资源类型
- 错误处理:自动识别盗链行为并返回相应错误
部署要点:
- 确保服务器时区正确
- 合理设置白名单域名
- 对于分布式环境,建议使用Redis存储令牌
- 定期更新密钥和安全性配置
这个案例提供了完整的防盗链解决方案,可以根据实际需求选择合适的实现方式。