最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot實(shí)現(xiàn)圖片防盜鏈的五種方式詳解

 更新時(shí)間:2025年08月19日 08:45:04   作者:墨瑾軒  
出于安全考慮,我們需要后端返回的圖片只允許在某個(gè)網(wǎng)站內(nèi)展示,不想被爬蟲(chóng)拿到圖片地址后被下載,或者,不想瀏覽器直接訪問(wèn)圖片鏈接,出于性能考慮,不想要?jiǎng)e人的網(wǎng)站,拿著我們的圖片鏈接去展示,所以本文給大家介紹了SpringBoot實(shí)現(xiàn)圖片防盜鏈的五種方式

什么是圖片防盜鏈?

想象一下,你的網(wǎng)站有一張超可愛(ài)的貓咪圖片(/images/cute_cat.jpg),但某天發(fā)現(xiàn)別的網(wǎng)站直接用 <img src="http://yourdomain.com/images/cute_cat.jpg"> 把你的圖偷走了!這就是盜鏈——別人不勞而獲,占用你服務(wù)器的流量和資源。

防盜鏈的核心思想

  • 檢查請(qǐng)求來(lái)源(Referer):只允許指定域名的請(qǐng)求訪問(wèn)資源。
  • 白名單機(jī)制:某些資源可以完全開(kāi)放訪問(wèn)。
  • 默認(rèn)圖片兜底:拒絕請(qǐng)求時(shí)返回一張“禁止盜鏈”的提示圖。

場(chǎng)景重現(xiàn)

你運(yùn)行的Spring Boot服務(wù)突然流量暴增,但發(fā)現(xiàn)90%請(qǐng)求來(lái)自第三方網(wǎng)站。這時(shí)候你會(huì)不會(huì)想:“難道我的貓咪圖成了別人的廣告位?”

實(shí)現(xiàn)方式對(duì)比:5種方法深度解析

方法優(yōu)點(diǎn)缺點(diǎn)
過(guò)濾器(Filter)全局?jǐn)r截,適合靜態(tài)資源無(wú)法處理復(fù)雜邏輯
攔截器(Interceptor)可訪問(wèn)Spring上下文僅限MVC請(qǐng)求
Nginx配置性能高,無(wú)需代碼不靈活
簽名URL安全性強(qiáng)增加復(fù)雜度
混合策略多層防護(hù)配置復(fù)雜

方法1:過(guò)濾器(Filter)實(shí)現(xiàn)防盜鏈

1. 創(chuàng)建Spring Boot項(xiàng)目

添加必要依賴(lài)(pom.xml):

<dependencies>
    <!-- Web支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 緩存支持(可選) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

    <!-- Lombok(簡(jiǎn)化代碼) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2. 配置防盜鏈參數(shù)(application.yml)

# 防盜鏈配置
anti-hotlink:
  # 是否啟用防盜鏈
  enabled: true
  # 允許的域名列表(支持子域名和正則)
  allowed-domains:
    - localhost
    - 127.0.0.1
    - "*.example.com"
    - "^test\\d+\\.domain\\.com$"  # 匹配test1.domain.com等
  # 需要保護(hù)的資源格式(結(jié)尾匹配)
  protected-formats:
    - .jpg
    - .jpeg
    - .png
    - .gif
  # 是否允許直接訪問(wèn)(無(wú)Referer)
  allow-direct-access: true
  # 拒絕訪問(wèn)時(shí)的動(dòng)作(REDIRECT/FORBIDDEN/DEFAULT_IMAGE)
  deny-action: DEFAULT_IMAGE
  # 默認(rèn)圖片路徑
  default-image: /images/no-hotlinking.png
  # 白名單路徑(無(wú)需檢查)
  whitelist-paths:
    - /api/public/**
    - /images/public/**

3. 編寫(xiě)過(guò)濾器

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 圖片防盜鏈過(guò)濾器
 */
@Component
@Slf4j
public class AntiHotlinkFilter implements Filter {

    // 從配置中讀取參數(shù)
    @Value("${anti-hotlink.enabled}")
    private boolean enabled;

    @Value("${anti-hotlink.allowed-domains}")
    private List<String> allowedDomains;

    @Value("${anti-hotlink.protected-formats}")
    private List<String> protectedFormats;

    @Value("${anti-hotlink.allow-direct-access}")
    private boolean allowDirectAccess;

    @Value("${anti-hotlink.deny-action}")
    private String denyAction;

