欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot實現(xiàn)接口防刷的五種方案

 更新時間:2025年04月10日 08:33:12   作者:風(fēng)象南  
接口防刷是保障系統(tǒng)安全與穩(wěn)定性的重要措施,惡意的高頻請求不僅會消耗服務(wù)器資源,還可能導(dǎo)致數(shù)據(jù)異常,甚至系統(tǒng)癱瘓,本文將介紹在SpringBoot框架下實現(xiàn)接口防刷的5種技術(shù)方案,需要的朋友可以參考下

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

最常見的防刷方案是通過自定義注解和AOP切面實現(xiàn)訪問頻率限制。這種方法簡單易用,實現(xiàn)成本低。

實現(xiàn)步驟

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

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
    /**
     * 限制時間段,單位為秒
     */
    int time() default 60;
    
    /**
     * 在限制時間段內(nèi)允許的最大請求次數(shù)
     */
    int count() default 10;
    
    /**
     * 限流的key,支持SpEL表達式
     */
    String key() default "";
    
    /**
     * 提示信息
     */
    String message() default "操作太頻繁,請稍后再試";
}

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

@Aspect
@Component
@Slf4j
public class RateLimitAspect {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
        // 獲取請求的方法名
        String methodName = pjp.getSignature().getName();
        // 獲取請求的類名
        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) {
            throw new RuntimeException(rateLimit.message());
        }
        
        // 執(zhí)行目標方法
        return pjp.proceed();
    }
    
    private String getLimitKey(ProceedingJoinPoint pjp, RateLimit rateLimit, String methodName, String className) {
        // 獲取用戶自定義的key
        String key = rateLimit.key();
        
        if (StringUtils.hasText(key)) {
            // 支持SpEL表達式解析
            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 {
            // 默認使用類名+方法名+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的計數(shù)器實現(xiàn)限流
        try {
            Long currentCount = redisTemplate.opsForValue().increment(key, 1);
            
            // 如果是第一次訪問,設(shè)置過期時間
            if (currentCount == 1) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            
            return currentCount > count;
        } catch (Exception e) {
            log.error("限流異常", e);
            return false;
        }
    }
    
    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;
    }
}

1.3 使用示例

@RestController
@RequestMapping("/api")
public class UserController {
    
    @RateLimit(time = 60, count = 3, message = "請求太頻繁,請稍后再試")
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUser(id);
    }
    
    // 使用SpEL表達式指定key
    @RateLimit(time = 60, count = 1, key = "#id + '_' + #request.remoteAddr")
    @PostMapping("/user/{id}/update")
    public Result updateUser(@PathVariable Long id, @RequestBody UserDTO userDTO, HttpServletRequest request) {
        return userService.updateUser(id, userDTO);
    }
}

優(yōu)缺點分析

優(yōu)點:

  • 實現(xiàn)簡單,上手容易,單機情況下可以去掉Redis換成本地緩存實現(xiàn)
  • 注解式使用,對業(yè)務(wù)代碼無侵入
  • 可以精確控制接口粒度
  • 支持靈活的限流策略配置

缺點:

  • 限流邏輯相對簡單,無法應(yīng)對復(fù)雜場景
  • 缺少預(yù)警機制

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

令牌桶算法是一種更加靈活的限流算法,可以允許突發(fā)流量,同時又能限制長期的平均流量。

實現(xiàn)步驟

2.1 引入依賴

Google提供的Guava庫中包含了令牌桶實現(xiàn):

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

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

@Component
public class RateLimiter {
    // 使用ConcurrentHashMap存儲不同接口的令牌桶
    private final ConcurrentHashMap<String, com.google.common.util.concurrent.RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
    
    /**
     * 獲取特定接口的令牌桶,不存在則創(chuàng)建
     * @param key 限流鍵
     * @param permitsPerSecond 每秒允許的請求量
     * @return 令牌桶實例
     */
    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 每秒允許的請求量
     * @param timeout 超時時間
     * @param unit 時間單位
     * @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);
    }
}

2.3 創(chuàng)建攔截器

@Component
public class TokenBucketInterceptor implements HandlerInterceptor {
    
