0

0

如何用Java实现防盗链功能 Java控制资源访问来源方式

爱谁谁

爱谁谁

发布时间:2025-07-19 17:43:01

|

840人浏览过

|

来源于php中文网

原创

防盗链功能在java中可通过多种方式实现。1. 基于http referer头校验,使用servlet filter或spring interceptor拦截请求,检查referer字段是否来自允许的域名,对特定资源类型(如图片、视频)进行访问控制;2. 使用签名url/令牌机制,通过hmac算法生成带过期时间与签名的url,服务器端验证签名与有效期,防止伪造与长期盗用;3. 结合session或cookie认证,确保资源仅对已登录用户开放;4. 引入oauth2或jwt,在api驱动或微服务架构中实现安全授权访问。这些策略分别适用于不同安全等级需求,其中签名url是目前最可靠的方式。

如何用Java实现防盗链功能 Java控制资源访问来源方式

用Java实现防盗链功能,核心思路其实很简单:就是在服务器端对请求资源的来源进行校验。最直接、也最常用的一种方式,就是检查HTTP请求头中的Referer字段,看看这个请求是从哪个页面跳转过来的。如果Referer不是我们自己的域名,那很可能就是盗链,直接拒绝访问或者返回一个错误。当然,这只是个起点,更健壮的方案会涉及到签名URL或令牌机制。

如何用Java实现防盗链功能 Java控制资源访问来源方式

解决方案

在我看来,防盗链这事儿,无非就是想让你的资源乖乖地呆在自己的地盘上,别被别人“顺手牵羊”去展示,还白白消耗你的带宽。实现起来,Java提供了不少灵活的姿势。

1. 基于HTTP Referer头的校验(Servlet Filter/Spring Interceptor)

立即学习Java免费学习笔记(深入)”;

如何用Java实现防盗链功能 Java控制资源访问来源方式

这是最基础、也最容易上手的办法。原理就是当浏览器请求一个资源(比如图片、CSS、JS文件),它通常会带上一个Referer头,告诉服务器这个请求是从哪个页面发起的。我们只需要在服务器端拦截这个请求,检查Referer头是否合法。

你可以写一个Servlet Filter或者在Spring Boot里用HandlerInterceptor来做这件事。

如何用Java实现防盗链功能 Java控制资源访问来源方式
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

public class AntiLeechFilter implements Filter {

    // 允许的域名列表,这里可以从配置文件读取
    private List<String> allowedDomains;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 实际项目中,这里应该从配置加载,例如 application.properties
        String domainsStr = filterConfig.getInitParameter("allowedDomains");
        if (domainsStr != null) {
            this.allowedDomains = Arrays.asList(domainsStr.split(","));
        } else {
            this.allowedDomains = Arrays.asList("yourdomain.com", "www.yourdomain.com"); // 示例
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String referer = httpRequest.getHeader("Referer");
        String requestURI = httpRequest.getRequestURI();

        // 仅对特定资源类型进行防盗链,比如图片、视频等
        if (requestURI.matches(".*\.(jpg|jpeg|png|gif|mp4|webm|ogg)$")) {
            boolean isAllowed = false;
            if (referer == null || referer.isEmpty()) {
                // 如果Referer为空,可能是直接访问,也可能是某些浏览器或代理设置导致,
                // 这时需要根据业务决定是否放行。我通常会放行,因为有些合法用户可能没有Referer。
                isAllowed = true;
            } else {
                for (String domain : allowedDomains) {
                    if (referer.contains(domain)) {
                        isAllowed = true;
                        break;
                    }
                }
            }

            if (!isAllowed) {
                // 不允许访问,可以返回403 Forbidden,或者重定向到默认图片/提示页面
                httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                // 或者 httpResponse.sendRedirect("/path/to/forbidden_image.png");
                return; // 阻止请求继续
            }
        }
        chain.doFilter(request, response); // 放行请求
    }

    @Override
    public void destroy() {
        // 清理资源
    }
}

然后在web.xml(或Spring Boot的@ServletComponentScan)中配置这个Filter。

2. 签名URL/令牌机制

