【架构实战】API接口防刷与限流策略

一、接口防刷概述

接口防刷是保护系统安全的重要手段:

常见攻击:

  • 暴力破解密码
  • 恶意爬虫
  • 刷接口(抽奖、秒杀)
  • CC攻击

二、限流算法

1. 计数器算法

java 复制代码
@Component
public class CounterRateLimiter {
    
    public boolean tryAcquire(String key, int limit, int window) {
        String redisKey = "ratelimit:" + key;
        Long count = redisTemplate.opsForValue().increment(redisKey);
        
        if (count == 1) {
            redisTemplate.expire(redisKey, window, TimeUnit.SECONDS);
        }
        
        return count <= limit;
    }
}

2. 滑动窗口算法

java 复制代码
@Component
public class SlidingWindowRateLimiter {
    
    public boolean tryAcquire(String key, int limit, int window) {
        long now = System.currentTimeMillis();
        String redisKey = "ratelimit:sliding:" + key;
        
        // 移除窗口外的请求
        redisTemplate.opsForZSet().removeRangeByScore(
            redisKey, 0, now - window * 1000);
        
        // 当前请求数
        Long count = redisTemplate.opsForZSet().zCard(redisKey);
        
        if (count >= limit) {
            return false;
        }
        
        // 添加当前请求
        redisTemplate.opsForZSet().add(redisKey, now, now);
        redisTemplate.expire(redisKey, window * 2, TimeUnit.SECONDS);
        
        return true;
    }
}

3. 令牌桶算法

java 复制代码
@Component
public class TokenBucketRateLimiter {
    
    public boolean tryAcquire(String key, int bucketSize, int refillRate) {
        String redisKey = "tokenbucket:" + key;
        
        // 获取令牌
        Long tokens = redisTemplate.opsForValue().decrement(redisKey + ":tokens");
        
        if (tokens == null) {
            // 初始化令牌桶
            redisTemplate.opsForValue().set(redisKey + ":tokens", bucketSize - 1);
            redisTemplate.opsForValue().set(redisKey + ":last", System.currentTimeMillis());
            return true;
        }
        
        if (tokens < 0) {
            // 令牌不足
            return false;
        }
        
        return true;
    }
}

三、分布式限流

1. Lua脚本限流

java 复制代码
@Component
public class LuaRateLimiter {
    
    private static final String SCRIPT = """
        local key = KEYS[1]
        local limit = tonumber(ARGV[1])
        local window = tonumber(ARGV[2])
        
        local current = redis.call('INCR', key)
        
        if current == 1 then
            redis.call('EXPIRE', key, window)
        end
        
        if current > limit then
            return 0
        end
        
        return 1
        """;
    
    public boolean tryAcquire(String key, int limit, int window) {
        DefaultRedisScript<Long> script = new DefaultScript();
        script.setScriptText(SCRIPT);
        
        Long result = redisTemplate.execute(
            script,
            Collections.singletonList(key),
            String.valueOf(limit),
            String.valueOf(window)
        );
        
        return result != null && result == 1;
    }
}

2. Sentinel限流

java 复制代码
@Service
public class SentinelRateLimiter {
    
    @SentinelResource(value = "getOrder", blockHandler = "blockHandler")
    public Order getOrder(Long orderId) {
        return orderService.getById(orderId);
    }
    
    public Order blockHandler(Long orderId, BlockException e) {
        throw new BusinessException("请求过于频繁,请稍后重试");
    }
}

// 配置限流规则
@Configuration
public class SentinelConfig {
    
    @PostConstruct
    public void initRules() {
        List<FlowRule> rules = new ArrayList<>();
        
        FlowRule rule = new FlowRule();
        rule.setResource("getOrder");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(100);  // 每秒100次
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        
        rules.add(rule);
        
        FlowRuleManager.loadRules(rules);
    }
}

四、接口防刷策略

1. 签名验证

java 复制代码
@Component
public class SignValidator {
    
    @Value("${app.secret}")
    private String appSecret;
    
    public boolean validateSign(Map<String, String> params, String sign) {
        // 1. 排序参数
        String sortedParams = params.entrySet().stream()
            .filter(e -> !e.getKey().equals("sign"))
            .sorted(Map.Entry.comparingByKey())
            .map(e -> e.getKey() + "=" + e.getValue())
            .collect(Collectors.joining("&"));
        
        // 2. 拼接密钥
        String signStr = sortedParams + "&key=" + appSecret;
        
        // 3. MD5签名
        String calculatedSign = MD5(signStr).toUpperCase();
        
        return calculatedSign.equals(sign.toUpperCase());
    }
}

2. 时间戳防重放

java 复制代码
@Component
public class TimestampValidator {
    
    private static final long MAX_TIMESTAMP_DIFF = 300000; // 5分钟
    
