防盗链是保护资源服务器的常用方法,旨在防止未经授权的外部链接直接访问服务器上的资源,如图片、音频和视频文件。在本文中,我们将探讨防盗链的概念和原理,并结合 Spring Boot 提供一个完整的可运行示例。
一、防盗链概念
防盗链是一种通过限制资源访问来源的技术,通常通过检查 HTTP 请求头中的 Referer
字段来实现。如果请求的来源不是允许的域名,则拒绝该请求。除此之外,还可以结合 Token
和时间戳进一步提高安全性,确保链接只能在一定时间内有效。
二、防盗链原理
-
Referer 校验:
- HTTP Referer 是浏览器在发送请求时附加的字段,用于标明请求的来源页面。
- 服务器可以通过检查 Referer 是否属于信任的域名,拒绝其他来源的访问请求。
-
Token 验证:
- 服务器为合法请求生成带签名的访问链接(包含 Token),客户端访问时携带该 Token。
- 服务器通过验证 Token 是否正确来判断请求合法性。
-
时间限制:
- 通过在请求中附带时间戳参数,限制链接的有效期。
- 服务器校验请求的时间戳与当前时间的差值,超出范围的请求将被拒绝。
通过以上三种机制,可以显著提高资源防盗链的安全性。
三、项目结构
以下是示例项目的目录结构:
src
├── main
│ ├── java
│ │ └── com.demo
│ │ ├── DemoApplication.java
│ │ ├── filter
│ │ │ ├── StaticResourceFilter.java
│ │ │ ├── TokenValidator.java
│ │ │ └── TimeValidator.java
│ └── resources
│ └── static
│ └── images
└──711815.jpeg
四、核心代码实现
1. 主应用程序入口
package com.et;
import com.et.filter.StaticResourceFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public FilterRegistrationBean<StaticResourceFilter> staticResourceFilter() {
FilterRegistrationBean<StaticResourceFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new StaticResourceFilter());
registrationBean.addUrlPatterns("/images/*");
return registrationBean;
}
}
2. 静态资源访问过滤器
package com.et.filter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class StaticResourceFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// validate Referer
String referer = httpRequest.getHeader("Referer");
String allowedDomain = "http://localhost:8088";
if (referer == null || !referer.startsWith(allowedDomain)) {
httpResponse.getWriter().write("403 Forbidden: Hotlinking not allowed");
return;
}
// validate Token
if (!TokenValidator.validateToken(httpRequest, httpResponse)) {
return;
}
// validate Timestamp
if (!TimeValidator.validateTimestamp(httpRequest, httpResponse)) {
return;
}
chain.doFilter(request, response);
}
}
3. Token 验证工具
package com.et.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TokenValidator {
public static boolean validateToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
String token = request.getParameter("token");
String validToken = "your-predefined-token"; //set your predefined token here
if (token == null || !token.equals(validToken)) {
response.getWriter().write("403 Forbidden: Invalid Token");
return false;
}
return true;
}
}
4. 时间限制验证工具
package com.et.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Instant;
public class TimeValidator {
private static final long ALLOWED_TIME_DIFF = 300; // offset in seconds( 300 seconds)
public static boolean validateTimestamp(HttpServletRequest request, HttpServletResponse response) throws IOException {
String timestampStr = request.getParameter("timestamp");
if (timestampStr == null) {
response.getWriter().write("403 Forbidden: Missing Timestamp");
return false;
}
try {
long timestamp = Long.parseLong(timestampStr);
long currentTimestamp = Instant.now().getEpochSecond();
if (Math.abs(currentTimestamp - timestamp) > ALLOWED_TIME_DIFF) {
response.getWriter().write("403 Forbidden: Timestamp Expired");
return false;
}
} catch (NumberFormatException e) {
response.getWriter().write("403 Forbidden: Invalid Timestamp");
return false;
}
return true;
}
}
5. 静态资源示例
将一个图片文件711815.jpeg
放入 src/main/resources/static/images
文件夹中。
以上只是一些关键代码,所有代码请参见下面代码仓库
代码仓库
- https://github.com/Harries/springboot-demo(antihotlinking)
五、测试方式
- 启动 Spring Boot 项目。
- 测试访问图片资源:
curl -X GET "http://localhost:8088/static/example.jpg?token=your-predefined-token×tamp=$(date +%s)" -H "Referer: http://localhost:8088"
- 检查以下情况:
- 如果 Referer 不正确,返回
403 Forbidden: Hotlinking not allowed
。 - 如果 Token 无效,返回
403 Forbidden: Invalid Token
。 - 如果时间戳超时,返回
403 Forbidden: Timestamp Expired
。
- 如果 Referer 不正确,返回
通过本文,您可以了解如何通过 Referer 校验、Token 验证和时间限制实现资源防盗链保护。如果有其他问题或需求,欢迎进一步探讨!