精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

SpringBoot 接口防刷的五種方案,太強(qiáng)了!

開發(fā) 前端
接口防刷是一個(gè)系統(tǒng)性工程,需要綜合考慮安全性、用戶體驗(yàn)、性能開銷和運(yùn)維復(fù)雜度等多方面因素。本文介紹的 5 種方案各有優(yōu)缺點(diǎn),你可以根據(jù)實(shí)際需求靈活選擇和組合。

在當(dāng)今的互聯(lián)網(wǎng)環(huán)境中,接口防刷已成為保障系統(tǒng)安全與穩(wěn)定運(yùn)行的關(guān)鍵環(huán)節(jié)。惡意的高頻請(qǐng)求如同隱藏在暗處的“殺手”,不僅會(huì)大量消耗服務(wù)器寶貴的資源,還可能引發(fā)數(shù)據(jù)異常,嚴(yán)重時(shí)甚至?xí)?dǎo)致整個(gè)系統(tǒng)癱瘓,給業(yè)務(wù)帶來不可估量的損失。

本文將深入剖析在 Spring Boot 框架下實(shí)現(xiàn)接口防刷的 5 種技術(shù)方案,幫助你根據(jù)實(shí)際需求選擇最合適的方法,為系統(tǒng)安全保駕護(hù)航。

1. 基于注解的訪問頻率限制

基于注解的訪問頻率限制是一種廣受歡迎的接口防刷方案,它通過自定義注解和 AOP 切面的巧妙結(jié)合,實(shí)現(xiàn)了對(duì)接口訪問頻率的有效控制。這種方法簡(jiǎn)單易用,實(shí)現(xiàn)成本低,就像給接口穿上了一層輕便的“防護(hù)衣”。

實(shí)現(xiàn)步驟

1.1 創(chuàng)建限流注解

首先,我們需要?jiǎng)?chuàng)建一個(gè)自定義的限流注解,用于標(biāo)記需要進(jìn)行訪問頻率限制的接口方法。以下是具體的代碼實(shí)現(xiàn):

圖片圖片

在這個(gè)注解中,我們可以通過 time 屬性設(shè)置限制的時(shí)間段,通過 count 屬性設(shè)置在該時(shí)間段內(nèi)允許的最大請(qǐng)求次數(shù),通過 key 屬性指定限流的鍵,支持使用 SpEL 表達(dá)式進(jìn)行靈活配置,message 屬性則用于在請(qǐng)求被限定時(shí)給用戶提供提示信息。

1.2 實(shí)現(xiàn)限流切面

接下來,我們需要實(shí)現(xiàn)一個(gè) AOP 切面,用于處理限流邏輯。以下是具體的代碼實(shí)現(xiàn):

@Aspect
@Component
@Slf4j
publicclass RateLimitAspect {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
        // 獲取請(qǐng)求的方法名
        String methodName = pjp.getSignature().getName();
        // 獲取請(qǐng)求的類名
        String className = pjp.getTarget().getClass().getName();
        
        // 組合限流 key
        String limitKey = getLimitKey(pjp, rateLimit, methodName, className);
        
        // 獲取限流參數(shù)
        int time = rateLimit.time();
        int count = rateLimit.count();
        
        // 執(zhí)行限流邏輯
        boolean limited = isLimited(limitKey, time, count);
        if (limited) {
            thrownew RuntimeException(rateLimit.message());
        }
        
        // 執(zhí)行目標(biāo)方法
        return pjp.proceed();
    }
    
    private String getLimitKey(ProceedingJoinPoint pjp, RateLimit rateLimit, String methodName, String className) {
        // 獲取用戶自定義的 key
        String key = rateLimit.key();
        
        if (StringUtils.hasText(key)) {
            // 支持 SpEL 表達(dá)式解析
            StandardEvaluationContext context = new StandardEvaluationContext();
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            String[] parameterNames = signature.getParameterNames();
            Object[] args = pjp.getArgs();
            
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
            
            ExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(key);
            key = expression.getValue(context, String.class);
        } else {
            // 默認(rèn)使用類名+方法名+IP 地址作為 key
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String ip = getIpAddress(request);
            key = ip + ":" + className + ":" + methodName;
        }
        
        return"rate_limit:" + key;
    }
    
    private boolean isLimited(String key, int time, int count) {
        // 使用 Redis 的計(jì)數(shù)器實(shí)現(xiàn)限流
        try {
            Long currentCount = redisTemplate.opsForValue().increment(key, 1);
            
            // 如果是第一次訪問,設(shè)置過期時(shí)間
            if (currentCount == 1) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            
            return currentCount > count;
        } catch (Exception e) {
            log.error("限流異常", e);
            returnfalse;
        }
    }
    
    private String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

在這個(gè)切面中,我們使用 @Around 注解攔截所有標(biāo)記了 @RateLimit 注解的方法。在方法執(zhí)行前,我們會(huì)根據(jù)注解中的參數(shù)計(jì)算出限流的鍵,并使用 Redis 的計(jì)數(shù)器來記錄請(qǐng)求次數(shù)。如果請(qǐng)求次數(shù)超過了限制,我們會(huì)拋出一個(gè)異常,提示用戶操作過于頻繁。

1.3 使用示例

以下是一個(gè)使用 @RateLimit 注解的示例:

圖片圖片

在這個(gè)示例中,我們?cè)?nbsp;getUser 方法上使用了 @RateLimit 注解,設(shè)置了 60 秒內(nèi)最多允許 3 次請(qǐng)求。在 updateUser 方法上,我們使用了 SpEL 表達(dá)式來動(dòng)態(tài)生成限流的鍵,實(shí)現(xiàn)了更加靈活的限流策略。

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 實(shí)現(xiàn)簡(jiǎn)單,上手容易:該方案的實(shí)現(xiàn)邏輯清晰,代碼量較少,即使是初學(xué)者也能快速掌握。在單機(jī)情況下,甚至可以去掉 Redis 換成本地緩存實(shí)現(xiàn),進(jìn)一步降低了實(shí)現(xiàn)難度。
  • 注解式使用,對(duì)業(yè)務(wù)代碼無侵入:通過自定義注解的方式,我們可以將限流邏輯與業(yè)務(wù)邏輯分離,對(duì)業(yè)務(wù)代碼的改動(dòng)非常小,不會(huì)影響原有的業(yè)務(wù)功能。
  • 可以精確控制接口粒度:我們可以針對(duì)不同的接口方法設(shè)置不同的限流參數(shù),實(shí)現(xiàn)對(duì)接口訪問頻率的精確控制。
  • 支持靈活的限流策略配置:通過 SpEL 表達(dá)式,我們可以根據(jù)請(qǐng)求的參數(shù)、用戶信息等動(dòng)態(tài)生成限流的鍵,實(shí)現(xiàn)更加靈活的限流策略。

缺點(diǎn)

  • 限流邏輯相對(duì)簡(jiǎn)單,無法應(yīng)對(duì)復(fù)雜場(chǎng)景:該方案的限流邏輯主要基于固定的時(shí)間窗口和請(qǐng)求次數(shù),對(duì)于一些復(fù)雜的場(chǎng)景,如突發(fā)流量、動(dòng)態(tài)限流等,可能無法滿足需求。
  • 缺少預(yù)警機(jī)制:當(dāng)請(qǐng)求次數(shù)接近或達(dá)到限制時(shí),系統(tǒng)無法及時(shí)發(fā)出預(yù)警,可能會(huì)導(dǎo)致用戶體驗(yàn)下降。

2. 令牌桶算法實(shí)現(xiàn)限流

令牌桶算法是一種更加靈活的限流算法,它就像一個(gè)裝有令牌的桶,系統(tǒng)會(huì)以固定的速率向桶中添加令牌,每個(gè)請(qǐng)求需要從桶中獲取一個(gè)令牌才能被處理。這種算法可以允許突發(fā)流量,同時(shí)又能限制長(zhǎng)期的平均流量,為系統(tǒng)提供了更加靈活的流量控制能力。

實(shí)現(xiàn)步驟

2.1 引入依賴

Google 提供的 Guava 庫中包含了令牌桶的實(shí)現(xiàn),我們可以通過以下依賴將其引入項(xiàng)目:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

2.2 創(chuàng)建令牌桶限流器

