当前位置: 代码网 > it编程>编程语言>Java > SpringBoot实现图片防盗链技术的原理分析与解决

SpringBoot实现图片防盗链技术的原理分析与解决

2025年07月03日 Java 我要评论
1. 前言:图片盗链的危害与影响在现代 web 应用中,网站往往需要展示大量图片资源(商品图、文章配图、用户头像等)。若不做防护,其他站点或爬虫可以直接引用这些图片 url,占用带宽、盗用版权、造成服

1. 前言:图片盗链的危害与影响

在现代 web 应用中,网站往往需要展示大量图片资源(商品图、文章配图、用户头像等)。若不做防护,其他站点或爬虫可以直接引用这些图片 url,占用带宽、盗用版权、造成服务器压力过大会导致:

  • 流量损失:盗链消耗您的服务器带宽
  • 成本增加:cdn和服务器费用飙升
  • 版权侵犯:原创内容被非法使用
  • seo影响:搜索引擎排名下降

为此,我们需要为图片资源加一道“防盗链”保护,确保只有合法来源或携带正确凭证的请求才能成功获取图片。本文将带着小伙伴们深入解析防盗链技术原理,并提供前后端完整解决方案。

2. 为什么要实施防盗链

我们先来看一个例子:假设你的云服务器按带宽、流量计费,并且开通了cdn加速服务,那么图片被盗链你可能面临下图问题

所以实施防盗链,可以解决以下几个问题:

1.节省带宽与流量成本

非法盗链会导致大量免费流量被外站消耗,增加服务器的网络和流量费用。

2.保护版权与资源安全

防止未授权站点随意引用和传播图片资源,保障内容提供方的利益。

3.防止爬虫恶意抓取

结合签名或 referer 校验,可以有效拦截简单爬虫,避免批量抓取。

4.提升访问性能

当检测到非授权请求时,直接返回 403 或空白图,减轻后端压力。

3. 防盗链核心技术原理

主要有两种常见思路:

referer 校验:后端检查 http 请求头中的 referer,只有来自本站页面的请求才允许访问。

签名(token)校验:前端在图片 url 上附加时间戳与签名(hmac/md5),后端校验签名并判断是否过期。

referer 校验简单易用,但可以被伪造;签名方案更安全,可以自定义过期时间、权限范围。

3.1 referer 校验

当浏览器请求资源时,会在header中包含来源页面地址:

get /image.jpg http/1.1
host: your-domain.com
referer: https://attacker-site.com/stolen-page.html

防盗链核心逻辑:

后端校验referer:

@configuration
public class securityconfig implements webmvcconfigurer {
    //配置在yml文件中的合法域名
    @value("${allowed.domains}")
    private list<string> alloweddomains;
    
    @override
    public void addinterceptors(interceptorregistry registry) {
        registry.addinterceptor(new refererinterceptor(alloweddomains))
                .addpathpatterns("/images/**");
    }
}

public class refererinterceptor implements handlerinterceptor {
    
    private final set<string> alloweddomains;
    
    public refererinterceptor(list<string> domains) {
        this.alloweddomains = new hashset<>(domains);
    }
    
    @override
    public boolean prehandle(httpservletrequest request, 
                             httpservletresponse response, 
                             object handler) throws exception {
        
        string referer = request.getheader("referer");
        if (referer == null) {
            // 允许无referer的直接访问(如浏览器地址栏)
            return true;
        }
        
        try {
            string domain = new url(referer).gethost();
            if (alloweddomains.contains(domain)) {
                return true;
            }
        } catch (malformedurlexception e) {
            // url格式错误视为非法
        }
        
        // 返回防盗链提示图
        response.setcontenttype("image/png");
        files.copy(paths.get("static/anti-leech.png"), 
                  response.getoutputstream());
        return false;
    }
}

3.2 签名(token)校验

3.2.1 前端实现思路

  • 计算签名:用约定好的 secretkey 对图片路径(或文件名)+ 时间戳做 hmac/md5 计算。
  • 拼接 url:/images/{filename}?ts={timestamp}&sign={signature}
  • 将带签名的 url 输出到页面或组件内
<!doctype html>
<html lang="zh-cn">
<head>
  <meta charset="utf-8">
  <title>防盗链示例</title>
</head>
<body>
  <h3>商品展示图:</h3>
  <img id="productimg" alt="product">

  <script>
    // 约定的密钥(不能泄露到公网,示例仅展示逻辑)
    const secret_key = 'mysupersecretkey';


     // 简单 md5 签名(生产环境请使用 hmac)
     // 这里只借助外部库 md5.min.js
    function generatesignedurl(filename) {
      const ts = date.now();
      // 签名内容:filename + ts + secret_key
      const raw = `${filename}${ts}${secret_key}`;
      const sign = md5(raw);
      return `/images/${filename}?ts=${ts}&sign=${sign}`;
    }

    // 使用示例
    document.getelementbyid('productimg').src = generatesignedurl('sample.jpg');
  </script>
  <!-- 引入 md5 库 -->
  <script src="https://cdn.jsdelivr.net/npm/blueimp-md5/js/md5.min.js"></script>
</body>
</html>

3.2.1 后端(spring boot)示例

添加依赖

spring boot 项目中追加 commons-codec 依赖

<!-- pom.xml -->
<dependency>
  <groupid>commons-codec</groupid>
  <artifactid>commons-codec</artifactid>
  <version>1.15</version>
</dependency>

编写签名校验拦截器