Referer校验虽然简单,但有个很明显的弊端:Referer头可以被伪造,或者在某些隐私设置下根本不发送。所以,如果你的资源价值更高,或者对安全性有更严格的要求,那就得上“签名URL”这套了。

签名URL的思路是:当用户访问你的网页时,你在后台动态生成一个带有“签名”的资源URL。这个签名通常包含资源路径、过期时间,以及一个只有你服务器知道的密钥,然后用加密算法(比如HMAC)生成一个哈希值。当用户浏览器请求这个资源时,服务器会解析URL中的签名,重新计算一次哈希值,如果和URL中的签名匹配,并且没过期,才允许访问。

这种方式的好处是,即使别人拿到了这个URL,如果过期了或者没有你的密钥,也无法生成新的有效签名,从而有效防止盗链。

生成签名URL的简化逻辑:

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class SignedUrlGenerator {

    private static final String SECRET_KEY = "your_super_secret_key_for_signing"; // 确保足够复杂且保密
    private static final String HMAC_ALGORITHM = "HmacSHA256";

    public static String generateSignedUrl(String resourcePath, long expirationTimeMillis)
            throws NoSuchAlgorithmException, InvalidKeyException {
        // 组合要签名的数据:资源路径 + 过期时间
        String dataToSign = resourcePath + ":" + expirationTimeMillis;

        Mac hmacSha256 = Mac.getInstance(HMAC_ALGORITHM);
        SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM);
        hmacSha256.init(secretKeySpec);

        byte[] signatureBytes = hmacSha256.doFinal(dataToSign.getBytes(StandardCharsets.UTF_8));
        String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes);

        // 构建签名URL,例如:/resources/image.jpg?expires=1678886400000&signature=xxxx
        return String.format("%s?expires=%d&signature=%s",
                resourcePath, expirationTimeMillis, URLEncoder.encode(signature, StandardCharsets.UTF_8.name()));
    }

    public static boolean validateSignedUrl(String fullUrl, String requestedResourcePath)
            throws NoSuchAlgorithmException, InvalidKeyException {
        // 实际解析URL参数,这里简化
        // 从fullUrl中解析出expires和signature
        // 例如:String url = "/resources/image.jpg?expires=1678886400000&signature=xxxx";
        // 假设解析后:
        long expires = 0; // 从URL参数中获取
        String signatureFromUrl = ""; // 从URL参数中获取

        // 简单示例解析URL参数
        try {
            String queryString = fullUrl.substring(fullUrl.indexOf("?") + 1);
            String[] params = queryString.split("&");
            for (String param : params) {
                if (param.startsWith("expires=")) {
                    expires = Long.parseLong(param.substring("expires=".length()));
                } else if (param.startsWith("signature=")) {
                    signatureFromUrl = param.substring("signature=".length());
                }
            }
        } catch (Exception e) {
            return false; // URL格式不正确
        }


        if (System.currentTimeMillis() > expires) {
            System.out.println("URL已过期");
            return false; // URL过期
        }

        String dataToSign = requestedResourcePath + ":" + expires;

        Mac hmacSha256 = Mac.getInstance(HMAC_ALGORITHM);
        SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_UTF8), HMAC_ALGORITHM);
        hmacSha256.init(secretKeySpec);

        byte[] expectedSignatureBytes = hmacSha256.doFinal(dataToSign.getBytes(StandardCharsets.UTF_8));
        String expectedSignature = Base64.getUrlEncoder().withoutPadding().encodeToString(expectedSignatureBytes);

        return expectedSignature.equals(signatureFromUrl);
    }

    public static void main(String[] args) throws Exception {
        String resource = "/path/to/my_video.mp4";
        long expiration = System.currentTimeMillis() + 60 * 60 * 1000; // 1小时后过期

        String signedUrl = generateSignedUrl(resource, expiration);
        System.out.println("生成的签名URL: " + signedUrl);

        // 模拟服务器端校验
        String fullRequestUrl = "http://localhost:8080" + signedUrl; // 实际请求的完整URL
        boolean isValid = validateSignedUrl(fullRequestUrl, resource);
        System.out.println("签名URL是否有效: " + isValid);

        // 模拟过期
        Thread.sleep(100); // 假设时间流逝
        boolean isValidAfterExpire = validateSignedUrl(fullRequestUrl, resource);
        System.out.println("签名URL是否有效 (模拟过期): " + isValidAfterExpire);
    }
}