接下來,我們需要?jiǎng)?chuàng)建一個(gè)令牌桶限流器,用于管理不同接口的令牌桶。以下是具體的代碼實(shí)現(xiàn):

@Component
publicclass RateLimiter {
    // 使用 ConcurrentHashMap 存儲(chǔ)不同接口的令牌桶
    privatefinal ConcurrentHashMap<String, com.google.common.util.concurrent.RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
    
    /**
     * 獲取特定接口的令牌桶,不存在則創(chuàng)建
     * @param key 限流鍵
     * @param permitsPerSecond 每秒允許的請(qǐng)求量
     * @return 令牌桶實(shí)例
     */
    public com.google.common.util.concurrent.RateLimiter getRateLimiter(String key, double permitsPerSecond) {
        return rateLimiterMap.computeIfAbsent(key, 
            k -> com.google.common.util.concurrent.RateLimiter.create(permitsPerSecond));
    }
    
    /**
     * 嘗試獲取令牌
     * @param key 限流鍵
     * @param permitsPerSecond 每秒允許的請(qǐng)求量
     * @param timeout 超時(shí)時(shí)間
     * @param unit 時(shí)間單位
     * @return 是否獲取成功
     */
    public boolean tryAcquire(String key, double permitsPerSecond, long timeout, TimeUnit unit) {
        com.google.common.util.concurrent.RateLimiter rateLimiter = getRateLimiter(key, permitsPerSecond);
        return rateLimiter.tryAcquire(1, timeout, unit);
    }
}

在這個(gè)限流器中,我們使用 ConcurrentHashMap 來存儲(chǔ)不同接口的令牌桶,確保線程安全。通過 getRateLimiter 方法,我們可以根據(jù)限流鍵獲取對(duì)應(yīng)的令牌桶,如果令牌桶不存在,則會(huì)自動(dòng)創(chuàng)建一個(gè)新的令牌桶。通過 tryAcquire 方法,我們可以嘗試從令牌桶中獲取一個(gè)令牌,如果在指定的超時(shí)時(shí)間內(nèi)獲取成功,則返回 true,否則返回 false

2.3 創(chuàng)建攔截器

為了實(shí)現(xiàn)對(duì)接口的限流,我們需要?jiǎng)?chuàng)建一個(gè)攔截器,在請(qǐng)求進(jìn)入接口之前進(jìn)行令牌的獲取操作。以下是具體的代碼實(shí)現(xiàn):

圖片圖片

在這個(gè)攔截器中,我們首先判斷請(qǐng)求的 URI 是否以 /api/ 開頭,如果是,則進(jìn)行限流處理。然后,我們獲取請(qǐng)求的 IP 地址和 URI,組合成限流鍵。接著,我們嘗試從令牌桶中獲取一個(gè)令牌,如果獲取失敗,則返回一個(gè)限流響應(yīng),提示用戶請(qǐng)求過于頻繁。

2.4 配置攔截器

最后,我們需要將攔截器配置到 Spring Boot 應(yīng)用中,使其生效。以下是具體的代碼實(shí)現(xiàn):

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private TokenBucketInterceptor tokenBucketInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenBucketInterceptor)
                .addPathPatterns("/**");
    }
}

在這個(gè)配置類中,我們使用 WebMvcConfigurer 接口的 addInterceptors 方法將攔截器添加到攔截器鏈中,并設(shè)置攔截所有的請(qǐng)求。

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 支持突發(fā)流量,不會(huì)完全拒絕短時(shí)高峰:令牌桶算法允許在短時(shí)間內(nèi)有較高的請(qǐng)求速率,只要桶中有足夠的令牌。當(dāng)突發(fā)流量到來時(shí),系統(tǒng)可以快速處理這些請(qǐng)求,而不會(huì)像固定窗口算法那樣直接拒絕請(qǐng)求。
  • 平滑的限流效果,用戶體驗(yàn)更好:由于令牌桶算法是以固定的速率向桶中添加令牌,因此可以實(shí)現(xiàn)平滑的限流效果,避免了請(qǐng)求的突然中斷,提高了用戶體驗(yàn)。
  • 可以配置不同接口的不同限流策略:通過使用不同的限流鍵,我們可以為不同的接口配置不同的令牌桶,實(shí)現(xiàn)對(duì)不同接口的差異化限流。
  • 無需額外的存儲(chǔ)設(shè)施:令牌桶算法的實(shí)現(xiàn)不需要額外的存儲(chǔ)設(shè)施,只需要在內(nèi)存中維護(hù)一個(gè)令牌桶即可,降低了系統(tǒng)的復(fù)雜度和成本。

缺點(diǎn)

  • 只適用于單機(jī)部署,分布式環(huán)境需要額外改造:該方案的令牌桶是在內(nèi)存中維護(hù)的,因此只適用于單機(jī)部署的環(huán)境。在分布式環(huán)境中,需要將令牌桶的狀態(tài)存儲(chǔ)到共享的存儲(chǔ)設(shè)施中,如 Redis,才能實(shí)現(xiàn)分布式限流。
  • 重啟應(yīng)用后狀態(tài)丟失:由于令牌桶的狀態(tài)是在內(nèi)存中維護(hù)的,因此當(dāng)應(yīng)用重啟后,令牌桶的狀態(tài)會(huì)丟失,需要重新初始化。
  • 無法精確控制時(shí)間窗口內(nèi)的請(qǐng)求總量:令牌桶算法只能控制請(qǐng)求的平均速率,無法精確控制在某個(gè)時(shí)間窗口內(nèi)的請(qǐng)求總量。在某些對(duì)請(qǐng)求總量有嚴(yán)格限制的場(chǎng)景下,可能無法滿足需求。

3. 分布式限流(Redis + Lua 腳本)

在分布式系統(tǒng)中,單機(jī)限流方案往往難以滿足需求,因?yàn)椴煌膶?shí)例之間無法共享限流狀態(tài)。利用 Redis 和 Lua 腳本可以實(shí)現(xiàn)高效的分布式限流,確保系統(tǒng)在分布式環(huán)境下的安全性和穩(wěn)定性。

實(shí)現(xiàn)步驟

3.1 定義 Lua 腳本

首先,我們需要定義一個(gè) Redis 限流的 Lua 腳本,用于實(shí)現(xiàn)限流邏輯。以下是具體的腳本內(nèi)容:

圖片圖片

在這個(gè)腳本中,我們首先獲取限流鍵、限流窗口、限流閾值和當(dāng)前時(shí)間戳。然后,我們移除過期的請(qǐng)求記錄,統(tǒng)計(jì)當(dāng)前窗口內(nèi)的請(qǐng)求數(shù)。如果請(qǐng)求數(shù)超過了閾值,我們返回 0 表示拒絕請(qǐng)求。否則,我們添加當(dāng)前請(qǐng)求記錄,并設(shè)置過期時(shí)間,最后返回當(dāng)前窗口剩余可用請(qǐng)求數(shù)。

3.2 創(chuàng)建 Redis 限流服務(wù)

接下來,我們需要?jiǎng)?chuàng)建一個(gè) Redis 限流服務(wù),用于執(zhí)行 Lua 腳本并處理限流邏輯。以下是具體的代碼實(shí)現(xiàn):

@Service
@Slf4j
publicclass RedisRateLimiterService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private DefaultRedisScript<Long> rateLimiterScript;
    
    @PostConstruct
    public void init() {
        // 加載 Lua 腳本
        rateLimiterScript = new DefaultRedisScript<>();
        rateLimiterScript.setLocation(new ClassPathResource("scripts/rate_limiter.lua"));
        rateLimiterScript.setResultType(Long.class);
    }
    
    /**
     * 嘗試獲取訪問權(quán)限
     * @param key 限流鍵
     * @param window 時(shí)間窗口(秒)
     * @param threshold 閾值
     * @return 剩余可用請(qǐng)求數(shù),-1 表示被限流
     */
    public long isAllowed(String key, int window, int threshold) {
        try {
            // 執(zhí)行 lua 腳本
            List<String> keys = Collections.singletonList(key);
            Long remainingCount = redisTemplate.execute(
                rateLimiterScript, 
                keys, 
                String.valueOf(window), 
                String.valueOf(threshold),
                String.valueOf(System.currentTimeMillis())
            );
            
            return remainingCount == null ? -1 : remainingCount;
        } catch (Exception e) {
            log.error("Redis rate limiter error", e);
            // 發(fā)生異常時(shí)放行請(qǐng)求
            return threshold;
        }
    }
}