import org.apache.commons.codec.digest.digestutils;
import org.springframework.stereotype.component;
import org.springframework.web.servlet.handlerinterceptor;
import jakarta.servlet.http.httpservletrequest;
import jakarta.servlet.http.httpservletresponse;
import java.io.file;
import java.io.ioexception;

@component
public class imageauthinterceptor implements handlerinterceptor {

    private static final string secret_key = "mysupersecretkey";
    // 签名有效期:5 分钟
    private static final long expire_millis = 5 * 60 * 1000;

    @override
    public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws ioexception {
        string tsparam = request.getparameter("ts");
        string signparam = request.getparameter("sign");
        string uri = request.getrequesturi(); // e.g. /images/sample.jpg

        if (tsparam == null || signparam == null) {
            response.senderror(httpservletresponse.sc_forbidden);
            return false;
        }

        long ts = long.parselong(tsparam);
        long now = system.currenttimemillis();
        if (now - ts > expire_millis) {  // 超时
            response.senderror(httpservletresponse.sc_forbidden, "链接过期");
            return false;
        }

        // 计算服务器端签名
        string path = uri.substring("/images/".length()); // sample.jpg
        string raw = path + tsparam + secret_key;
        string serversign = digestutils.md5hex(raw);

        if (!serversign.equalsignorecase(signparam)) {
            response.senderror(httpservletresponse.sc_forbidden);
            return false;
        }

        // 签名校验通过,继续处理请求(交给静态文件 handler)
        return true;
    }
}

注册拦截器

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.context.annotation.configuration;
import org.springframework.web.servlet.config.annotation.*;

@configuration
public class webconfig implements webmvcconfigurer {

    @autowired
    private imageauthinterceptor imageauthinterceptor;

    @override
    public void addinterceptors(interceptorregistry registry) {
        registry.addinterceptor(imageauthinterceptor)
                .addpathpatterns("/images/**");
    }

    @override
    public void addresourcehandlers(resourcehandlerregistry registry) {
        // 将 /images/** 映射到本地文件系统目录
        registry.addresourcehandler("/images/**")
                .addresourcelocations("file:/opt/app/images/");
    }
}

存放图片

将需要防盗链的图片放到服务器 /opt/app/images/ 目录下,例如 sample.jpg

4. 高级防护策略

通过上述的代码演示,无论你是使用 referer 校验,还是基于签名校验,相信小伙伴已经可以轻松应用于自己的项目中,这里博主再简单罗列两点高级防护策略,供小伙伴们参考

4.1 动态水印技术

将图片资源默认都加上动态水印

public void addwatermark(inputstream imagestream, 
                         outputstream output, 
                         string text) throws ioexception {
    
    bufferedimage image = imageio.read(imagestream);
    graphics2d g = image.creategraphics();
    
    // 设置水印透明度
    g.setcomposite(alphacomposite.getinstance(alphacomposite.src_over, 0.3f));
    g.setcolor(color.black);
    g.setfont(new font("arial", font.bold, 30));
    
    // 计算水印位置
    fontmetrics metrics = g.getfontmetrics();
    int x = (image.getwidth() - metrics.stringwidth(text)) / 2;
    int y = image.getheight() - 50;
    
    // 添加文字水印
    g.drawstring(text, x, y);
    g.dispose();
    
    imageio.write(image, "jpg", output);
}

4.2 智能行为分析

我们还可以设置一些行为限制,比如 几秒内可以访问多少次

@component
public class imagerequestanalyzer {
    
    private final map<string, requestcounter> ipcounters = new concurrenthashmap<>();
    
    @scheduled(fixedrate = 60000) // 每分钟清理
    public void cleancounters() {
        ipcounters.entryset().removeif(entry -> 
            entry.getvalue().isexpired());
    }
    
    public boolean issuspiciousrequest(httpservletrequest request) {
        string ip = request.getremoteaddr();
        string path = request.getrequesturi();
        
        requestcounter counter = ipcounters.computeifabsent(
            ip + path, k -> new requestcounter());
        
        counter.increment();
        
        // 规则1: 10秒内超过20次请求
        if (counter.getcount(10) > 20) return true;
        
        // 规则2: 1分钟内超过100次请求
        if (counter.getcount(60) > 100) return true;
        
        // 规则3: 异常user-agent
        string ua = request.getheader("user-agent");
        if (ua == null || ua.contains("python") || ua.contains("curl")) {
            return true;
        }
        
        return false;
    }
    
    static class requestcounter {
        private final list<long> timestamps = new arraylist<>();
        
        public synchronized void increment() {
            timestamps.add(system.currenttimemillis());
        }
        
        public synchronized int getcount(int seconds) {
            long cutoff = system.currenttimemillis() - seconds * 1000l;
            timestamps.removeif(t -> t < cutoff);
            return timestamps.size();
        }
        
        public boolean isexpired() {
            return timestamps.isempty() || 
                system.currenttimemillis() - timestamps.get(0) > 3600000;
        }
    }
}

5. 结语

本文讲解了 referer校验 + 签名校验两种防盗链方案,其中签名校验 我们实现在前端生成带有防盗链签名的图片 url,后端(spring boot)在拦截器中校验签名并检查有效期,只有合法请求才能获取图片资源。该方案优点在于:

  • 安全性高:签名不可伪造,且可设置过期
  • 灵活可扩展:可加入用户鉴权、权限控制等
  • 轻量无侵入:仅依赖拦截器和静态资源映射

以上就是springboot实现图片防盗链技术的原理分析与解决的详细内容,更多关于springboot图片防盗链的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com