    @Autowired
    private RateLimiter rateLimiter;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 僅對API請求進行限流
        String requestURI = request.getRequestURI();
        if (!requestURI.startsWith("/api/")) {
            return true;
        }
        
        // 獲取IP地址作為限流鍵
        String ip = getIpAddress(request);
        String key = ip + ":" + requestURI;
        
        // 嘗試獲取令牌,設(shè)置每秒2個請求的速率,等待100毫秒
        boolean acquired = rateLimiter.tryAcquire(key, 2.0, 100, TimeUnit.MILLISECONDS);
        
        if (!acquired) {
            // 獲取失敗,返回限流響應(yīng)
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.getWriter().write("{"code":429,"message":"請求過于頻繁,請稍后再試"}");
            return false;
        }
        
        return true;
    }
    
    // getIpAddress方法同上
}

2.4 配置攔截器

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

優(yōu)缺點分析

優(yōu)點:

  • 支持突發(fā)流量,不會完全拒絕短時高峰
  • 平滑的限流效果,用戶體驗更好
  • 可以配置不同接口的不同限流策略
  • 無需額外的存儲設(shè)施

缺點:

  • 只適用于單機部署,分布式環(huán)境需要額外改造
  • 重啟應(yīng)用后狀態(tài)丟失
  • 無法精確控制時間窗口內(nèi)的請求總量

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

對于分布式系統(tǒng),單機限流方案難以滿足需求。利用Redis和Lua腳本可以實現(xiàn)高效的分布式限流。

實現(xiàn)步驟

3.1 定義Lua腳本

創(chuàng)建一個Redis限流的Lua腳本,放在resources目錄下的scripts/rate_limiter.lua

-- 限流Key
local key = KEYS[1]
-- 限流窗口,單位秒
local window = tonumber(ARGV[1])
-- 限流閾值
local threshold = tonumber(ARGV[2])
-- 當前時間戳
local now = tonumber(ARGV[3])

-- 移除過期的請求記錄
redis.call('ZREMRANGEBYSCORE', key, 0, now - window * 1000)

-- 獲取當前窗口內(nèi)的請求數(shù)
local count = redis.call('ZCARD', key)

-- 如果請求數(shù)超過閾值,拒絕請求
if count >= threshold then
    return 0
end

-- 添加當前請求記錄
redis.call('ZADD', key, now, now .. '-' .. math.random())
-- 設(shè)置過期時間
redis.call('EXPIRE', key, window)

-- 返回當前窗口剩余可用請求數(shù)
return threshold - count - 1

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

@Service
@Slf4j
public class 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 時間窗口(秒)
     * @param threshold 閾值
     * @return 剩余可用請求數(shù),-1表示被限流
     */
    public long isAllowed(String key, int window, int threshold) {
        try {
            // 執(zhí)行l(wèi)ua腳本
            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ā)生異常時放行請求
            return threshold;
        }
    }
}

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

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedRateLimit {
    /**
     * 限流的key前綴
     */
    String prefix() default "rate:";
    
    /**
     * 時間窗口,單位秒
     */
    int window() default 60;
    
    /**
     * 在時間窗口內(nèi)允許的最大請求數(shù)
     */
    int threshold() default 10;
    
    /**
     * 限流模式: ip - 按IP限流, user - 按用戶限流, all - 接口總體限流
     */
    String mode() default "ip";
}

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

@Aspect
@Component
@Slf4j
public class 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) {
            throw new RuntimeException("接口訪問過于頻繁,請稍后再試");
        }
        
        // 執(zhí)行目標方法
        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的代碼同上一個示例
        return "127.0.0.1"; // 簡化處理
    }
    
    // 獲取當前用戶ID,根據(jù)實際認證系統(tǒng)實現(xiàn)
    private Object getUserId() {
        // 這里簡化處理,實際中應(yīng)從認證信息中獲取
        // 例如:SecurityContextHolder.getContext().getAuthentication().getPrincipal()
        return null;
    }
}

3.5 使用示例

@RestController
@RequestMapping("/api")
public class 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();
    }
}

優(yōu)缺點分析