在這個(gè)服務(wù)中,我們使用 StringRedisTemplate 來執(zhí)行 Lua 腳本。在 init 方法中,我們加載 Lua 腳本并設(shè)置返回值類型。在 isAllowed 方法中,我們執(zhí)行 Lua 腳本并根據(jù)返回值判斷是否允許訪問。如果返回值為 -1 表示被限流,否則返回剩余可用請(qǐng)求數(shù)。

3.3 創(chuàng)建分布式限流注解

為了方便使用,我們可以創(chuàng)建一個(gè)分布式限流注解,用于標(biāo)記需要進(jìn)行分布式限流的接口方法。以下是具體的代碼實(shí)現(xiàn):

圖片圖片

在這個(gè)注解中,我們可以通過 prefix 屬性設(shè)置限流鍵的前綴,通過 window 屬性設(shè)置時(shí)間窗口,通過 threshold 屬性設(shè)置時(shí)間窗口內(nèi)允許的最大請(qǐng)求數(shù),通過 mode 屬性設(shè)置限流模式。

3.4 實(shí)現(xiàn)分布式限流切面

接下來,我們需要實(shí)現(xiàn)一個(gè) AOP 切面,用于處理分布式限流邏輯。以下是具體的代碼實(shí)現(xiàn):

@Aspect
@Component
@Slf4j
publicclass DistributedRateLimitAspect {
    
    @Autowired
    private RedisRateLimiterService rateLimiterService;
    
    @Autowired(required = false)
    private HttpServletRequest request;
    
    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint pjp, DistributedRateLimit rateLimit) throws Throwable {
        String key = generateKey(pjp, rateLimit);
        
        long remainingCount = rateLimiterService.isAllowed(
            key, 
            rateLimit.window(), 
            rateLimit.threshold()
        );
        
        if (remainingCount < 0) {
            thrownew RuntimeException("接口訪問過于頻繁,請(qǐng)稍后再試");
        }
        
        // 執(zhí)行目標(biāo)方法
        return pjp.proceed();
    }
    
    private String generateKey(ProceedingJoinPoint pjp, DistributedRateLimit rateLimit) {
        String methodName = pjp.getSignature().getName();
        String className = pjp.getTarget().getClass().getName();
        StringBuilder key = new StringBuilder(rateLimit.prefix());
        
        key.append(className).append(".").append(methodName);
        
        // 根據(jù)限流模式添加不同的后綴
        switch (rateLimit.mode()) {
            case"ip":
                // 按 IP 限流
                key.append(":").append(getIpAddress());
                break;
            case"user":
                // 按用戶限流
                Object userId = getUserId();
                key.append(":").append(userId != null ? userId : "anonymous");
                break;
            case"all":
                // 接口總體限流,不添加后綴
                break;
            default:
                key.append(":").append(getIpAddress());
                break;
        }
        
        return key.toString();
    }
    
    private String getIpAddress() {
        // IP 獲取方法同上
        if (request == null) {
            return"unknown";
        }
        // 獲取 IP 的代碼同上一個(gè)示例
        return"127.0.0.1"; // 簡(jiǎn)化處理
    }
    
    // 獲取當(dāng)前用戶 ID,根據(jù)實(shí)際認(rèn)證系統(tǒng)實(shí)現(xiàn)
    private Object getUserId() {
        // 這里簡(jiǎn)化處理,實(shí)際中應(yīng)從認(rèn)證信息中獲取
        // 例如:SecurityContextHolder.getContext().getAuthentication().getPrincipal()
        returnnull;
    }
}

在這個(gè)切面中,我們使用 @Around 注解攔截所有標(biāo)記了 @DistributedRateLimit 注解的方法。在方法執(zhí)行前,我們會(huì)根據(jù)注解中的參數(shù)生成限流鍵,并調(diào)用 RedisRateLimiterService 的 isAllowed 方法判斷是否允許訪問。如果不允許訪問,我們會(huì)拋出一個(gè)異常,提示用戶接口訪問過于頻繁。

3.5 使用示例

以下是一個(gè)使用 @DistributedRateLimit 注解的示例:

@RestController
@RequestMapping("/api")
publicclass PaymentController {
    
    @DistributedRateLimit(prefix = "pay:", window = 3600, threshold = 5, mode = "user")
    @PostMapping("/payment")
    public Result createPayment(@RequestBody PaymentRequest paymentRequest) {
        // 創(chuàng)建支付業(yè)務(wù)邏輯
        return paymentService.createPayment(paymentRequest);
    }
    
    @DistributedRateLimit(window = 60, threshold = 30, mode = "ip")
    @GetMapping("/products")
    public List<Product> getProducts() {
        // 查詢產(chǎn)品列表
        return productService.findAll();
    }
    
    @DistributedRateLimit(window = 1, threshold = 100, mode = "all")
    @GetMapping("/hot/resource")
    public Resource getHotResource() {
        // 獲取熱門資源
        return resourceService.getHotResource();
    }
}

在這個(gè)示例中,我們?cè)?nbsp;createPayment 方法上使用了 @DistributedRateLimit 注解,設(shè)置了 3600 秒內(nèi)每個(gè)用戶最多允許 5 次請(qǐng)求。在 getProducts 方法上,我們?cè)O(shè)置了 60 秒內(nèi)每個(gè) IP 最多允許 30 次請(qǐng)求。在 getHotResource 方法上,我們?cè)O(shè)置了 1 秒內(nèi)整個(gè)接口最多允許 100 次請(qǐng)求。

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 適用于分布式系統(tǒng),多實(shí)例間共享限流狀態(tài):通過使用 Redis 作為共享存儲(chǔ),不同的實(shí)例可以共享限流狀態(tài),確保系統(tǒng)在分布式環(huán)境下的一致性和穩(wěn)定性。
  • 支持多種限流模式:可以根據(jù) IP、用戶、接口總量等不同的維度進(jìn)行限流,滿足不同場(chǎng)景的需求。
  • 基于滑動(dòng)窗口,計(jì)數(shù)更精確:Lua 腳本使用滑動(dòng)窗口算法來統(tǒng)計(jì)請(qǐng)求數(shù),相比固定窗口算法,計(jì)數(shù)更加精確,可以有效避免誤判。
  • 使用 Lua 腳本保證原子性,避免競(jìng)態(tài)條件:Lua 腳本在 Redis 中是原子執(zhí)行的,確保了限流邏輯的原子性,避免了多個(gè)實(shí)例同時(shí)修改限流狀態(tài)時(shí)可能出現(xiàn)的競(jìng)態(tài)條件。

缺點(diǎn)

  • 強(qiáng)依賴 Redis:該方案的實(shí)現(xiàn)依賴于 Redis,如果 Redis 出現(xiàn)故障,可能會(huì)影響系統(tǒng)的正常運(yùn)行。
  • 實(shí)現(xiàn)復(fù)雜度較高:需要編寫 Lua 腳本并進(jìn)行 Redis 操作,實(shí)現(xiàn)復(fù)雜度相對(duì)較高,對(duì)開發(fā)人員的技術(shù)要求也較高。

4. 集成 Sentinel 實(shí)現(xiàn)接口防刷

阿里巴巴開源的 Sentinel 是一個(gè)強(qiáng)大的流量控制組件,它就像一個(gè)智能的“交通警察”,可以對(duì)系統(tǒng)的流量進(jìn)行實(shí)時(shí)監(jiān)控和控制,提供了豐富的限流、熔斷、系統(tǒng)保護(hù)等功能。

實(shí)現(xiàn)步驟

4.1 添加依賴

首先,我們需要在項(xiàng)目中添加 Sentinel 的依賴:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2021.0.4.0</version>
</dependency>

4.2 配置 Sentinel

在 application.properties 中添加 Sentinel 的配置:

# Sentinel 控制臺(tái)地址
spring.cloud.sentinel.transport.dashboard=localhost:8080
# 取消 Sentinel 控制臺(tái)懶加載
spring.cloud.sentinel.eager=true
# 應(yīng)用名稱
spring.application.name=my-application

在這個(gè)配置中,我們?cè)O(shè)置了 Sentinel 控制臺(tái)的地址,取消了控制臺(tái)的懶加載,并指定了應(yīng)用的名稱。

