SpringBoot 实现接口防刷的 5 种实现方案

接口防刷是保障系统安全与稳定性的重要措施,恶意高频请求可能导致服务器资源耗尽或数据异常。本文介绍SpringBoot中5种接口防刷方案,涵盖简单场景到复杂分布式系统的防护需求。

1. 基于注解的访问频率限制

核心思路:通过自定义注解和AOP切面,结合Redis实现请求频率控制。

实现步骤

1.1 创建限流注解@RateLimit

@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface RateLimit {  
    int time() default 60;        // 限制时间段(秒)  
    int count() default 10;       // 时间段内最大请求次数  
    String key() default "";      // 限流Key(支持SpEL表达式)  
    String message() default 操作太频繁,请稍后再试;  
}  

1.2 实现限流切面RateLimitAspect

@Aspect  
@Component  
public class RateLimitAspect {  
    @Autowired private StringRedisTemplate redisTemplate;  

    @Around(@annotation(rateLimit))  
    public Object around(ProceedingJoinPoint pjp, RateLimit rateLimit) {  
        String limitKey = 生成限流Key(pjp, rateLimit);  
        int time = rateLimit.time();  
        int count = rateLimit.count();  
        if (isLimited(limitKey, time, count)) {  
            throw new RuntimeException(rateLimit.message());  
        }  
        return pjp.proceed();  
    }  

    private boolean isLimited(String key, int time, int count) {  
        Long currentCount = redisTemplate.opsForValue().increment(key);  
        if (currentCount == 1) redisTemplate.expire(key, time, TimeUnit.SECONDS);  
        return currentCount > count;  
    }  
}  

1.3 使用示例

@RestController  
public class UserController {  
    @RateLimit(time = 60, count = 3)  
    @GetMapping(/api/user/{id})  
    public User getUser(@PathVariable Long id) { /* ... */ }  

    @RateLimit(key = #id + _ + #request.remoteAddr)  
    @PostMapping(/api/user/{id}/update)  
    public Result updateUser(...) { /* ... */ }  
}  

优缺点

  • 优点:实现简单、无侵入性、支持接口粒度控制。
  • 缺点:逻辑简单、无预警机制,分布式需依赖Redis。

2. 令牌桶算法实现限流

核心思路:基于Guava的令牌桶算法,允许突发流量但限制平均速率。

实现步骤

2.1 引入Guava依赖

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

2.2 创建令牌桶管理器

@Component  
public class RateLimiter {  
    private final ConcurrentHashMap<String, com.google.common.util.concurrent.RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();  

    public boolean tryAcquire(String key, double permitsPerSecond) {  
        return rateLimiterMap.computeIfAbsent(key, k -> RateLimiter.create(permitsPerSecond)).tryAcquire();  
    }  
}  

2.3 实现拦截器

@Component  
public class TokenBucketInterceptor implements HandlerInterceptor {  
    @Autowired private RateLimiter rateLimiter;  

    @Override  
    public boolean preHandle(HttpServletRequest request, ...) {  
        String key = ip + requestURI;  
        if (!rateLimiter.tryAcquire(key, 2.0)) {  
            response.setStatus(429);  
            response.getWriter().write({code:429,message:请求过于频繁});  
            return false;  
        }  
        return true;  
    }  
}  

优缺点

  • 优点:支持突发流量、单机性能好、配置灵活。
  • 缺点:仅适用于单机、分布式需改造、状态易丢失。

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

核心思路:利用Redis存储请求记录,通过Lua脚本实现原子性计数和滑动窗口。

实现步骤

3.1 定义Lua脚本rate_limiter.lua

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)  
local count = redis.call('ZCARD', key)  
if count >= threshold then return 0 end  
redis.call('ZADD', key, now, now..'-'..math.random())  
redis.call('EXPIRE', key, window)  
return threshold - count - 1  

3.2 创建Redis限流服务

@Service  
public class RedisRateLimiterService {  
    @Autowired private StringRedisTemplate redisTemplate;  
    private DefaultRedisScript<Long> rateLimiterScript;  

    public long isAllowed(String key, int window, int threshold) {  
        return redisTemplate.execute(rateLimiterScript, Collections.singletonList(key),  
            String.valueOf(window), String.valueOf(threshold), String.valueOf(System.currentTimeMillis()));  
    }  
}  

3.3 使用示例

@DistributedRateLimit(mode = user, threshold = 5)  
@PostMapping(/api/payment)  
public Result createPayment(...) { /* ... */ }  

优缺点

  • 优点:支持分布式、精确计数、原子性强。
  • 缺点:依赖Redis、实现复杂、需维护Lua脚本。

4. 集成Sentinel实现接口防刷

核心思路:使用阿里巴巴Sentinel组件,提供QPS限流、熔断、热点参数防护等功能。

实现步骤

4.1 添加依赖

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

4.2 配置流控规则

@Configuration  
public class SentinelConfig {  
    @PostConstruct  
    public void initFlowRules() {  
        FlowRule rule = new FlowRule();  
        rule.setResource(/api/user);  
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);  
        rule.setCount(10); // 每秒10请求  
        FlowRuleManager.loadRules(Arrays.asList(rule));  
    }  
}  

4.3 异常处理

@RestControllerAdvice  
public class SentinelExceptionHandler {  
    @ExceptionHandler(BlockException.class)  
    public Result handleBlockException(BlockException e) {  
        return Result.error(429, 请求过于频繁,请稍后再试);  
    }  
}  

优缺点

  • 优点:功能全面、支持动态规则、可视化管理。
  • 缺点:学习成本高、分布式需配置规则持久化。

5. 验证码与行为分析防刷

核心思路:对敏感操作(如登录)要求验证码,并通过行为分析识别机器人。

实现步骤

5.1 生成与验证验证码

@Service  
public class CaptchaService {  
    public String generateCaptcha(...) {  
        SpecCaptcha captcha = new SpecCaptcha(130, 48, 5);  
        redisTemplate.opsForValue().set(captchaId, captcha.text(), 5, TimeUnit.MINUTES);  
        return captcha.toBase64();  
    }  

    public boolean validateCaptcha(...) {  
        String correctCode = redisTemplate.opsForValue().get(captchaId);  
        return correctCode != null && correctCode.equals(captchaCode);  
    }  
}  

5.2 行为分析拦截器

@Component  
public class BehaviorAnalysisInterceptor {  
    @Override  
    public boolean preHandle(HttpServletRequest request, ...) {  
        String ip = getIpAddress(request);  
        Long count = redisTemplate.opsForValue().increment(behavior:freq:+ip);  
        if (count > 30) {  
            response.setStatus(429);  
            return false;  
        }  
        return true;  
    }  
}  

优缺点

  • 优点:有效区分人类与机器人、防护强度高。
  • 缺点:增加用户成本、可能被OCR破解、需前后端配合。

方案对比与选择

方案实现难度防刷效果分布式支持用户体验适用场景
基于注解的访问频率限制需Redis一般简单接口、单机场景
令牌桶算法中高单机允许突发流量的单机服务
分布式限流(Redis+Lua)支持一般分布式系统、精确限流
集成Sentinel中高需配置可配置复杂系统、多维度防护
验证码与行为分析支持较差敏感操作、关键业务

总结

接口防刷需平衡安全性与用户体验,建议:

  • 简单场景:使用基于注解的限流或令牌桶算法。
  • 分布式系统:选择Redis+Lua或Sentinel。
  • 敏感操作:结合验证码与行为分析增强防护。
    实施时应遵循最小影响、梯度防护原则,并确保监控与动态调整能力。

发表评论