一、接口防刷概述
接口防刷是保护系统安全的重要手段:
常见攻击:
- 暴力破解密码
- 恶意爬虫
- 刷接口(抽奖、秒杀)
- 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
- 签名验证:防止请求被篡改
- 验证码:防止机器攻击
最佳实践:
- 多层限流(网关+应用+数据库)
- 根据业务场景选择限流策略
- 做好监控和告警
个人观点,仅供参考