4.3 創(chuàng)建 Sentinel 配置

接下來,我們需要?jiǎng)?chuàng)建一個(gè) Sentinel 配置類,用于定義流控規(guī)則。以下是具體的代碼實(shí)現(xiàn):

圖片圖片

在這個(gè)配置類中,我們創(chuàng)建了一個(gè) SentinelResourceAspect 實(shí)例,并在 init 方法中定義了流控規(guī)則。對(duì)于 /api/user 接口,我們?cè)O(shè)置了每秒允許 10 個(gè)請(qǐng)求的限流規(guī)則。對(duì)于 /api/order 接口,我們?cè)O(shè)置了每秒允許 5 個(gè)請(qǐng)求的限流規(guī)則,并開啟了預(yù)熱模式,預(yù)熱期為 10 秒。

4.4 創(chuàng)建 URL 資源解析器

為了讓 Sentinel 能夠正確識(shí)別請(qǐng)求的資源,我們需要?jiǎng)?chuàng)建一個(gè) URL 資源解析器。以下是具體的代碼實(shí)現(xiàn):

@Component
publicclass UrlCleaner implements RequestOriginParser {
    
    @Override
    public String parseOrigin(HttpServletRequest request) {
        // 獲取請(qǐng)求的 URL 路徑
        String path = request.getRequestURI();
        
        // 可以添加更復(fù)雜的解析邏輯,例如:
        // 1. 去除路徑變量:/api/user/123 -> /api/user/{id}
        // 2. 添加請(qǐng)求方法前綴:GET:/api/user
        
        return path;
    }
}

在這個(gè)解析器中,我們簡(jiǎn)單地返回請(qǐng)求的 URL 路徑,你可以根據(jù)實(shí)際需求添加更復(fù)雜的解析邏輯。

4.5 創(chuàng)建全局異常處理器

當(dāng)請(qǐng)求被 Sentinel 限流時(shí),會(huì)拋出 BlockException 異常,我們需要?jiǎng)?chuàng)建一個(gè)全局異常處理器來處理這個(gè)異常。以下是具體的代碼實(shí)現(xiàn):

@RestControllerAdvice
publicclass SentinelExceptionHandler {
    
    @ExceptionHandler(BlockException.class)
    public Result handleBlockException(BlockException e) {
        String message = "請(qǐng)求過于頻繁,請(qǐng)稍后再試";
        if (e instanceof FlowException) {
            message = "接口限流:" + message;
        } elseif (e instanceof DegradeException) {
            message = "服務(wù)降級(jí):系統(tǒng)繁忙,請(qǐng)稍后再試";
        } elseif (e instanceof ParamFlowException) {
            message = "熱點(diǎn)參數(shù)限流:請(qǐng)求過于頻繁";
        } elseif (e instanceof SystemBlockException) {
            message = "系統(tǒng)保護(hù):系統(tǒng)資源不足";
        } elseif (e instanceof AuthorityException) {
            message = "授權(quán)控制:沒有訪問權(quán)限";
        }
        
        return Result.error(429, message);
    }
}

在這個(gè)異常處理器中,我們根據(jù)不同的 BlockException 類型返回不同的錯(cuò)誤信息,提示用戶請(qǐng)求被限流的原因。

4.6 使用 @SentinelResource 注解

為了讓 Sentinel 能夠?qū)涌谶M(jìn)行限流,我們需要在接口方法上使用 @SentinelResource 注解。以下是具體的代碼實(shí)現(xiàn):

@RestController
@RequestMapping("/api")
publicclass UserController {
    
    // 使用資源名定義限流資源
    @SentinelResource(value = "getUserById", 
                      blockHandler = "getUserBlockHandler",
                      fallback = "getUserFallback")
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUser(id);
    }
    
    // 限流處理方法
    public User getUserBlockHandler(Long id, BlockException e) {
        log.warn("Get user request blocked: {}", id, e);
        thrownew RuntimeException("請(qǐng)求頻率過高,請(qǐng)稍后再試");
    }
    
    // 異常回退方法
    public User getUserFallback(Long id, Throwable t) {
        log.error("Get user failed: {}", id, t);
        User fallbackUser = new User();
        fallbackUser.setId(id);
        fallbackUser.setName("Unknown");
        return fallbackUser;
    }
}

在這個(gè)示例中,我們?cè)?nbsp;getUser 方法上使用了 @SentinelResource 注解,指定了資源名、限流處理方法和異常回退方法。當(dāng)請(qǐng)求被限流時(shí),會(huì)調(diào)用 getUserBlockHandler 方法進(jìn)行處理。當(dāng)方法執(zhí)行過程中出現(xiàn)異常時(shí),會(huì)調(diào)用 getUserFallback 方法進(jìn)行回退。

4.7 更復(fù)雜的限流規(guī)則配置

除了基本的流控規(guī)則,Sentinel 還支持更復(fù)雜的限流規(guī)則配置,如基于 QPS + 調(diào)用關(guān)系的限流規(guī)則、基于并發(fā)線程數(shù)的限流規(guī)則、熱點(diǎn)參數(shù)限流規(guī)則等。以下是具體的代碼實(shí)現(xiàn):

@Service
@Slf4j
publicclass SentinelRuleService {
    
    public void initComplexFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        
        // 基于 QPS + 調(diào)用關(guān)系的限流規(guī)則
        FlowRule apiRule = new FlowRule();
        apiRule.setResource("/api/data");
        apiRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        apiRule.setCount(20);
        
        // 限制調(diào)用來源
        apiRule.setLimitApp("frontend"); // 只限制來自前端應(yīng)用的調(diào)用
        
        // 流控策略:關(guān)聯(lián)資源
        apiRule.setStrategy(RuleConstant.STRATEGY_RELATE);
        apiRule.setRefResource("/api/important"); // 當(dāng) important 接口 QPS 高時(shí),限制 data 接口
        
        rules.add(apiRule);
        
        // 基于并發(fā)線程數(shù)的限流
        FlowRule threadRule = new FlowRule();
        threadRule.setResource("/api/heavy-task");
        threadRule.setGrade(RuleConstant.FLOW_GRADE_THREAD); // 基于線程數(shù)
        threadRule.setCount(5); // 最多 5 個(gè)線程同時(shí)處理
        rules.add(threadRule);
        
        // 加載規(guī)則
        FlowRuleManager.loadRules(rules);
    }
    
    public void initHotspotRules() {
        // 熱點(diǎn)參數(shù)限流規(guī)則
        List<ParamFlowRule> rules = new ArrayList<>();
        
        ParamFlowRule rule = new ParamFlowRule("/api/product");
        // 對(duì)第 0 個(gè)參數(shù)(productId)進(jìn)行限流
        rule.setParamIdx(0);
        rule.setCount(5);
        
        // 特例配置
        ParamFlowItem item1 = new ParamFlowItem();
        item1.setObject("1"); // productId = 1 的商品
        item1.setCount(10);  // 可以有更高的 QPS
        
        ParamFlowItem item2 = new ParamFlowItem();
        item2.setObject("2"); // productId = 2 的商品
        item2.setCount(2);   // 更嚴(yán)格的限制
        
        rule.setParamFlowItemList(Arrays.asList(item1, item2));
        
        rules.add(rule);
        ParamFlowRuleManager.loadRules(rules);
    }
}

在這個(gè)服務(wù)中,我們定義了基于 QPS + 調(diào)用關(guān)系的限流規(guī)則、基于并發(fā)線程數(shù)的限流規(guī)則和熱點(diǎn)參數(shù)限流規(guī)則,并使用 FlowRuleManager 和 ParamFlowRuleManager 加載這些規(guī)則。

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 功能全面:Sentinel 支持 QPS 限流、并發(fā)線程數(shù)限流、熱點(diǎn)參數(shù)限流等多種限流方式,還提供了熔斷、系統(tǒng)保護(hù)等功能,可以滿足不同場(chǎng)景的需求。
  • 支持多種控制策略:可以選擇直接拒絕、預(yù)熱、排隊(duì)等多種控制策略,根據(jù)實(shí)際情況靈活調(diào)整限流行為。
  • 提供控制臺(tái)可視化管理:Sentinel 提供了可視化的控制臺(tái),可以實(shí)時(shí)監(jiān)控系統(tǒng)的流量情況,方便進(jìn)行規(guī)則的配置和管理。
  • 支持動(dòng)態(tài)規(guī)則調(diào)整:可以通過控制臺(tái)或 API 動(dòng)態(tài)調(diào)整限流規(guī)則,無需重啟應(yīng)用,提高了系統(tǒng)的靈活性和可維護(hù)性。
  • 可與 Spring Cloud 體系無縫集成:Sentinel 可以與 Spring Cloud 體系無縫集成,方便在微服務(wù)架構(gòu)中使用。