優(yōu)點:

  • 適用于分布式系統(tǒng),多實例間共享限流狀態(tài)
  • 支持多種限流模式:按IP、用戶、接口總量等
  • 基于滑動窗口,計數(shù)更精確
  • 使用Lua腳本保證原子性,避免競態(tài)條件

缺點:

  • 強依賴Redis
  • 實現(xiàn)復(fù)雜度較高

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

阿里巴巴開源的Sentinel是一個強大的流量控制組件,提供了豐富的限流、熔斷、系統(tǒng)保護等功能。

實現(xiàn)步驟

4.1 添加依賴

<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 控制臺地址
spring.cloud.sentinel.transport.dashboard=localhost:8080
# 取消Sentinel控制臺懶加載
spring.cloud.sentinel.eager=true
# 應(yīng)用名稱
spring.application.name=my-application

4.3 創(chuàng)建Sentinel配置

@Configuration
public class SentinelConfig {
    
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
    
    @PostConstruct
    public void init() {
        // 定義流控規(guī)則
        initFlowRules();
    }
    
    private void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        
        // 為/api/user接口設(shè)置流控規(guī)則
        FlowRule userRule = new FlowRule();
        userRule.setResource("/api/user");
        userRule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 基于QPS限流
        userRule.setCount(10); // 每秒允許10個請求
        rules.add(userRule);
        
        // 為/api/order接口設(shè)置流控規(guī)則
        FlowRule orderRule = new FlowRule();
        orderRule.setResource("/api/order");
        orderRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        orderRule.setCount(5); // 每秒允許5個請求
        orderRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); // 預(yù)熱模式
        orderRule.setWarmUpPeriodSec(10); // 10秒預(yù)熱期
        rules.add(orderRule);
        
        // 加載規(guī)則
        FlowRuleManager.loadRules(rules);
    }
}

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

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

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

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

4.6 使用@SentinelResource注解

@RestController
@RequestMapping("/api")
public class 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);
        throw new RuntimeException("請求頻率過高,請稍后再試");
    }
    
    // 異?;赝朔椒?
    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;
    }
}

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

@Service
@Slf4j
public class 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"); // 當important接口QPS高時,限制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個線程同時處理
        rules.add(threadRule);
        
        // 加載規(guī)則
        FlowRuleManager.loadRules(rules);
    }
    
    public void initHotspotRules() {
        // 熱點參數(shù)限流規(guī)則
        List<ParamFlowRule> rules = new ArrayList<>();
        
        ParamFlowRule rule = new ParamFlowRule("/api/product");
        // 對第0個參數(shù)(productId)進行限流
        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);   // 更嚴格的限制
        
        rule.setParamFlowItemList(Arrays.asList(item1, item2));
        
        rules.add(rule);
        ParamFlowRuleManager.loadRules(rules);
    }
}

優(yōu)缺點分析

優(yōu)點:

  • 功能全面,支持QPS限流、并發(fā)線程數(shù)限流、熱點參數(shù)限流等
  • 支持多種控制策略:直接拒絕、預(yù)熱、排隊等
  • 提供控制臺可視化管理
  • 支持動態(tài)規(guī)則調(diào)整
  • 可與Spring Cloud體系無縫集成

缺點:

  • 學(xué)習(xí)曲線較陡峭
  • 分布式場景下需要額外配置規(guī)則持久化
  • 引入了額外的依賴

5. 驗證碼與行為分析防刷

對于某些敏感操作(如登錄、注冊、支付等),可以結(jié)合驗證碼和行為分析來防止惡意請求。

實現(xiàn)步驟

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

首先添加依賴:

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

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

