⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/weixin_46157208/article/details/138051737 「北海南风」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

前言

出于安全考虑,我们需要后端返回的图片只允许在某个网站内展示,不想被爬虫拿到图片地址后被下载。或者,不想浏览器直接访问图片链接。

出于性能考虑,不想要别人的网站,拿着我们的图片链接去展示,白白消耗自己的服务器资源。

故而可在springboot中,使用简单的图片防盗链规则。拦截掉一些处理。

1、代码实现

本篇实战代码是简易版,代码写死配置

1-1、创建拦截器类

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ImageProtectionInterceptor implements HandlerInterceptor {

private static final String ALLOWED_DOMAIN = "baidudu.com"; // 允许的域名

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求的 URL
String requestUrl = request.getRequestURL().toString();

// 判断请求是否以图片后缀结尾
if (requestUrl.endsWith(".jpg") || requestUrl.endsWith(".png") || requestUrl.endsWith(".jpeg")) {
// 获取请求的来源域名
String referer = request.getHeader("Referer");

// 检查来源域名是否符合预期
if (referer != null && referer.contains(ALLOWED_DOMAIN)) {
return true; // 符合防盗链要求,放行请求
} else {
response.sendError(HttpServletResponse.SC_FORBIDDEN); // 返回 403 Forbidden
return false; // 拦截请求
}
}

return true; // 对非图片资源请求放行
}
}

1-2、注册拦截器

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器,拦截所有请求
registry.addInterceptor(new ImageProtectionInterceptor())
.addPathPatterns("/**"); // 拦截所有请求
}
}

2、代码实现(灵活配置)

2-1、在 application.yml 中配置信息

# 图片防盗链配置
img-protect:
# 图片防盗链保护开关
enabled: true
# 是否允许浏览器直接访问
allowBrowser: false
# 图片防盗链白名单,多个用逗号分隔【不填则所有网站都拦截】
allowReferer: baidudu.com

2-2、创建配置文件映射类

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties("img-protect")
public class ImgProtectConfig {

private boolean enabled;
private boolean allowBrowser;

private String allowReferer;

public boolean getEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public boolean getAllowBrowser() {
return allowBrowser;
}

public void setAllowBrowser(boolean allowBrowser) {
this.allowBrowser = allowBrowser;
}

public String getAllowReferer() {
return allowReferer;
}

public void setAllowReferer(String allowReferer) {
this.allowReferer = allowReferer;
}
}

2-3、创建拦截器类

import 上方2-2创建的类路径.ImgProtectConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

@Component
public class ImageProtectionInterceptor implements HandlerInterceptor {

@Autowired
private ImgProtectConfig imgProtectConfig;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

// 判断是否开启图片防盗链功能
if (!imgProtectConfig.getEnabled()){
return true;
}

// 获取请求的 URL
String requestUrl = request.getRequestURL().toString();

// 判断请求是否以图片后缀结尾
if (requestUrl.endsWith(".jpg") || requestUrl.endsWith(".png") || requestUrl.endsWith(".jpeg")) {
// 获取请求的来源域名
String referer = request.getHeader("Referer");

// 检查来源域名是否符合预期,referer 为 null 则说明是浏览器直接访问。
if (referer == null && imgProtectConfig.getAllowBrowser()){
return true; // 符合防盗链要求,放行请求
}else if (referer != null && isAllowedDomain(referer)) {
return true; // 符合防盗链要求,放行请求
} else {
response.sendError(HttpServletResponse.SC_FORBIDDEN); // 返回 403 Forbidden
return false; // 拦截请求
}
}

return true; // 对非图片资源请求放行
}


// 检查是否来自允许的域名
private boolean isAllowedDomain(String referer) {
// 获取允许的域名
String allowedReferers = imgProtectConfig.getAllowReferer();
// 如果允许的域名不为空
if (allowedReferers.trim() != null && !"".equals(allowedReferers.trim())) {
// 将允许的域名分割成字符串数组
Set<String> allowedDomains = new HashSet<>(Arrays.asList(allowedReferers.split(",")));
// 遍历允许的域名
for (String allowedDomain : allowedDomains) {
// 如果请求的域名包含允许的域名,则返回true
if (referer.contains(allowedDomain.trim())) {
return true;
}
}
}
// 否则返回false
return false;
}

}

2-4、注册拦截器

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {


// 不能再使用 new 方式创建对象 !!!
@Autowired
private ImageProtectionInterceptor imageProtectionInterceptor;


@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器,拦截所有请求
registry.addInterceptor(imageProtectionInterceptor)
.addPathPatterns("/**"); // 拦截所有请求
}
}

结束语

以上防盗链拦截器基本实现可以对付一般情况下的图片盗链,但并不能保证绝对安全。

可能出现以下等情况:

  • Referer 伪造: 恶意客户端可以伪造 referer 头。攻击者可以伪造有效的 referer 来绕过保护。
  • 漏报: 攻击者可能找到绕过 referer 检查的方法(例如使用 data URI 或 base64 编码的图片)。
  • 误报: 合法用户可能因为 referer 不匹配而被阻止(例如隐私浏览器或代理服务器)。
  • 反向代理: 攻击者可以在url路径中,添加域名白名单作为反向代理路径,绕开代码的contains方法检查。
文章目录
  1. 1. 前言
  2. 2. 1、代码实现
    1. 2.0.0.1. 1-1、创建拦截器类
    2. 2.0.0.2. 1-2、注册拦截器
  • 3. 2、代码实现(灵活配置)
    1. 3.0.0.1. 2-1、在 application.yml 中配置信息
    2. 3.0.0.2. 2-2、创建配置文件映射类
    3. 3.0.0.3. 2-3、创建拦截器类
    4. 3.0.0.4. 2-4、注册拦截器
  • 4. 结束语