缺點(diǎn)

  • 學(xué)習(xí)曲線較陡峭:Sentinel 的功能豐富,配置復(fù)雜,對(duì)于初學(xué)者來說,學(xué)習(xí)成本較高。
  • 分布式場(chǎng)景下需要額外配置規(guī)則持久化:在分布式場(chǎng)景下,需要額外配置規(guī)則持久化,確保不同實(shí)例之間的規(guī)則一致性。
  • 引入了額外的依賴:集成 Sentinel 需要引入額外的依賴,增加了項(xiàng)目的復(fù)雜度和維護(hù)成本。

5. 驗(yàn)證碼與行為分析防刷

對(duì)于某些敏感操作,如登錄、注冊(cè)、支付等,單純的限流可能無法有效防止惡意請(qǐng)求。此時(shí),可以結(jié)合驗(yàn)證碼和行為分析來進(jìn)一步增強(qiáng)系統(tǒng)的安全性,有效區(qū)分人類用戶和自動(dòng)化腳本。

實(shí)現(xiàn)步驟

5.1 圖形驗(yàn)證碼實(shí)現(xiàn)

首先,我們需要添加圖形驗(yàn)證碼的依賴:

<dependency>
    <groupId>com.github.whvcse</groupId>
    <artifactId>easy-captcha</artifactId>
    <version>1.6.2</version>
</dependency>

5.2 創(chuàng)建驗(yàn)證碼服務(wù)

接下來,我們需要?jiǎng)?chuàng)建一個(gè)驗(yàn)證碼服務(wù),用于生成和驗(yàn)證驗(yàn)證碼。以下是具體的代碼實(shí)現(xiàn):

@Service
publicclass CaptchaService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    privatestaticfinallong CAPTCHA_EXPIRE_TIME = 5 * 60; // 5 分鐘
    
    /**
     * 生成驗(yàn)證碼
     * @param request HTTP 請(qǐng)求
     * @param response HTTP 響應(yīng)
     * @return 驗(yàn)證碼 Base64 字符串
     */
    public String generateCaptcha(HttpServletRequest request, HttpServletResponse response) {
        // 生成驗(yàn)證碼
        SpecCaptcha captcha = new SpecCaptcha(130, 48, 5);
        
        // 生成驗(yàn)證碼 ID
        String captchaId = UUID.randomUUID().toString();
        
        // 將驗(yàn)證碼存入 Redis
        redisTemplate.opsForValue().set(
            "captcha:" + captchaId, 
            captcha.text().toLowerCase(), 
            CAPTCHA_EXPIRE_TIME, 
            TimeUnit.SECONDS
        );
        
        // 設(shè)置 Cookie
        Cookie cookie = new Cookie("captchaId", captchaId);
        cookie.setMaxAge((int) CAPTCHA_EXPIRE_TIME);
        cookie.setPath("/");
        response.addCookie(cookie);
        
        // 返回 Base64 編碼的驗(yàn)證碼圖片
        return captcha.toBase64();
    }
    
    /**
     * 驗(yàn)證驗(yàn)證碼
     * @param request HTTP 請(qǐng)求
     * @param captchaCode 用戶輸入的驗(yàn)證碼
     * @return 是否驗(yàn)證通過
     */
    public boolean validateCaptcha(HttpServletRequest request, String captchaCode) {
        // 從 Cookie 獲取驗(yàn)證碼 ID
        Cookie[] cookies = request.getCookies();
        String captchaId = null;
        
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("captchaId".equals(cookie.getName())) {
                    captchaId = cookie.getValue();
                    break;
                }
            }
        }
        
        if (captchaId == null) {
            returnfalse;
        }
        
        // 從 Redis 獲取正確的驗(yàn)證碼
        String key = "captcha:" + captchaId;
        String correctCode = redisTemplate.opsForValue().get(key);
        
        // 驗(yàn)證成功后刪除驗(yàn)證碼
        if (correctCode != null && correctCode.equals(captchaCode.toLowerCase())) {
            redisTemplate.delete(key);
            returntrue;
        }
        
        returnfalse;
    }
}

在這個(gè)服務(wù)中,我們使用 easy-captcha 庫生成驗(yàn)證碼,并將驗(yàn)證碼存入 Redis 中。在驗(yàn)證驗(yàn)證碼時(shí),我們從 Cookie 中獲取驗(yàn)證碼 ID,然后從 Redis 中獲取正確的驗(yàn)證碼進(jìn)行比對(duì)。

5.3 創(chuàng)建驗(yàn)證碼控制器

為了方便前端獲取驗(yàn)證碼,我們需要?jiǎng)?chuàng)建一個(gè)驗(yàn)證碼控制器。以下是具體的代碼實(shí)現(xiàn):

@RestController
@RequestMapping("/api/captcha")
public class CaptchaController {
    
    @Autowired
    private CaptchaService captchaService;
    
    @GetMapping
    public Map<String, String> getCaptcha(HttpServletRequest request, HttpServletResponse response) {
        String base64 = captchaService.generateCaptcha(request, response);
        return Map.of("captcha", base64);
    }
}

在這個(gè)控制器中,我們提供了一個(gè) GET 請(qǐng)求接口,用于生成驗(yàn)證碼并返回 Base64 編碼的驗(yàn)證碼圖片。

5.4 創(chuàng)建驗(yàn)證碼注解

為了標(biāo)記需要進(jìn)行驗(yàn)證碼驗(yàn)證的接口方法,我們可以創(chuàng)建一個(gè)驗(yàn)證碼注解。以下是具體的代碼實(shí)現(xiàn):

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CaptchaRequired {
    String captchaParam() default "captchaCode";
}

在這個(gè)注解中,我們可以通過 captchaParam 屬性指定驗(yàn)證碼參數(shù)的名稱。

5.5 實(shí)現(xiàn)驗(yàn)證碼攔截器

接下來,我們需要實(shí)現(xiàn)一個(gè)驗(yàn)證碼攔截器,在請(qǐng)求進(jìn)入接口之前進(jìn)行驗(yàn)證碼驗(yàn)證。以下是具體的代碼實(shí)現(xiàn):

@Component
publicclass CaptchaInterceptor implements HandlerInterceptor {
    
    @Autowired
    private CaptchaService captchaService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            returntrue;
        }
        
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        CaptchaRequired captchaRequired = handlerMethod.getMethodAnnotation(CaptchaRequired.class);
        
        if (captchaRequired == null) {
            returntrue;
        }
        
        // 獲取驗(yàn)證碼參數(shù)
        String captchaParam = captchaRequired.captchaParam();
        String captchaCode = request.getParameter(captchaParam);
        
        if (StringUtils.hasText(captchaCode)) {
            // 驗(yàn)證驗(yàn)證碼
            boolean valid = captchaService.validateCaptcha(request, captchaCode);
            if (valid) {
                returntrue;
            }
        }
        
        // 驗(yàn)證失敗
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        response.getWriter().write("{\"code\":400,\"message\":\"驗(yàn)證碼錯(cuò)誤或已過期\"}");
        returnfalse;
    }
}

在這個(gè)攔截器中,我們首先判斷請(qǐng)求的處理方法是否標(biāo)記了 @CaptchaRequired 注解。如果標(biāo)記了,則獲取驗(yàn)證碼參數(shù)并調(diào)用 CaptchaService 的 validateCaptcha 方法進(jìn)行驗(yàn)證。如果驗(yàn)證失敗,則返回一個(gè)錯(cuò)誤響應(yīng),提示用戶驗(yàn)證碼錯(cuò)誤或已過期。

5.6 創(chuàng)建行為分析服務(wù)

除了驗(yàn)證碼,我們還可以通過行為分析來檢測(cè)可疑的機(jī)器行為。以下是具體的代碼實(shí)現(xiàn):

