原创

Spring Boot 实现防盗链功能

防盗链是保护资源服务器的常用方法,旨在防止未经授权的外部链接直接访问服务器上的资源,如图片、音频和视频文件。在本文中,我们将探讨防盗链的概念和原理,并结合 Spring Boot 提供一个完整的可运行示例。

一、防盗链概念

防盗链是一种通过限制资源访问来源的技术,通常通过检查 HTTP 请求头中的 Referer 字段来实现。如果请求的来源不是允许的域名,则拒绝该请求。除此之外,还可以结合 Token 和时间戳进一步提高安全性,确保链接只能在一定时间内有效。

二、防盗链原理

  1. Referer 校验
    • HTTP Referer 是浏览器在发送请求时附加的字段,用于标明请求的来源页面。
    • 服务器可以通过检查 Referer 是否属于信任的域名,拒绝其他来源的访问请求。
  2. Token 验证
    • 服务器为合法请求生成带签名的访问链接(包含 Token),客户端访问时携带该 Token。
    • 服务器通过验证 Token 是否正确来判断请求合法性。
  3. 时间限制
    • 通过在请求中附带时间戳参数,限制链接的有效期。
    • 服务器校验请求的时间戳与当前时间的差值,超出范围的请求将被拒绝。
通过以上三种机制,可以显著提高资源防盗链的安全性。

三、项目结构

以下是示例项目的目录结构:
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 文件夹中。

五、测试方式

  1. 启动 Spring Boot 项目。
  2. 测试访问图片资源:
    curl -X GET "http://localhost:8088/static/example.jpg?token=your-predefined-token&timestamp=$(date +%s)" -H "Referer: http://localhost:8088"
  3. 检查以下情况:
    • 如果 Referer 不正确,返回 403 Forbidden: Hotlinking not allowed
    • 如果 Token 无效,返回 403 Forbidden: Invalid Token
    • 如果时间戳超时,返回 403 Forbidden: Timestamp Expired
通过本文,您可以了解如何通过 Referer 校验、Token 验证和时间限制实现资源防盗链保护。如果有其他问题或需求,欢迎进一步探讨!
正文到此结束
Loading...