    public boolean validateTimestamp(long timestamp) {
        long now = System.currentTimeMillis();
        long diff = Math.abs(now - timestamp);
        return diff <= MAX_TIMESTAMP_DIFF;
    }
}

3. 唯一请求ID

java 复制代码
@Component
public class IdempotentChecker {
    
    public boolean check(String requestId) {
        String key = "idempotent:" + requestId;
        Boolean result = redisTemplate.opsForValue()
            .setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
        
        return Boolean.TRUE.equals(result);
    }
}

五、验证码防护

1. 图形验证码

java 复制代码
@Service
public class CaptchaService {
    
    public Captcha generateCaptcha() {
        // 生成随机字符串
        String code = generateRandomCode(4);
        
        // 生成图片
        BufferedImage image = generateImage(code);
        
        // 生成唯一ID
        String captchaId = UUID.randomUUID().toString();
        
        // 存入Redis
        redisTemplate.opsForValue().set(
            "captcha:" + captchaId,
            code.toLowerCase(),
            5,
            TimeUnit.MINUTES
        );
        
        return new Captcha(captchaId, image);
    }
    
    public boolean verify(String captchaId, String code) {
        String cachedCode = (String) redisTemplate.opsForValue()
            .get("captcha:" + captchaId);
        
        if (cachedCode == null) {
            return false;
        }
        
        boolean valid = cachedCode.equalsIgnoreCase(code);
        
        if (valid) {
            redisTemplate.delete("captcha:" + captchaId);
        }
        
        return valid;
    }
}

2. 短信验证码

java 复制代码
@Service
public class SmsCodeService {
    
    public void sendSmsCode(String phone) {
        // 检查发送频率
        String sendKey = "sms:send:" + phone;
        Long sendCount = redisTemplate.opsForValue().increment(sendKey);
        
        if (sendCount != null && sendCount > 5) {
            throw new BusinessException("发送过于频繁,请1小时后再试");
        }
        
        if (sendCount == 1) {
            redisTemplate.expire(sendKey, 1, TimeUnit.HOURS);
        }
        
        // 生成6位验证码
        String code = String.format("%06d", new Random().nextInt(1000000));
        
        // 存入Redis
        String codeKey = "sms:code:" + phone;
        redisTemplate.opsForValue().set(codeKey, code, 5, TimeUnit.MINUTES);
        
        // 发送短信
        smsClient.send(phone, "验证码:" + code);
    }
}

六、IP限流

java 复制代码
@Component
public class IPRateLimiter {
    
    public boolean tryAcquire(String ip, String api, int limit, int window) {
        String key = "ip:ratelimit:" + ip + ":" + api;
        
        Long count = redisTemplate.opsForValue().increment(key);
        
        if (count == 1) {
            redisTemplate.expire(key, window, TimeUnit.SECONDS);
        }
        
        if (count > limit) {
            // 封禁IP
            blockIP(ip);
            return false;
        }
        
        return true;
    }
    
    private void blockIP(String ip) {
        String blockKey = "ip:block:" + ip;
        redisTemplate.opsForValue().set(blockKey, "1", 1, TimeUnit.HOURS);
    }
}

七、总结

接口防刷是系统安全的重要组成:

  • 限流算法:计数器、滑动窗口、令牌桶
  • 分布式限流:Lua脚本、Sentinel
  • 签名验证:防止请求被篡改
  • 验证码:防止机器攻击

最佳实践:

  1. 多层限流(网关+应用+数据库)
  2. 根据业务场景选择限流策略
  3. 做好监控和告警

个人观点,仅供参考

相关推荐
Python私教35 分钟前
Codex 写出的代码能跑却算错钱:我用 3 个测试拆穿一次 AI 编程幻觉
python·单元测试·ai编程
Python私教1 小时前
我只写了一个 add 工具,终于把 MCP 的 Host、Client、Server 跑明白了
python·ai编程·mcp
qq_454245031 小时前
认知自举:LLM自我指令泛化的三层逻辑
人工智能·架构·prompt
霸道流氓气质1 小时前
分布式系统中接口时序不确定性处理
java·开发语言·分布式
XUHUOJUN1 小时前
Azure Stack Hub 存储服务:托管磁盘、Blob/Table/Queue 与容量管理(下篇)
架构·azure stack
库克克1 小时前
【C++】C++11 包装器function 与 绑定器 bind
开发语言·c++
多加点辣也没关系1 小时前
JavaScript|第30章:事件机制
开发语言·javascript
小大宇1 小时前
python milvus 案例
开发语言·python·milvus
Gauss松鼠会2 小时前
【GaussDB】GaussDB锁阻塞源头查询
java·开发语言·前端·数据库·算法·gaussdb·经验总结
XUHUOJUN2 小时前
Azure Stack Hub 存储服务:从 S2D 物理栈到租户服务全景(上篇)
架构·azure stack