@Service
@Slf4j
publicclass BehaviorAnalysisService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 檢查是否是可疑的機(jī)器行為
     * @param request HTTP 請(qǐng)求
     * @return 是否可疑
     */
    public boolean isSuspicious(HttpServletRequest request) {
        // 1. 獲取客戶端信息
        String ip = getIpAddress(request);
        String userAgent = request.getHeader("User-Agent");
        String requestId = request.getSession().getId();
        
        // 2. 檢查訪問頻率
        String freqKey = "behavior:freq:" + ip;
        Long count = redisTemplate.opsForValue().increment(freqKey, 1);
        redisTemplate.expire(freqKey, 1, TimeUnit.MINUTES);
        
        if (count != null && count > 30) {
            log.warn("訪問頻率異常: IP={}, count={}", ip, count);
            returntrue;
        }
        
        // 3. 檢查 User-Agent
        if (userAgent == null || isBotuserAgent(userAgent)) {
            log.warn("可疑的 User-Agent: {}", userAgent);
            returntrue;
        }
        
        // 4. 檢查請(qǐng)求時(shí)間模式
        String timeKey = "behavior:time:" + ip;
        long now = System.currentTimeMillis();
        String lastTimeStr = redisTemplate.opsForValue().get(timeKey);
        
        if (lastTimeStr != null) {
            long lastTime = Long.parseLong(lastTimeStr);
            long interval = now - lastTime;
            
            // 如果請(qǐng)求間隔非常均勻,可能是機(jī)器人
            if (isUniformInterval(ip, interval)) {
                log.warn("請(qǐng)求間隔異常均勻: IP={}, interval={}", ip, interval);
                returntrue;
            }
        }
        
        redisTemplate.opsForValue().set(timeKey, String.valueOf(now), 10, TimeUnit.MINUTES);
        
        // 更多高級(jí)檢測(cè)邏輯...
        
        returnfalse;
    }
    
    /**
     * 檢查是否是機(jī)器人 UA
     */
    private boolean isBotuserAgent(String userAgent) {
        String ua = userAgent.toLowerCase();
        return ua.contains("bot") || ua.contains("spider") || ua.contains("crawl") ||
               ua.isEmpty() || ua.length() < 40;
    }
    
    /**
     * 檢查請(qǐng)求間隔是否異常均勻
     */
    private boolean isUniformInterval(String ip, long interval) {
        String key = "behavior:intervals:" + ip;
        
        // 獲取最近的幾個(gè)間隔
        List<String> intervalStrs = redisTemplate.opsForList().range(key, 0, 4);
        redisTemplate.opsForList().leftPush(key, String.valueOf(interval));
        redisTemplate.opsForList().trim(key, 0, 9);  // 只保留最近 10 個(gè)
        redisTemplate.expire(key, 10, TimeUnit.MINUTES);
        
        if (intervalStrs == null || intervalStrs.size() < 5) {
            returnfalse;
        }
        
        // 計(jì)算間隔的方差,方差小說明請(qǐng)求間隔很均勻
        List<Long> intervals = intervalStrs.stream()
                .map(Long::parseLong)
                .collect(Collectors.toList());
        
        double mean = intervals.stream().mapToLong(Long::longValue).average().orElse(0);
        double variance = intervals.stream()
                .mapToDouble(i -> Math.pow(i - mean, 2))
                .average()
                .orElse(0);
        
        return variance < 100;  // 方差閾值,需要根據(jù)實(shí)際情況調(diào)整
    }
    
    // getIpAddress 方法同上
}

在這個(gè)服務(wù)中,我們從多個(gè)維度對(duì)請(qǐng)求進(jìn)行分析,包括訪問頻率、User-Agent 和請(qǐng)求時(shí)間模式。如果發(fā)現(xiàn)可疑的行為,我們會(huì)記錄日志并返回 true

5.7 創(chuàng)建行為分析攔截器

最后,我們需要?jiǎng)?chuàng)建一個(gè)行為分析攔截器,在請(qǐng)求進(jìn)入接口之前進(jìn)行行為分析。以下是具體的代碼實(shí)現(xiàn):

@Component
publicclass BehaviorAnalysisInterceptor implements HandlerInterceptor {
    
    @Autowired
    private BehaviorAnalysisService behaviorAnalysisService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 對(duì)于需要保護(hù)的端點(diǎn)進(jìn)行檢查
        String path = request.getRequestURI();
        if (path.startsWith("/api/") && isPotentialRiskEndpoint(path)) {
            boolean suspicious = behaviorAnalysisService.isSuspicious(request);
            
            if (suspicious) {
                // 需要驗(yàn)證碼或其他額外驗(yàn)證
                response.setContentType("application/json;charset=UTF-8");
                response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
                response.getWriter().write("{\"code\":429,\"message\":\"檢測(cè)到異常訪問,請(qǐng)進(jìn)行驗(yàn)證\",\"needCaptcha\":true}");
                returnfalse;
            }
        }
        
        returntrue;
    }
    
    /**
     * 判斷是否是高風(fēng)險(xiǎn)端點(diǎn)
     */
    private boolean isPotentialRiskEndpoint(String path) {
        return path.contains("/login") || 
               path.contains("/register") || 
               path.contains("/payment") || 
               path.contains("/order") ||
               path.contains("/password");
    }
}

在這個(gè)攔截器中,我們首先判斷請(qǐng)求的 URI 是否以 /api/ 開頭,并且是否是高風(fēng)險(xiǎn)端點(diǎn)。如果是,則調(diào)用 BehaviorAnalysisService 的 isSuspicious 方法進(jìn)行行為分析。如果發(fā)現(xiàn)可疑行為,則返回一個(gè)錯(cuò)誤響應(yīng),提示用戶進(jìn)行驗(yàn)證。

5.8 使用示例

以下是一個(gè)使用驗(yàn)證碼和行為分析的示例:

@RestController
@RequestMapping("/api")
publicclass UserController {
    
    @CaptchaRequired
    @PostMapping("/login")
    public Result login(@RequestParam String username, 
                        @RequestParam String password,
                        @RequestParam String captchaCode) {
        // 登錄邏輯
        return userService.login(username, password);
    }
    
    @CaptchaRequired
    @PostMapping("/register")
    public Result register(@RequestBody UserRegisterDTO registerDTO,
                          @RequestParam String captchaCode) {
        // 注冊(cè)邏輯
        return userService.register(registerDTO);
    }
}

在這個(gè)示例中,我們?cè)?nbsp;login 和 register 方法上使用了 @CaptchaRequired 注解,要求用戶輸入驗(yàn)證碼進(jìn)行驗(yàn)證。

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 能有效區(qū)分人類用戶和自動(dòng)化腳本:通過驗(yàn)證碼和行為分析,可以準(zhǔn)確地識(shí)別出自動(dòng)化腳本的惡意請(qǐng)求,有效防止刷接口行為。
  • 對(duì)惡意用戶有較強(qiáng)的阻止作用:驗(yàn)證碼和行為分析的雙重防護(hù),大大增加了惡意用戶的攻擊成本,使其難以得逞。
  • 針對(duì)敏感操作提供額外安全層:對(duì)于登錄、注冊(cè)、支付等敏感操作,驗(yàn)證碼和行為分析可以提供額外的安全保障,保護(hù)用戶的隱私和財(cái)產(chǎn)安全。
  • 可以實(shí)現(xiàn)自適應(yīng)安全策略:根據(jù)用戶的行為特征和系統(tǒng)的安全狀況,可以動(dòng)態(tài)調(diào)整驗(yàn)證碼和行為分析的策略,實(shí)現(xiàn)自適應(yīng)的安全防護(hù)。

缺點(diǎn)

  • 增加了用戶操作成本,可能影響用戶體驗(yàn):要求用戶輸入驗(yàn)證碼會(huì)增加用戶的操作步驟,降低用戶體驗(yàn)。特別是對(duì)于一些頻繁操作的用戶,可能會(huì)感到厭煩。
  • 實(shí)現(xiàn)復(fù)雜,需要前后端配合:驗(yàn)證碼和行為分析的實(shí)現(xiàn)涉及到前后端的多個(gè)環(huán)節(jié),需要前后端密切配合,增加了開發(fā)和維護(hù)的難度。
  • 某些驗(yàn)證碼可能被 OCR 技術(shù)破解:雖然現(xiàn)在的驗(yàn)證碼技術(shù)不斷發(fā)展,但仍然存在被 OCR 技術(shù)破解的風(fēng)險(xiǎn),需要不斷更新和改進(jìn)驗(yàn)證碼的生成和驗(yàn)證方式。
  • 行為分析可能產(chǎn)生誤判:行為分析是基于一定的規(guī)則和算法進(jìn)行的,可能會(huì)出現(xiàn)誤判的情況,將正常用戶的行為誤判為可疑行為,影響用戶的正常使用。