@Service
public class CaptchaService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final long CAPTCHA_EXPIRE_TIME = 5 * 60; // 5分鐘
    
    /**
     * 生成驗證碼
     * @param request HTTP請求
     * @param response HTTP響應(yīng)
     * @return 驗證碼Base64字符串
     */
    public String generateCaptcha(HttpServletRequest request, HttpServletResponse response) {
        // 生成驗證碼
        SpecCaptcha captcha = new SpecCaptcha(130, 48, 5);
        
        // 生成驗證碼ID
        String captchaId = UUID.randomUUID().toString();
        
        // 將驗證碼存入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編碼的驗證碼圖片
        return captcha.toBase64();
    }
    
    /**
     * 驗證驗證碼
     * @param request HTTP請求
     * @param captchaCode 用戶輸入的驗證碼
     * @return 是否驗證通過
     */
    public boolean validateCaptcha(HttpServletRequest request, String captchaCode) {
        // 從Cookie獲取驗證碼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) {
            return false;
        }
        
        // 從Redis獲取正確的驗證碼
        String key = "captcha:" + captchaId;
        String correctCode = redisTemplate.opsForValue().get(key);
        
        // 驗證成功后刪除驗證碼
        if (correctCode != null && correctCode.equals(captchaCode.toLowerCase())) {
            redisTemplate.delete(key);
            return true;
        }
        
        return false;
    }
}

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

@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);
    }
}

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

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

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

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

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

@Service
@Slf4j
public class BehaviorAnalysisService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 檢查是否是可疑的機器行為
     * @param request HTTP請求
     * @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);
            return true;
        }
        
        // 3. 檢查User-Agent
        if (userAgent == null || isBotuserAgent(userAgent)) {
            log.warn("可疑的User-Agent: {}", userAgent);
            return true;
        }
        
        // 4. 檢查請求時間模式
        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;
            
            // 如果請求間隔非常均勻,可能是機器人
            if (isUniformInterval(ip, interval)) {
                log.warn("請求間隔異常均勻: IP={}, interval={}", ip, interval);
                return true;
            }
        }
        
        redisTemplate.opsForValue().set(timeKey, String.valueOf(now), 10, TimeUnit.MINUTES);
        
        // 更多高級檢測邏輯...
        
        return false;
    }
    
    /**
     * 檢查是否是機器人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;
    }
    
    /**
     * 檢查請求間隔是否異常均勻
     */
    private boolean isUniformInterval(String ip, long interval) {
        String key = "behavior:intervals:" + ip;
        
        // 獲取最近的幾個間隔
        List<String> intervalStrs = redisTemplate.opsForList().range(key, 0, 4);
        redisTemplate.opsForList().leftPush(key, String.valueOf(interval));
        redisTemplate.opsForList().trim(key, 0, 9);  // 只保留最近10個
        redisTemplate.expire(key, 10, TimeUnit.MINUTES);
        
        if (intervalStrs == null || intervalStrs.size() < 5) {
            return false;
        }
        
        // 計算間隔的方差,方差小說明請求間隔很均勻
        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ù)實際情況調(diào)整
    }
    
    // getIpAddress方法同上
}

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

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

5.8 使用示例

@RestController
@RequestMapping("/api")
public class 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) {
        // 注冊邏輯
        return userService.register(registerDTO);
    }
}

優(yōu)缺點分析

優(yōu)點:

  • 能有效區(qū)分人類用戶和自動化腳本
  • 對惡意用戶有較強的阻止作用
  • 針對敏感操作提供額外安全層
  • 可以實現(xiàn)自適應(yīng)安全策略

缺點:

  • 增加了用戶操作成本,可能影響用戶體驗
  • 實現(xiàn)復(fù)雜,需要前后端配合
  • 某些驗證碼可能被OCR技術(shù)破解
  • 行為分析可能產(chǎn)生誤判

方案對比與選擇

方案實現(xiàn)難度防刷效果分布式支持用戶體驗適用場景
基于注解的訪問頻率限制需配合Redis一般一般接口,簡單場景
令牌桶算法中高單機允許突發(fā)流量的場景
分布式限流(Redis+Lua)支持一般分布式系統(tǒng),精確限流
Sentinel中高需額外配置可配置復(fù)雜系統(tǒng),多維度防護
驗證碼與行為分析支持較差敏感操作,關(guān)鍵業(yè)務(wù)

總結(jié)

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

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

  • 最小影響原則:盡量不影響正常用戶的體驗
  • 梯度防護原則:根據(jù)接口的重要程度采用不同強度的防護措施
  • 可監(jiān)控原則:提供充分的監(jiān)控和告警機制
  • 靈活調(diào)整原則:支持動態(tài)調(diào)整防護參數(shù)和策略

