图片防盗链系统的核心实现方案有两种:基于http referer的校验和基于token的动态链接验证。1. 基于http referer的校验通过检查请求头中的referer字段判断来源是否合法,但该方式易被伪造或因隐私设置失效;2. 基于token的动态链接方案在生成图片链接时附加带签名和时间戳的token,并在服务器端验证其有效性,安全性更高。具体实现中需完成token生成、传递、验证流程,并结合spring boot拦截器统一处理验证逻辑,同时面临性能开销、cdn兼容性、浏览器缓存等挑战。

在Java中构建图片防盗链系统,核心思路是通过服务器端对请求的来源进行判断。这通常意味着我们会检查HTTP请求头中的Referer信息,或者更安全地,为图片资源生成带有时间戳或签名的动态链接(token),在图片被请求时,后端再验证这个token的有效性,从而决定是否返回图片数据。这种做法能有效防止图片被未经授权的网站直接引用,保护你的带宽和内容版权。

构建一个图片防盗链系统,可以从两个主要方向入手:基于HTTP Referer的简单校验,以及更健壮的基于Token的动态链接方案。
方案一:基于HTTP Referer的校验
立即学习“Java免费学习笔记(深入)”;

这是最直接也最容易实现的方式。当浏览器请求一个资源时,通常会在HTTP请求头中带上Referer字段,指明这个请求是从哪个页面发起的。我们可以在服务器端检查这个字段。
假设你有一个Spring Boot应用,可以这样做:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Controller
public class ImageController {
private final String ALLOWED_DOMAIN = "http://yourdomain.com"; // 替换为你的域名
@GetMapping("/images/{imageName:.+}") // .+: 匹配文件名,包括点
@ResponseBody
public void getImage(@PathVariable String imageName,
@RequestHeader(value = "Referer", required = false) String referer,
HttpServletResponse response) throws IOException {
if (referer == null || !referer.startsWith(ALLOWED_DOMAIN)) {
// 如果Referer为空或者不是来自允许的域名,则拒绝访问
response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 Forbidden
// 可以返回一个默认的“禁止盗链”图片,或者直接结束
return;
}
// 假设图片存储在某个目录下
Path imagePath = Paths.get("/path/to/your/images/", imageName); // 替换为你的图片存储路径
if (Files.exists(imagePath) && Files.isReadable(imagePath)) {
response.setContentType(Files.probeContentType(imagePath));
try (InputStream is = Files.newInputStream(imagePath)) {
is.transferTo(response.getOutputStream());
}
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND); // 404 Not Found
}
}
}这段代码很简单,但需要注意的是,Referer头很容易被伪造,或者在某些隐私设置下可能不会发送。所以,这更多是一种初级的、象征性的防护。
方案二:基于Token的动态链接防盗链
这是更推荐、更健壮的方案。核心思想是,当你的页面(比如yourdomain.com/article/123)加载时,其中包含的图片链接不再是静态的/images/pic.jpg,而是动态生成的,例如/images/pic.jpg?token=XYZ×tamp=123。服务器端在处理/images/pic.jpg的请求时,会验证这个token和timestamp是否有效。
实现步骤概览:
Token生成示例(一个简化的HMAC签名):
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class ImageTokenUtil {
private static final String SECRET_KEY = "your_very_secret_key_here"; // 生产环境请使用更复杂的密钥
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final long TOKEN_EXPIRATION_MILLIS = 5 * 60 * 1000; // Token有效期:5分钟
public static String generateSignedToken(String imagePath) {
long expires = System.currentTimeMillis() + TOKEN_EXPIRATION_MILLIS;
String dataToSign = imagePath + ":" + expires; // 待签名数据:图片路径 + 过期时间
try {
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM);
mac.init(secretKeySpec);
byte[] hmacSha256 = mac.doFinal(dataToSign.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(hmacSha256);
return Base64.getUrlEncoder().withoutPadding().encodeToString(dataToSign.getBytes(StandardCharsets.UTF_8)) + "." + signature;
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
// 实际应用中需要更好的异常处理
e.printStackTrace();
return null;
}
}
public static boolean validateSignedToken(String fullToken) {
if (fullToken == null || !fullToken.contains(".")) {
return false;
}
String[] parts = fullToken.split("\.");
if (parts.length != 2) {
return false;
}
String encodedData = parts[0];
String receivedSignature = parts[1];
try {
String dataToSign = new String(Base64.getUrlDecoder().decode(encodedData), StandardCharsets.UTF_8);
String[] dataParts = dataToSign.split(":");
if (dataParts.length != 2) {
return false;
}
String imagePath = dataParts[0];
long expires = Long.parseLong(dataParts[1]);
if (System.currentTimeMillis() > expires) {
// Token已过期
return false;
}
// 重新计算签名并比对
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM);
mac.init(secretKeySpec);
byte[] hmacSha256 = mac.doFinal(dataToSign.getBytes(StandardCharsets.UTF_8));
String expectedSignature = Base64.getUrlEncoder().withoutPadding().encodeToString(hmacSha256);
return expectedSignature.equals(receivedSignature);
} catch (Exception e) {
// 签名解析或验证失败
e.printStackTrace();
return false;
}
}
}图片服务Controller结合Token验证:
// 假设ImageController中引入了ImageTokenUtil
// ... 其他导入和类定义 ...
@GetMapping("/secure-images/{encodedToken}/{imageName:.+}")
@ResponseBody
public void getSecureImage(@PathVariable String encodedToken,
@PathVariable String imageName,
HttpServletResponse response) throws IOException {
// 实际的图片路径需要从token中解析出来,或者这里只作为辅助验证
// 假设token中包含完整的图片路径信息,这里简化处理
String fullToken = encodedToken + "." + imageName; // 这里需要根据实际token生成方式调整
if (!ImageTokenUtil.validateSignedToken(fullToken)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 Forbidden
return;
}
// 从token中解析出实际的图片路径,或者直接使用imageName(如果token只负责验证权限)
// 为了简化,这里仍然假设imageName是实际的文件名,但实际应用中,token应包含完整路径或校验路径
Path imagePath = Paths.get("/path/to/your/images/", imageName);
if (Files.exists(imagePath) && Files.isReadable(imagePath)) {
response.setContentType(Files.probeContentType(imagePath));
try (InputStream is = Files.newInputStream(imagePath)) {
is.transferTo(response.getOutputStream());
}
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND); // 404 Not Found
}
}在HTML页面中,图片链接会变成类似这样:
<img src="/secure-images/eyJpbWFnZVBhdGgiOiIvZm9vL2Jhci5qcGcifQ.somesignature/pic.jpg" />
这只是一个示意,实际的URL结构和Token的生成与解析需要严格对应。
谈到传统的防盗链方法,我们通常会想到基于HTTP Referer的校验,或者一些前端JavaScript的判断。这些方法之所以效果不佳,甚至可以说“防君子不防小人”,主要有几个原因:
首先,Referer头信息极易伪造。攻击者或者一些爬虫工具,可以非常轻松地在请求中伪造Referer头,将其设置为你的合法域名。这就好比你家门上贴了个“本小区住户请走正门”的牌子,但小偷换身衣服就混进来了,你根本无法辨别。很多浏览器出于用户隐私考虑,也可能默认不发送Referer,或者只发送部分信息,这导致即使是合法用户也可能被误判。
其次,前端JavaScript防盗链形同虚设。所有在浏览器端执行的逻辑,都可以在开发者工具中被轻易查看、修改甚至禁用。你用JavaScript判断当前页面URL是否是你的域名,如果不是就替换图片链接或者阻止加载,这对于稍微懂点前端知识的人来说,绕过简直不费吹灰之力。就好比你把家里的钥匙藏在门口地毯下,并希望小偷找不到——这显然不现实。
再者,CDN缓存可能带来复杂性。如果你的图片通过CDN加速,CDN节点会缓存你的图片。当CDN收到请求时,它可能不会携带原始的Referer信息到你的源站,或者CDN自身的配置会影响Referer的传递。这使得基于Referer的校验在CDN环境下变得更加复杂,甚至失效。你得考虑CDN如何与你的防盗链逻辑配合,这本身就是个挑战。
最后,这些方法都无法阻止直接下载。如果有人直接知道了你的图片URL,并且你的服务器没有做任何校验,他可以直接通过下载工具、脚本等方式批量下载你的图片,完全绕过你的网页访问。
正是因为这些局限性,我们才需要更底层的、服务器端强制执行的、基于加密和时效性的验证机制,也就是Token防盗链。
实现一个健壮的图片防盗链系统,远不止写几行代码那么简单。这里面涉及到不少关键的技术点和需要面对的挑战:
关键技术点:
安全Token生成与验证:
URL重写与路由:
/images/secure/{token}/{filename}。图片流式传输:
Content-Type(如image/jpeg, image/png等)和Content-Length头,确保浏览器能正确解析和显示图片。Files.newInputStream和transferTo方法可以高效地传输文件。错误处理与用户体验:
面临的挑战:
这些挑战要求我们在设计系统时,不仅要考虑功能实现,更要从性能、可扩展性、安全性和运维角度进行全面权衡。
在Spring Boot项目中实现图片资源保护,我们可以利用Spring框架的特性,比如拦截器(Interceptor)或者过滤器(Filter),来集中处理防盗链逻辑,而不是在每个图片处理方法中重复代码。这使得代码更清晰,维护起来也方便。
这里我们以基于Token的防盗链为例,展示如何通过Spring Interceptor来优雅地实现。
1. Token生成工具类(同上,或更完善):ImageTokenUtil.java (保持不变,或根据实际需求调整,例如generateSignedToken方法可能需要传入图片资源的唯一ID而不是路径,然后通过ID查找实际路径)
2. 图片资源控制器: 这个控制器会变得非常简洁,因为它不再直接处理防盗链逻辑,而是专注于读取和返回图片文件。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Controller
public class SecureImageController {
private final String IMAGE_BASE_PATH = "/path/to/your/images/"; // 你的图片存储根目录
@GetMapping("/secure-images/{imageName:.+}") // 这里的URL不再包含token,token会在拦截器中处理
@ResponseBody
public void getSecureImage(@PathVariable String imageName,
HttpServletResponse response) throws IOException {
// 假设拦截器已经验证了权限,这里直接提供图片
Path imagePath = Paths.get(IMAGE_BASE_PATH, imageName);
if (Files.exists(imagePath) && Files.isReadable(imagePath)) {
response.setContentType(Files.probeContentType(imagePath));
try (InputStream is = Files.newInputStream(imagePath)) {
is.transferTo(response.getOutputStream());
}
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND); // 图片不存在
}
}
}3. 防盗链拦截器:
这是核心部分,它会在请求到达SecureImageController之前进行拦截和验证。
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
public class AntiHotlinkInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求参数中获取token,例如:/secure-images/pic.jpg?token=XYZ
String token = request.getParameter("token");
String requestUri = request.getRequestURI(); // /secure-images/pic.jpg
String imageName = requestUri.substring(requestUri.lastIndexOf('/') + 1); // pic.jpg
// 假设token中需要包含imageName作为验证的一部分
// 实际场景中,token可能只包含一个资源ID,然后通过ID去查找资源路径
String dataToValidate = imageName; // 或者更复杂的,从token中解析出原始数据
if (token == null || !ImageTokenUtil.validateSignedToken(token)) {
// Token无效或缺失
response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 Forbidden
// 可以选择返回一个默认的“禁止盗链”图片,或者一个错误页面
// response.sendRedirect("/error-images/hotlink-forbidden.png");
return false; // 阻止请求继续向下执行
}
// 如果token验证通过,则放行
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在Controller方法执行后,视图渲染前执行
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在整个请求完成后执行,用于资源清理等
}
}4. 配置拦截器:
以上就是如何在Java中构建图片防盗链接系统 Java判断来源并控制资源访问的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号