开发者

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

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

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

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

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

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

    2. 为什么要实施防盗链

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

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

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

    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
    

    防盗链核心逻辑:

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

    后端校验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>
      <imphpg id="productImg" alt="SpringBoot实现图片防盗链技术的原理分析与解决">
    
      <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&g编程t;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 Lisphpt<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图片防盗链的资料请关注编程客栈(www.devze.com)其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