通過合理實施接口防刷策略,可以有效提高系統(tǒng)的安全性和穩(wěn)定性,為用戶提供更好的服務(wù)體驗。

以上就是SpringBoot實現(xiàn)接口防刷的五種方案的詳細內(nèi)容,更多關(guān)于SpringBoot接口防刷的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java?Stream?API詳解與使用示例詳解

    Java?Stream?API詳解與使用示例詳解

    Java?Stream?API?是一個功能強大的工具,適用于處理集合和數(shù)據(jù)流,本文全面介紹了?Java?Stream?API?的概念、功能以及如何在?Java?中有效地使用它進行集合和數(shù)據(jù)流的處理,感興趣的朋友跟隨小編一起看看吧
    2024-05-05
  • java 中內(nèi)部類的實例詳解

    java 中內(nèi)部類的實例詳解

    這篇文章主要介紹了java 中內(nèi)部類的實例詳解的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-09-09
  • Java利用Redis實現(xiàn)高并發(fā)計數(shù)器的示例代碼

    Java利用Redis實現(xiàn)高并發(fā)計數(shù)器的示例代碼

    這篇文章主要介紹了Java利用Redis實現(xiàn)高并發(fā)計數(shù)器的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • Java中final作用于變量、參數(shù)、方法及類該如何處理

    Java中final作用于變量、參數(shù)、方法及類該如何處理

    Java中的final關(guān)鍵字非常重要,它可以應(yīng)用于類、方法以及變量,下面這篇文章主要給大家介紹了關(guān)于Java中final作用于變量、參數(shù)、方法及類該如何處理的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下。
    2017-12-12
  • Java編程異常簡單代碼示例

    Java編程異常簡單代碼示例

    這篇文章主要介紹了Java編程異常簡單代碼示例,還是比較不錯的,這里分享個大家,需要的朋友可以參考下。
    2017-11-11
  • SpringBoot導(dǎo)出Excel的四種方式小結(jié)

    SpringBoot導(dǎo)出Excel的四種方式小結(jié)

    近期接到了一個小需求,要將系統(tǒng)中的數(shù)據(jù)導(dǎo)出為Excel,且能將Excel數(shù)據(jù)導(dǎo)入到系統(tǒng),對于大多數(shù)研發(fā)人員來說,這算是一個最基本的操作了,本文就給大家總結(jié)一下SpringBoot導(dǎo)出Excel的四種實現(xiàn)方式,需要的朋友可以參考下
    2024-12-12
  • 解讀@RabbitListener起作用的原理

    解讀@RabbitListener起作用的原理

    這篇文章主要介紹了解讀@RabbitListener起作用的原理,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • MyBatis配置與CRUD超詳細講解

    MyBatis配置與CRUD超詳細講解

    這篇文章主要介紹了MyBatis配置與CRUD,CRUD是指在做計算處理時的增加(Create)、讀取(Read)、更新(Update)和刪除(Delete)幾個單詞的首字母簡寫。CRUD主要被用在描述軟件系統(tǒng)中數(shù)據(jù)庫或者持久層的基本操作功能
    2023-02-02
  • SpringCloud微服務(wù)架構(gòu)實戰(zhàn)之微服務(wù)治理功能的實現(xiàn)

    SpringCloud微服務(wù)架構(gòu)實戰(zhàn)之微服務(wù)治理功能的實現(xiàn)

    這篇文章主要介紹了SpringCloud微服務(wù)架構(gòu)實戰(zhàn)之微服務(wù)治理,這些治理工具主要包括服務(wù)的注冊與發(fā)現(xiàn)、負載均衡管理、動態(tài)路由、服務(wù)降級和故障轉(zhuǎn)移、鏈路跟蹤、服務(wù)監(jiān)控等,需要的朋友可以參考下
    2022-02-02
  • jsp頁面中獲取servlet請求中的參數(shù)的辦法詳解

    jsp頁面中獲取servlet請求中的參數(shù)的辦法詳解

    在JAVA WEB應(yīng)用中,如何獲取servlet請求中的參數(shù),本文講解了jsp頁面中獲取servlet請求中的參數(shù)的辦法
    2018-03-03

最新評論