    @Value("${anti-hotlink.default-image}")
    private String defaultImage;

    @Value("${anti-hotlink.whitelist-paths}")
    private List<String> whitelistPaths;

    // 路徑匹配工具
    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (!enabled) {
            chain.doFilter(request, response);
            return;
        }

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String requestURI = httpRequest.getRequestURI();
        String referer = httpRequest.getHeader("Referer");

        // 檢查是否在白名單中
        if (isWhitelisted(requestURI)) {
            chain.doFilter(request, response);
            return;
        }

        // 檢查是否是受保護(hù)的資源格式
        if (!isProtectedResource(requestURI)) {
            chain.doFilter(request, response);
            return;
        }

        // 直接訪問(wèn)(無(wú)Referer)且允許
        if (allowDirectAccess && referer == null) {
            chain.doFilter(request, response);
            return;
        }

        // 檢查Referer是否合法
        if (isValidReferer(referer)) {
            chain.doFilter(request, response);
        } else {
            handleInvalidRequest(httpResponse);
        }
    }

    /**
     * 檢查路徑是否在白名單中
     */
    private boolean isWhitelisted(String requestURI) {
        for (String path : whitelistPaths) {
            if (pathMatcher.match(path, requestURI)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 檢查是否是受保護(hù)的資源格式
     */
    private boolean isProtectedResource(String requestURI) {
        for (String format : protectedFormats) {
            if (requestURI.endsWith(format)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 檢查Referer是否合法
     */
    private boolean isValidReferer(String referer) {
        if (referer == null) {
            return false;
        }

        for (String domain : allowedDomains) {
            if (domain.startsWith("^")) {
                // 正則匹配
                Pattern pattern = Pattern.compile(domain.substring(1));
                if (pattern.matcher(referer).matches()) {
                    return true;
                }
            } else if (domain.equals("*")) {
                // 通配符匹配
                return true;
            } else if (referer.contains(domain)) {
                // 精確匹配
                return true;
            }
        }
        return false;
    }

    /**
     * 處理非法請(qǐng)求
     */
    private void handleInvalidRequest(HttpServletResponse response) throws IOException {
        switch (denyAction) {
            case "REDIRECT":
                response.sendRedirect("https://example.com/forbidden");
                break;
            case "FORBIDDEN":
                response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
                break;
            case "DEFAULT_IMAGE":
                response.sendRedirect(defaultImage);
                break;
            default:
                response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
        }
    }
}

代碼解析

  • doFilter:核心邏輯入口,檢查請(qǐng)求是否合法。
  • isWhitelisted:判斷路徑是否在白名單中。
  • isValidReferer:支持正則、通配符和精確匹配。
  • handleInvalidRequest:根據(jù)配置返回不同響應(yīng)。

方法2:攔截器(Interceptor)實(shí)現(xiàn)

1. 創(chuàng)建攔截器類(lèi)

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 圖片防盜鏈攔截器
 */
@Component
public class AntiHotlinkInterceptor implements HandlerInterceptor {

    // 配置參數(shù)(需從配置文件注入)
    private final List<String> allowedDomains;
    private final List<String> protectedFormats;
    private final boolean allowDirectAccess;
    private final String denyAction;
    private final String defaultImage;

    public AntiHotlinkInterceptor(
            List<String> allowedDomains,
            List<String> protectedFormats,
            boolean allowDirectAccess,
            String denyAction,
            String defaultImage) {
        this.allowedDomains = allowedDomains;
        this.protectedFormats = protectedFormats;
        this.allowDirectAccess = allowDirectAccess;
        this.denyAction = denyAction;
        this.defaultImage = defaultImage;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        String referer = request.getHeader("Referer");

        // 檢查是否是受保護(hù)的資源格式
        if (!isProtectedResource(requestURI)) {
            return true;
        }

        // 直接訪問(wèn)(無(wú)Referer)且允許
        if (allowDirectAccess && referer == null) {
            return true;
        }

        // 檢查Referer是否合法
        if (isValidReferer(referer)) {
            return true;
        } else {
            handleInvalidRequest(response);
            return false;
        }
    }

    private boolean isProtectedResource(String requestURI) {
        for (String format : protectedFormats) {
            if (requestURI.endsWith(format)) {
                return true;
            }
        }
        return false;
    }

    private boolean isValidReferer(String referer) {
        if (referer == null) {
            return false;
        }

        for (String domain : allowedDomains) {
            if (domain.startsWith("^")) {
                // 正則匹配
                Pattern pattern = Pattern.compile(domain.substring(1));
                if (pattern.matcher(referer).matches()) {
                    return true;
                }
            } else if (domain.equals("*")) {
                // 通配符匹配
                return true;
            } else if (referer.contains(domain)) {
                // 精確匹配
                return true;
            }
        }
        return false;
    }

    private void handleInvalidRequest(HttpServletResponse response) throws IOException {
        switch (denyAction) {
            case "REDIRECT":
                response.sendRedirect("https://example.com/forbidden");
                break;
            case "FORBIDDEN":
                response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
                break;
            case "DEFAULT_IMAGE":
                response.sendRedirect(defaultImage);
                break;
            default:
                response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
        }
    }
}

方法3:Nginx配置防盜鏈

如果你使用Nginx作為反向代理,可以更高效地實(shí)現(xiàn)防盜鏈:

location ~* \.(jpg|jpeg|png|gif)$ {
    # 限制Referer
    valid_referers none blocked example.com *.example.com ~\.example\.com$;
    if ($invalid_referer) {
        rewrite ^/images/(.*)$ /images/no-hotlinking.png last;
    }
}

性能對(duì)比

  • Nginx:毫秒級(jí)響應(yīng),無(wú)需Java處理
  • Java過(guò)濾器:延遲約10ms

方法4:簽名URL(Token驗(yàn)證)

1. 生成帶Token的URL

import java.security.MessageDigest;
import java.time.Instant;
import java.util.Base64;

public class TokenGenerator {

    private static final String SECRET_KEY = "your-secret-key";
    private static final int TTL_SECONDS = 300; // 5分鐘過(guò)期

    public static String generateSignedUrl(String filePath) {
        long timestamp = Instant.now().getEpochSecond();
        String token = generateToken(filePath, timestamp);
        return "https://yourdomain.com" + filePath + "?token=" + token + "&ts=" + timestamp;
    }

    private static String generateToken(String filePath, long timestamp) {
        try {
            String input = filePath + SECRET_KEY + timestamp;
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(input.getBytes());
            return Base64.getEncoder().encodeToString(digest);
        } catch (Exception e) {
            throw new RuntimeException("Token generation failed", e);
        }
    }

    public static boolean validateToken(String filePath, String token, long timestamp) {
        return generateToken(filePath, timestamp).equals(token);
    }
}

方法5:混合策略(Filter + Token)

1. 修改過(guò)濾器邏輯

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    String requestURI = httpRequest.getRequestURI();
    String referer = httpRequest.getHeader("Referer");

    // 檢查是否是簽名請(qǐng)求
    if (isSignedRequest(httpRequest)) {
        chain.doFilter(request, response);
        return;
    }

    // 其余邏輯同方法1
}

注意事項(xiàng)與優(yōu)化建議

1. Referer不可靠

  • 偽造問(wèn)題:惡意客戶(hù)端可偽造Referer頭。
  • 解決方案:結(jié)合Token驗(yàn)證或Nginx配置。

2. 緩存優(yōu)化

@Cacheable("hotlink_domains")
private boolean isAllowedDomain(String domain) {
    // 緩存域名檢查結(jié)果
    return allowedDomains.contains(domain);
}

3. 白名單陷阱

  • 路徑匹配漏洞/api/public/** 可能被繞過(guò)。
  • 解決方案:使用嚴(yán)格路徑匹配規(guī)則。

以上就是SpringBoot實(shí)現(xiàn)圖片防盜鏈的五種方式詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot圖片防盜鏈的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java 學(xué)習(xí)筆記(入門(mén)篇)_java程序helloWorld

    java 學(xué)習(xí)筆記(入門(mén)篇)_java程序helloWorld

    安裝配置完Java的jdk,下面就開(kāi)始寫(xiě)第一個(gè)java程序--hello World.用來(lái)在控制臺(tái)輸出“Hello World”,接下來(lái)詳細(xì)介紹,感興趣的朋友可以參考下
    2013-01-01
  • Spring中依賴(lài)注入(DI)幾種方式解讀

    Spring中依賴(lài)注入(DI)幾種方式解讀

    這篇文章主要介紹了Spring中依賴(lài)注入(DI)幾種方式解讀,構(gòu)造器依賴(lài)注入通過(guò)容器觸發(fā)一個(gè)類(lèi)的構(gòu)造器來(lái)實(shí)現(xiàn)的,該類(lèi)有一系列參數(shù),每個(gè)參數(shù)代表一個(gè)對(duì)其他類(lèi)的依賴(lài),需要的朋友可以參考下
    2024-01-01
  • Java實(shí)現(xiàn)圖片倒影的源碼實(shí)例內(nèi)容

    Java實(shí)現(xiàn)圖片倒影的源碼實(shí)例內(nèi)容

    在本篇文章里小編給大家整理的是關(guān)于Java實(shí)現(xiàn)圖片倒影的源碼以及相關(guān)知識(shí)點(diǎn),有需要的朋友們學(xué)習(xí)下。
    2019-09-09
  • springboot運(yùn)行到dokcer中 dockerfile的場(chǎng)景分析

    springboot運(yùn)行到dokcer中 dockerfile的場(chǎng)景分析

    這篇文章主要介紹了springboot運(yùn)行到dokcer中 dockerfile,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-03-03
  • Java事件處理機(jī)制(自定義事件)實(shí)例詳解

    Java事件處理機(jī)制(自定義事件)實(shí)例詳解

    這篇文章主要介紹了Java事件處理機(jī)制(自定義事件)實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下
    2016-12-12
  • SpringBoot使用Guava實(shí)現(xiàn)日志脫敏的示例代碼

    SpringBoot使用Guava實(shí)現(xiàn)日志脫敏的示例代碼

    本文主要介紹了SpringBoot使用Guava實(shí)現(xiàn)日志脫敏的示例代碼,使用Guava中的Strings、Maps和CharMatcher類(lèi)來(lái)進(jìn)行日志脫敏,保護(hù)敏感數(shù)據(jù)的安全,感興趣的可以了解一下
    2024-01-01
  • 使用Java高效將CSV轉(zhuǎn)換為Excel的實(shí)現(xiàn)方法

    使用Java高效將CSV轉(zhuǎn)換為Excel的實(shí)現(xiàn)方法

    在日常的數(shù)據(jù)處理和分析工作中,CSV文件因其輕量級(jí)和通用性而廣受歡迎,然而,當(dāng)我們需要進(jìn)行復(fù)雜的數(shù)據(jù)分析或利用Excel強(qiáng)大的圖表和公式功能時(shí),將CSV數(shù)據(jù)轉(zhuǎn)換為Excel文件就成了開(kāi)發(fā)者們常見(jiàn)的需求和痛點(diǎn),本文將深入探討如何利用Java高效地將CSV轉(zhuǎn)換為Excel
    2025-08-08
  • SpringMVC通過(guò)Ajax處理Json數(shù)據(jù)的步驟詳解

    SpringMVC通過(guò)Ajax處理Json數(shù)據(jù)的步驟詳解

    這篇文章主要介紹了SpringMVC通過(guò)Ajax處理Json數(shù)據(jù)的步驟詳解,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • Java中的AQS框架原理詳解

    Java中的AQS框架原理詳解

    這篇文章主要介紹了Java中的AQS框架原理詳解,AQS核心思想是,如果被請(qǐng)求的共享資源(state)空閑,則將當(dāng)前請(qǐng)求資源的線程設(shè)置為有效的工作線程,并且將共享資源設(shè)置為鎖定狀態(tài),需要的朋友可以參考下
    2023-12-12
  • SpringBoot使用命令行參數(shù)方式

    SpringBoot使用命令行參數(shù)方式

    Spring?Boot允許通過(guò)命令行參數(shù)靈活配置和部署項(xiàng)目,在main方法中傳入?yún)?shù)并通過(guò)SpringApplication.run()加載配置,如果需要關(guān)閉命令行配置,可以設(shè)置addCommandLineProperties為false
    2025-12-12

最新評(píng)論

泰州市| 曲麻莱县| 井陉县| 班戈县| 武川县| 双鸭山市| 台前县| 司法| 张家港市| 东乌珠穆沁旗| 开远市| 前郭尔| 普宁市| 龙川县| 抚顺县| 若羌县| 屯昌县| 徐州市| 周至县| 洛阳市| 额敏县| 澄迈县| 兴文县| 策勒县| 东港市| 彰武县| 庆云县| 房产| 邵武市| 沐川县| 中西区| 高陵县| 南漳县| 宜宾县| 乐安县| 平舆县| 毕节市| 礼泉县| 讷河市| 怀远县| 星座|