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图片防盗链的资料请关注代码网其它相关文章!
发表评论