欢迎光临
我们一直在努力

前端实现图片防盗链技术详解 - 原理分析与SpringBoot解决方案

在这里插入图片描述

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Micro麦可乐的博客 🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战 🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战 🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解 🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用 🍎《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例 ✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧 💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程 🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整 👍《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术 如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

前端实现图片防盗链技术详解 – 原理分析与SpringBoot解决方案

  • 1. 前言:图片盗链的危害与影响
  • 2. 为什么要实施防盗链?
  • 3. 防盗链核心技术原理
      • 3.1 Referer 校验
      • 3.2 签名(Token)校验
        • 3.2.1 前端实现思路
        • 3.2.1 后端(Spring Boot)示例
  • 4. 高级防护策略
      • 4.1 动态水印技术
      • 4.2 智能行为分析
  • 5. 结语

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

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

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

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


2. 为什么要实施防盗链?

我们先来看一个例子:假设你的云服务器按带宽、流量计费,并且开通了CDN加速服务,那么图片被盗链你可能面临下图问题 在这里插入图片描述 所以实施防盗链,可以解决以下几个问题:

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

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

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

提升访问性能 当检测到非授权请求时,直接返回 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)在拦截器中校验签名并检查有效期,只有合法请求才能获取图片资源。该方案优点在于:

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

希望本文能帮助你快速上手图片防盗链实现,保护自己的图片资源不被非法盗链。如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家 一键三连 给博主一点点鼓励!


前端技术专栏回顾:

01【前端技术】 ES6 介绍及常用语法说明 02【前端技术】标签页通讯localStorage、BroadcastChannel、SharedWorker的技术详解 03 前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案 04 前端开发中深拷贝的循环引用问题:从问题复现到完美解决 05 前端AJAX请求上传下载进度监控指南详解与完整代码示例 06 TypeScript 进阶指南 – 使用泛型与keyof约束参数 07 前端实现视频文件动画帧图片提取全攻略 – 附完整代码样例 08 前端函数防抖(Debounce)完整讲解 – 从原理、应用到完整实现 09 JavaScript异步编程 Async/Await 使用详解:从原理到最佳实践 10 前端图片裁剪上传全流程详解:从预览到上传的完整流程 11 前端大文件分片上传详解 – Spring Boot 后端接口实现 在这里插入图片描述

赞(0)
未经允许不得转载:171主机测评 » 前端实现图片防盗链技术详解 - 原理分析与SpringBoot解决方案
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址