方案對(duì)比與選擇

方案

實(shí)現(xiàn)難度

防刷效果

分布式支持

用戶體驗(yàn)

適用場(chǎng)景

基于注解的訪問頻率限制

需配合 Redis

一般

一般接口,簡(jiǎn)單場(chǎng)景

令牌桶算法

中高

單機(jī)

允許突發(fā)流量的場(chǎng)景

分布式限流(Redis+Lua)

支持

一般

分布式系統(tǒng),精確限流

Sentinel

中高

需額外配置

可配置

復(fù)雜系統(tǒng),多維度防護(hù)

驗(yàn)證碼與行為分析

支持

較差

敏感操作,關(guān)鍵業(yè)務(wù)

總結(jié)

接口防刷是一個(gè)系統(tǒng)性工程,需要綜合考慮安全性、用戶體驗(yàn)、性能開銷和運(yùn)維復(fù)雜度等多方面因素。本文介紹的 5 種方案各有優(yōu)缺點(diǎn),你可以根據(jù)實(shí)際需求靈活選擇和組合。

無論采用哪種方案,接口防刷都應(yīng)該遵循以下原則:

  • 最小影響原則:盡量不影響正常用戶的體驗(yàn),確保系統(tǒng)在安全的前提下能夠高效運(yùn)行。
  • 梯度防護(hù)原則:根據(jù)接口的重要程度和風(fēng)險(xiǎn)等級(jí),采用不同強(qiáng)度的防護(hù)措施,實(shí)現(xiàn)精準(zhǔn)防護(hù)。
  • 可監(jiān)控原則:提供充分的監(jiān)控和告警機(jī)制,及時(shí)發(fā)現(xiàn)和處理異常情況,確保系統(tǒng)的安全性和穩(wěn)定性。
  • 靈活調(diào)整原則:支持動(dòng)態(tài)調(diào)整防護(hù)參數(shù)和策略,根據(jù)系統(tǒng)的運(yùn)行情況和安全需求,及時(shí)做出調(diào)整。

通過合理實(shí)施接口防刷策略,可以有效提高系統(tǒng)的安全性和穩(wěn)定性,為用戶提供更好的服務(wù)體驗(yàn)。希望本文能對(duì)你有所幫助,讓你的系統(tǒng)在復(fù)雜的網(wǎng)絡(luò)環(huán)境中更加安全可靠!


責(zé)任編輯:武曉燕 來源: 碼猿技術(shù)專欄
相關(guān)推薦

2025-04-14 04:01:00

2025-07-02 08:00:00

防抖SpringBoot開發(fā)

2025-01-22 14:02:35

2023-10-17 08:55:08

數(shù)據(jù)庫數(shù)據(jù)業(yè)務(wù)

2025-09-01 02:00:00

2022-05-30 16:31:08

CSS

2025-02-08 08:00:00

JavaDeepSeekIDEA

2025-05-14 01:00:00

Spring工具工廠類

2024-08-29 15:26:21

2021-03-04 09:31:42

開源技術(shù) 項(xiàng)目

2025-04-10 00:25:00

Spring@JsonView注解

2025-01-13 13:47:13

2023-12-10 20:33:50

Redis搜索全文

2025-11-07 08:05:18

2025-06-12 08:21:22

2025-06-06 08:28:56

2025-06-26 01:22:00

SpringBean開發(fā)

2024-06-07 09:06:36

2025-01-20 10:22:23

2024-09-13 10:21:50

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