在实际应用中,你会在你的HTML页面或API响应中返回这些签名过的URL。当用户浏览器发起请求时,你的服务器端Filter或Controller会捕获到这个请求,然后调用validateSignedUrl方法进行校验。

为什么需要实现防盗链?防盗链能带来哪些实际好处?

说实话,防盗链这事儿,很多人可能觉得有点“小题大做”,或者觉得“我的资源也没那么重要”。但站在一个运营者的角度,或者一个有点儿成本意识的开发者来看,它能带来的好处可不是一星半点。

首先,最直接的就是节省带宽成本。想想看,如果你的网站上有大量图片、视频或者其他大文件,而这些文件被其他网站直接引用(盗链),那么每一次用户访问那个盗链网站,你的服务器就得为他们的用户提供数据传输服务。这就像你家水管被别人偷偷接走了,水费还得你来付。积少成多,尤其对于流量大的网站,这笔费用可不低。防盗链就像给你的水管装了个阀门,只有你允许的人才能用。

其次,是保护你的内容版权和知识产权。你辛辛苦苦制作的图片、视频、文档,本意是在你的平台上展示,承载你的品牌价值。一旦被盗链,它们就脱离了你的语境,可能出现在任何不相关的网站上,甚至是被恶意使用。这不仅稀释了你内容的价值,也可能带来不必要的法律风险。防盗链确保了你的内容始终在你的控制之下被消费。

再者,从某种程度上讲,它也能提升你网站的整体性能和稳定性。虽然这听起来有点间接,但如果服务器不用处理大量的盗链请求,那么它就有更多的资源去响应合法用户的请求,从而提升用户体验。减少了无谓的负载,服务器崩溃的风险自然也小了。

最后,可能还有点儿SEO上的考量。虽然这不直接是防盗链的主要目的,但如果你大量内容被盗链,搜索引擎可能会觉得你的内容在多个地方重复出现,从而影响你网站的原创性和排名。当然,这只是一个非常次要的因素,但聊胜于无嘛。

拍我AI
拍我AI

AI视频生成平台PixVerse的国内版本

下载

所以,在我看来,防盗链不仅仅是技术活,更是一种资源管理和成本控制的策略。尤其对于那些内容密集型网站,它几乎是必备的功能。

Java实现防盗链时,如何处理Referer头缺失或被伪造的情况?

这是个老生常谈的问题,也是Referer校验最大的痛点。我个人在处理这个问题时,总是带着一种“既要防君子,也要防小人”的心态。

1. Referer头缺失:

Referer头缺失的情况并不少见。比如:

  • 用户隐私设置: 很多浏览器或安全插件允许用户禁用发送Referer头,以保护隐私。
  • 直接访问: 用户直接在浏览器地址栏输入你的资源URL(例如,他把你的图片链接收藏了),或者从本地文件打开HTML页面引用你的资源,这时通常也不会有Referer
  • HTTPS到HTTP: 从HTTPS页面跳转到HTTP页面时,Referer头可能会被浏览器出于安全考虑而移除。
  • 某些代理或防火墙: 它们可能会剥离或修改HTTP头。

如果你的Referer校验逻辑是“没有Referer就拒绝”,那很可能会误伤到很多合法用户。我的建议是,对于Referer为空的情况,要根据你的业务场景来判断。 对于一些公开的、无害的资源(比如网站Logo、公共CSS/JS),我倾向于放行。而对于核心内容(比如付费视频、用户上传的私密图片),则绝对不能仅仅因为Referer为空就放行,这时就需要更严格的校验手段了。

2. Referer头被伪造:

这个就更麻烦了。HTTP头说白了就是一些文本信息,理论上,任何一个有心人都可以通过抓包工具、编程脚本(比如使用curl或者Pythonrequests库),轻易地伪造Referer头,让它看起来像是从你的合法域名发出的。这种情况下,单纯依赖Referer校验,形同虚设。它能防住那些不懂技术的“小白”,但对于稍微有点技术背景的人来说,简直是小菜一碟。

解决方案:

面对Referer的这些缺陷,我的经验是:不要把所有宝都压在Referer上。

  • 对于简单场景,Referer足够: 如果你的防盗链需求只是为了防止一些不经意的盗链行为,或者资源价值没那么高,那Referer校验作为第一道防线,成本低、见效快,完全可以接受。
  • 对于高价值或敏感资源,必须上签名URL: 这是解决Referer缺陷的终极武器。就像前面提到的,签名URL不依赖于任何客户端可伪造的HTTP头,而是基于你服务器独有的密钥和加密算法。即使攻击者知道了你的资源路径,没有密钥也无法生成有效的签名,或者即使拿到了一个签名的URL,也很快会过期。这是目前最主流、也最可靠的防盗链方案。很多大型CDN服务商(如阿里云CDN、腾讯云CDN)也都提供了基于URL签名的防盗链功能,原理和我们自己实现的是一致的。

所以,总结来说,Referer校验是“防君子不防小人”,而签名URL才是真正意义上的“防小人”。在实际项目中,我通常会把两者结合起来,或者直接上签名URL,因为一次投入,长久受益。

除了Referer校验,Java还有哪些更安全的资源访问控制策略?

当我们谈到资源访问控制,防盗链只是其中一个方面。在Java的世界里,尤其是在企业级应用中,我们有更多、更强大的工具和策略来确保资源的安全性。

1. 深入理解签名URL/令牌机制

前面简单提到了签名URL,但它其实可以玩出很多花样。除了基本的资源路径和过期时间,你还可以把更多信息编码到签名里,比如:

  • 用户ID: 确保这个URL只能被特定用户访问。
  • IP地址: 绑定访问者的IP,防止URL被分享给其他人(但要注意IP变化的问题)。
  • 单次使用令牌: 生成一个用一次就失效的令牌,防止重复下载或分享。这通常需要服务器端维护一个已使用令牌的列表或状态。

在Java中实现这个,关键在于javax.crypto.Mac类,它提供了HMAC(基于哈希的消息认证码)功能,结合一个安全的密钥,可以生成难以伪造的签名。

// 校验签名URL的过滤器或拦截器
public class SignedUrlValidationFilter implements Filter {
    // ... init方法获取密钥等

    @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();
        // 假设只有 /secured-resources/ 下的资源需要签名校验
        if (requestURI.startsWith("/secured-resources/")) {
            String expiresParam = httpRequest.getParameter("expires");
            String signatureParam = httpRequest.getParameter("signature");

            if (expiresParam == null || signatureParam == null) {
                httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                return;
            }

            try {
                long expires = Long.parseLong(expiresParam);
                if (System.currentTimeMillis() > expires) {
                    httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); // URL过期
                    return;
                }

                // 提取资源路径,例如 /secured-resources/video.mp4
                String resourcePath = requestURI; // 简化,实际可能需要去除上下文路径

                // 重新计算签名
                String dataToSign = resourcePath + ":" + expires;
                Mac hmacSha256 = Mac.getInstance("HmacSHA256");
                SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
                hmacSha256.init(secretKeySpec);
                byte[] expectedSignatureBytes = hmacSha256.doFinal(dataToSign.getBytes(StandardCharsets.UTF_8));
                String expectedSignature = Base64.getUrlEncoder().withoutPadding().encodeToString(expectedSignatureBytes);

                if (!expectedSignature.equals(signatureParam)) {
                    httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); // 签名不匹配
                    return;
                }

            } catch (Exception e) {
                httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); // 解析或签名错误
                return;
            }
        }
        chain.doFilter(request, response);
    }
    // ... destroy
}

2. 基于会话(Session)或Cookie的认证

如果你的资源是针对登录用户开放的,那么最直接的办法就是利用Java Web的会话机制。当用户登录成功后,服务器会创建一个Session,并把Session ID发送给客户端(通常通过Cookie)。后续的请求,只要带上有效的Session ID,服务器就知道是哪个用户在请求。

你可以写一个拦截器,检查请求是否包含有效的Session,以及Session中是否包含了该用户访问此资源的权限信息。这种方式对于保护用户私有资源非常有效。

3. OAuth2 / JWT (JSON Web Tokens)

对于更现代的、API驱动的微服务架构,或者需要第三方应用访问你的资源时,OAuth2和JWT是更强大的选择。

  • OAuth2: 是一种授权框架,允许用户授权第三方应用访问他们在某个服务商上的特定资源,而无需共享自己的凭据。它通过颁发访问令牌(Access Token)来实现。
  • JWT: 是一种紧凑且自包含的方式,用于在各方之间安全地传输信息。一个JWT通常包含三部分:头部、载荷(Payload)和签名

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

156

2025.08.06

Java Spring Security 与认证授权
Java Spring Security 与认证授权

本专题系统讲解 Java Spring Security 框架在认证与授权中的应用,涵盖用户身份验证、权限控制、JWT与OAuth2实现、跨站请求伪造(CSRF)防护、会话管理与安全漏洞防范。通过实际项目案例,帮助学习者掌握如何 使用 Spring Security 实现高安全性认证与授权机制,提升 Web 应用的安全性与用户数据保护。

88

2026.01.26

spring boot框架优点
spring boot框架优点

spring boot框架的优点有简化配置、快速开发、内嵌服务器、微服务支持、自动化测试和生态系统支持。本专题为大家提供spring boot相关的文章、下载、课程内容,供大家免费下载体验。

139

2023.09.05

spring框架有哪些
spring框架有哪些

spring框架有Spring Core、Spring MVC、Spring Data、Spring Security、Spring AOP和Spring Boot。详细介绍:1、Spring Core,通过将对象的创建和依赖关系的管理交给容器来实现,从而降低了组件之间的耦合度;2、Spring MVC,提供基于模型-视图-控制器的架构,用于开发灵活和可扩展的Web应用程序等。

408

2023.10.12

Java Spring Boot开发
Java Spring Boot开发

本专题围绕 Java 主流开发框架 Spring Boot 展开,系统讲解依赖注入、配置管理、数据访问、RESTful API、微服务架构与安全认证等核心知识,并通过电商平台、博客系统与企业管理系统等项目实战,帮助学员掌握使用 Spring Boot 快速开发高效、稳定的企业级应用。

73

2025.08.19

Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性
Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性

Spring Boot 是一个基于 Spring 框架的 Java 开发框架,它通过 约定优于配置的原则,大幅简化了 Spring 应用的初始搭建、配置和开发过程,让开发者可以快速构建独立的、生产级别的 Spring 应用,无需繁琐的样板配置,通常集成嵌入式服务器(如 Tomcat),提供“开箱即用”的体验,是构建微服务和 Web 应用的流行工具。

147

2025.12.22

Java Spring Boot 微服务实战
Java Spring Boot 微服务实战

本专题深入讲解 Java Spring Boot 在微服务架构中的应用,内容涵盖服务注册与发现、REST API开发、配置中心、负载均衡、熔断与限流、日志与监控。通过实际项目案例(如电商订单系统),帮助开发者掌握 从单体应用迁移到高可用微服务系统的完整流程与实战能力。

271

2025.12.24

Spring Boot企业级开发与MyBatis Plus实战
Spring Boot企业级开发与MyBatis Plus实战

本专题面向 Java 后端开发者,系统讲解如何基于 Spring Boot 与 MyBatis Plus 构建高效、规范的企业级应用。内容涵盖项目架构设计、数据访问层封装、通用 CRUD 实现、分页与条件查询、代码生成器以及常见性能优化方案。通过完整实战案例,帮助开发者提升后端开发效率,减少重复代码,快速交付稳定可维护的业务系统。

32

2026.02.11

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.3万人学习

C# 教程
C# 教程

共94课时 | 11.1万人学习

Java 教程
Java 教程

共578课时 | 80.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号