亚洲欧美欧美一区二区三区| 麻豆视频观看网址久久| 精品国产sm最大网站| 日本aa在线观看| 内射无码专区久久亚洲| 美女爽到呻吟久久久久| 色诱女教师一区二区三区| 在线观看一区二区三区视频| 亚洲国产欧美日本视频| 中文字幕在线不卡| 国产亚洲一区二区三区在线播放 | 美女做暖暖视频免费在线观看全部网址91 | 日韩经典在线观看| 欧洲杯足球赛直播| 欧美大片在线观看一区| 久久久久久亚洲精品中文字幕| 逼特逼视频在线观看| 奇米777日韩| 亚洲综合久久久久| 亚洲精品一区二区三区四区五区 | ijzzijzzij亚洲大全| 少妇高潮一区二区三区99小说| 91精品啪在线观看国产81旧版| 精品国产一区a| 三上悠亚av一区二区三区| free性护士videos欧美| 国产精品久久福利| 欧美不卡三区| 黄色一级大片在线免费看国产一 | 91麻豆高清视频| 成人黄在线观看| 99精品在线播放| 激情视频一区二区三区| 久久精彩免费视频| 色综合99久久久无码国产精品| 国产成人一二片| 欧美一级日韩一级| 三级视频中文字幕| 日本综合字幕| 色综合久久综合网97色综合| 国产欧美精品aaaaaa片| 91麻豆一二三四在线| 国产精品午夜电影| 日韩精品大片| 韩国三级在线观看久| av网站一区二区三区| 99理论电影网| 99re只有精品| 欧美视频官网| 另类图片亚洲另类| 中文字幕无码日韩专区免费| 激情婷婷综合| 国产亚洲精品高潮| 亚洲一区视频在线播放| 伊人春色精品| 亚洲欧美三级伦理| 男人舔女人下部高潮全视频| 国产99久久久国产精品成人免费| 亚洲国产精品人久久电影| 免费无码av片在线观看| 成年人在线观看| 26uuu国产电影一区二区| 狠狠色综合色区| 深夜福利在线视频| 国产夜色精品一区二区av| 欧美一区免费视频| 成人高清免费观看mv| 国产蜜臀97一区二区三区| 日韩欧美第二区在线观看| av福利精品| 国产嫩草影院久久久久| 一区二区三区欧美成人| 羞羞网站在线免费观看| 亚洲五月六月丁香激情| 老太脱裤让老头玩ⅹxxxx| 欧洲一区精品| 欧美专区日韩专区| 国产精品日韩三级| av丝袜在线| 日本乱码高清不卡字幕| 中文字幕永久有效| www.神马久久| 亚洲男人天堂视频| 9.1片黄在线观看| 国产精品mv在线观看| 国模私拍一区二区三区| 激情五月婷婷在线| 在线亚洲激情| 国产精品一区久久久| 国产一区二区自拍视频| 国产成人免费高清| 美女黄毛**国产精品啪啪| wwwww在线观看免费视频| 亚洲三级小视频| 欧美深夜福利视频| 成人av色网站| 欧美va日韩va| 日韩视频在线观看免费视频| 亚洲一区 二区 三区| 91精品国产高清自在线| 99热6这里只有精品| 国产精品亚洲片在线播放| 日韩亚洲精品视频| 在线看成人av| 久热成人在线视频| 国产一区二区不卡视频在线观看| 国产黄色片在线观看| 亚洲黄色免费网站| 亚洲免费av一区二区三区| 涩涩视频在线播放| 欧美高清一级片在线| 国产精品麻豆入口| 国产精品毛片久久| 日产精品99久久久久久| www.黄色片| 日本一区二区成人| 黄页免费在线观看视频| 色999久久久精品人人澡69 | 精品推荐蜜桃传媒| 一区二区三区精品在线| wwww.国产| 欧美一区 二区| 九九精品在线观看| 在线观看国产小视频| 99精品久久只有精品| 国产一二三四区在线观看| 日本成人片在线| 日韩精品福利在线| avtt香蕉久久| 欧美性久久久| 91在线视频导航| 91网在线播放| 色8久久精品久久久久久蜜| 色哟哟无码精品一区二区三区| 天天综合网91| 国产欧美一区二区三区久久人妖 | 999精品视频在这里| 色偷偷91综合久久噜噜| 999视频在线| 久久久一区二区三区| 五十路熟女丰满大屁股| 国产精品18hdxxxⅹ在线| 欧美成人国产va精品日本一级| 中文字幕人妻色偷偷久久| 久久精品男人天堂av| 日韩精品免费播放| 国内精品久久久久久99蜜桃| 欧美中文在线免费| 依依成人在线视频| 国产拍揄自揄精品视频麻豆| 97视频在线免费播放| 神马午夜久久| 最近日韩中文字幕中文| 日本精品入口免费视频| 激情六月婷婷久久| 亚洲一区二区精品在线观看| 成人h在线观看| 国产一区二区三区在线看| 日本激情视频一区二区三区| 日韩激情视频网站| 偷拍视频一区二区| 久久精品国产福利| 日韩中文第一页| 国产精品人人爽| 亚洲乱码中文字幕综合| 免费看91视频| 在线一区视频| 日韩av一级大片| 久久久久伊人| 久久综合电影一区| 欧美熟妇另类久久久久久不卡| 亚洲成人www| 久久精品一区二区免费播放| 视频一区视频二区中文| 欧美亚洲视频一区| 97久久综合区小说区图片区| 91国内精品久久| 狠狠v欧美ⅴ日韩v亚洲v大胸| 欧美人妖巨大在线| 国产一二三四在线| 久久久影院官网| 天天干天天av| 1024日韩| 亚洲精品欧洲精品| 一区二区视频| 精品久久久av| 欧洲av在线播放| 在线免费观看日本欧美| 欧美爱爱免费视频| www.成人网.com| 日韩大片一区二区| 国语精品一区| 色视频一区二区三区| 欧美日韩黄网站| 日本不卡高字幕在线2019| 尤物视频在线免费观看| 欧美色另类天堂2015| 免费一级特黄3大片视频| 国产精品 日产精品 欧美精品| 18岁网站在线观看| 一本精品一区二区三区| 久久亚洲免费| 日本在线成人| 久久精品国产一区二区电影| 亚洲精选一区二区三区| 在线观看视频91| 国产亚洲欧美久久久久| 国产精品乱码人人做人人爱| 成人性生生活性生交12| 欧美日韩国内| 亚洲三区在线观看| 夜夜躁狠狠躁日日躁2021日韩| 91亚洲精品一区二区| 中文字幕在线视频网站| 欧美华人在线视频| 在线免费看a| 亚洲老头老太hd| 亚洲国产精品久久久久爰性色| 欧美三片在线视频观看| 丰满少妇乱子伦精品看片| 波多野结衣中文字幕一区二区三区| 日本一级黄视频| 91综合久久爱com| 国产欧美日韩丝袜精品一区| 涩涩在线视频| 欧美极品少妇xxxxⅹ裸体艺术| 日本在线免费| 亚洲性xxxx| 五月天激情开心网| 精品国产露脸精彩对白 | 在线观看的黄色| 欧美肥臀大乳一区二区免费视频| 在线看av的网址| 亚洲性69xxxbbb| 国产最新视频在线| 亚洲精品一区二区三区婷婷月| 亚洲毛片欧洲毛片国产一品色| 91精品蜜臀在线一区尤物| 五月婷婷激情视频| 欧美色欧美亚洲高清在线视频| 91精品国产乱码在线观看| 一区二区三区免费| 久草视频在线资源| 一区二区高清视频在线观看| 国产又黄又爽又无遮挡| 亚洲欧美在线aaa| 成人免费毛片xxx| 亚洲视频香蕉人妖| 无码人妻精品一区二区三区夜夜嗨| 欧美国产日产图区| 日韩黄色中文字幕| 国产精品国产三级国产aⅴ原创| 老熟妇一区二区| 亚洲国产高清不卡| 乱老熟女一区二区三区| 中文字幕五月欧美| 久久久久久久久久久久久女过产乱| 亚洲日本欧美天堂| 天天做夜夜爱爱爱| 中文字幕一区二区三区在线播放| 久久久久麻豆v国产| 亚洲欧洲一区二区在线播放| 99鲁鲁精品一区二区三区| 亚洲人成人一区二区在线观看| 欧美日韩免费做爰视频| 亚洲电影一级黄| 国产成人综合欧美精品久久| 日本道精品一区二区三区| 亚洲 小说区 图片区| 欧美老年两性高潮| 亚洲精品国产精品国| 亚洲娇小xxxx欧美娇小| 国产精品久久一区二区三区不卡 | 天天鲁一鲁摸一摸爽一爽| 一区二区三区av电影| 国产成人在线免费观看视频| 色偷偷成人一区二区三区91| 日韩xxx视频| 日韩三级.com| 中文字幕av网站| 91精品欧美福利在线观看 | 亚洲女人天堂色在线7777| 国产高清一区在线观看| 久久影视电视剧免费网站清宫辞电视| 女同一区二区免费aⅴ| 欧美一级高清免费播放| 六九午夜精品视频| 国产精品日韩二区| 国产aⅴ精品一区二区三区久久| 一区在线电影| 亚洲毛片网站| 国产福利在线免费| av资源网一区| 影音先锋男人资源在线观看| 亚洲高清久久久| 在线观看不卡的av| 亚洲精品久久久久中文字幕二区| 91在线视频| 97视频免费在线看| 国产精品久久久久久久久久辛辛 | 91精品在线播放| 日韩伦理一区二区三区| 在线视频福利一区| 亚洲免费综合| 欧美高清精品一区二区| 国产日韩欧美精品综合| 人妻人人澡人人添人人爽| 色屁屁一区二区| 国产91麻豆视频| 日韩视频在线免费观看| 欧美男男激情videos| 国产精品久久久久久久电影| 国产成人tv| 欧美h视频在线观看| 视频一区免费在线观看| 日韩女优在线视频| 亚洲精品免费看| 一级片在线免费观看视频| 日韩精品免费电影| 欧美另类tv| 成人av.网址在线网站| 极品美女一区二区三区| www.浪潮av.com| 盗摄精品av一区二区三区| 国产精品嫩草影院俄罗斯| 欧美日韩一区二区三区高清| 青春有你2免费观看完整版在线播放高清| 欧美成在线视频| 99综合99| 一区二区三区四区五区精品| 久久只有精品| 37p粉嫩大胆色噜噜噜| 亚洲3atv精品一区二区三区| 亚洲av无码乱码国产精品| 另类视频在线观看| 国产亚洲高清一区| 一区中文字幕在线观看| 日韩av在线免费观看不卡| 成人国产在线看| 人禽交欧美网站| 在线观看日本中文字幕| 欧美日韩亚洲91| 亚欧在线观看视频| 97在线观看免费| 色综合久久中文| 男女高潮又爽又黄又无遮挡| 99久久99久久精品免费观看| 日本天堂网在线观看| 欧美精品一区二区久久婷婷| 黄色美女视频在线观看| 国产九色精品| 夜久久久久久| 成人无码www在线看免费| 岛国av一区二区三区| 日韩av资源| 国产精品久久久久免费a∨| 日韩欧美一区二区三区免费看| 91国产精品视频在线观看| 国产精品国产三级国产aⅴ入口 | 黄色一级片在线| 精品国产sm最大网站免费看| 国产美女高潮在线观看| 国产精品视频xxxx| 国产精品久久久久久影院8一贰佰| 成人综合久久网| 一区二区三区日韩| 天天操天天射天天舔| 国产成人av网址| eeuss国产一区二区三区四区| 日本大片免费看| av男人天堂一区| 亚洲第一区av| 欧美日韩成人免费| 日本午夜精品久久久| 日日碰狠狠躁久久躁婷婷| 国产精品视频免费看| 国产欧美一级片| 91禁外国网站| 大片网站久久| av电影中文字幕| 一本一道久久a久久精品| 国产在线观看a视频| 国产日韩欧美一区二区三区四区| 久久国产一二区| 999精品在线视频| 亚洲国产精品久久91精品| 88xx成人永久免费观看| 久久久久久久久影视| 91色综合久久久久婷婷| 91国产免费视频| 久久久噜噜噜久久中文字免| 黑人操亚洲人| 91九色蝌蚪porny| 欧美性猛交xxxx黑人交| 欧美hdxxxx| 一区视频二区视频| 久久欧美一区二区| 亚洲伦理在线观看| 国产视频999| 裸体一区二区|