SpringBoot + 接口防刷 + 滑动窗口计数:登录、短信、支付接口防暴力攻击
引言
在互联网应用中,接口安全是一个永恒的话题。你有没有遇到过这种情况:用户疯狂点击登录按钮导致服务器压力过大,或者恶意刷短信验证码造成成本损失?这些问题的根源就是缺乏有效的接口防刷机制。
今天就来聊聊如何用SpringBoot结合Redis实现滑动窗口计数算法,为登录、短信、支付等关键接口建立坚固的防护墙,让你的系统在面对暴力攻击时依然稳如泰山。
为什么需要接口防刷?
接口暴力攻击的危害
让我们先看看没有防刷机制的系统面临什么风险:
服务器资源浪费:
- 恶意用户不断发起请求,消耗大量CPU和内存
- 数据库连接池被占满,影响正常用户访问
- 网络带宽被恶意请求占用
业务成本增加:
- 短信验证码被大量刷取,产生巨额费用
- 第三方API调用次数超限,影响业务正常运行
- 服务器扩容成本增加
用户体验下降:
- 正常用户的请求被恶意请求挤占
- 系统响应变慢,甚至出现服务不可用
- 影响业务正常运营
数据安全风险:
- 暴力破解密码尝试
- 恶意刷取优惠券或积分
- 爬虫批量抓取敏感数据
滑动窗口计数的优势
精准控制:
- 精确统计任意时间窗口内的请求次数
- 避免固定窗口算法的边界问题
- 实时响应请求频率变化
资源友好:
- 只保留时间窗口内的数据
- 自动清理过期请求记录
- 内存使用可控
灵活性强:
- 可根据不同接口设置不同限制
- 支持动态调整限流参数
- 适应各种业务场景需求
核心架构设计
我们的滑动窗口计数防刷架构:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ 用户请求 │───▶│ 拦截器/切面 │───▶│ Redis存储 │
│ (Controller) │ │ (RateLimiter) │ │ (ZSET结构) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
│ 发起请求 │ │
│───────────────────────▶│ │
│ │ 检查请求频率 │
│ │──────────────────────▶│
│ │ │
│ │ 查询ZSET中时间段内 │
│ │──────────────────────▶│
│ │ │
│ │ 统计请求数量 │
│ │──────────────────────▶│
│ │ │
│ │ 判断是否超限 │
│ │──────────────────────▶│
│ │ │
│ 请求成功或拒绝 │ │
│◀───────────────────────│ │
│ │ │
核心设计要点
1. 滑动窗口计数算法
// 滑动窗口计数器服务
@Service
@Slf4j
public class SlidingWindowCounter {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 检查指定用户在时间窗口内的请求次数
*
* @param key 限流标识(如:用户ID、IP地址)
* @param windowMs 时间窗口(毫秒)
* @param maxCount 最大请求数
* @return true表示允许请求,false表示超出限制
*/
public boolean isAllowed(String key, long windowMs, int maxCount) {
try {
// 当前时间戳
long currentTime = System.currentTimeMillis();
// 窗口开始时间
long windowStart = currentTime - windowMs;
// 使用Redis ZSET存储请求记录,score为时间戳,value为唯一标识
String redisKey = "rate_limit:" + key;
// 删除窗口之前的旧记录
redisTemplate.opsForZSet().removeRangeByScore(redisKey, 0, windowStart);
// 获取当前窗口内的请求数
Long currentCount = redisTemplate.opsForZSet().count(redisKey, windowStart, currentTime);
if (currentCount != null && currentCount >= maxCount) {
log.warn("请求被限制: key={}, windowMs={}, maxCount={}, currentCount={}",
key, windowMs, maxCount, currentCount);
return false;
}
// 添加当前请求记录
redisTemplate.opsForZSet().add(redisKey, generateUniqueValue(), currentTime);
// 设置过期时间,避免key永久存在
redisTemplate.expire(redisKey, Duration.ofMillis(windowMs + 60000)); // 多保留1分钟
log.debug("请求通过: key={}, currentCount={}", key, (currentCount != null ? currentCount + 1 : 1));
return true;
} catch (Exception e) {
log.error("滑动窗口计数检查异常", e);
// 发生异常时,默认允许请求,避免影响正常业务
return true;
}
}
/**
* 使用Lua脚本原子性执行滑动窗口检查
* 这样可以避免多次Redis网络往返,提高性能
*/
public boolean isAllowedAtomic(String key, long windowMs, int maxCount) {
String script =
"local key = KEYS[1] " +
"local window_start = ARGV[1] " +
"local current_time = ARGV[2] " +
"local max_count = ARGV[3] " +
// 删除过期数据
"redis.call('ZREMRANGEBYSCORE', key, 0, window_start) " +
// 统计当前窗口内请求数
"local current_count = redis.call('ZCOUNT', key, window_start, current_time) " +
// 检查是否超过限制
"if tonumber(current_count) >= tonumber(max_count) then " +
"return 0 " + // 超限
"else " +
"redis.call('ZADD', key, current_time, current_time .. ':' .. math.random(1000000)) " +
"redis.call('EXPIRE', key, 60) " + // 设置过期时间
"return 1 " + // 允许
"end";
try {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(redisScript,
Collections.singletonList("rate_limit:" + key),
String.valueOf(System.currentTimeMillis() - windowMs),
String.valueOf(System.currentTimeMillis()),
String.valueOf(maxCount)
);
boolean allowed = result != null && result == 1L;
if (!allowed) {
log.warn("请求被限制: key={}, windowMs={}, maxCount={}", key, windowMs, maxCount);
}
return allowed;
} catch (Exception e) {
log.error("原子性滑动窗口计数检查异常", e);
return true; // 异常时默认允许
}
}
/**
* 生成唯一值,用于区分同一时刻的不同请求
*/
private String generateUniqueValue() {
return System.currentTimeMillis() + ":" + ThreadLocalRandom.current().nextLong(1000000);
}
/**
* 获取当前窗口内的请求数
*/
public long getCurrentCount(String key, long windowMs) {
try {
long currentTime = System.currentTimeMillis();
long windowStart = currentTime - windowMs;
String redisKey = "rate_limit:" + key;
Long count = redisTemplate.opsForZSet().count(redisKey, windowStart, currentTime);
return count != null ? count : 0L;
} catch (Exception e) {
log.error("获取当前请求数异常", e);
return 0L;
}
}
}
2. 限流注解定义
// 限流注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
/**
* 限流的key,支持SpEL表达式
*/
String key() default "";
/**
* 时间窗口大小(毫秒)
*/
long window() default 60000; // 默认1分钟
/**
* 时间窗口内最大请求数
*/
int count() default 10;
/**
* 超限时的提示信息
*/
String message() default "请求过于频繁,请稍后再试";
/**
* 限流类型
*/
LimitType limitType() default LimitType.DEFAULT;
enum LimitType {
/**
* 默认策略,使用方法名+参数作为key
*/
DEFAULT,
/**
* 使用IP作为key
*/
IP,
/**
* 使用用户ID作为key
*/
USER
}
}
// 限流异常
public class RateLimitException extends RuntimeException {
public RateLimitException(String message) {
super(message);
}
}
3. 限流切面实现
// 限流切面
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
@Autowired
private SlidingWindowCounter slidingWindowCounter;
@Autowired
private HttpServletRequest request;
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
// 生成限流key
String key = generateKey(joinPoint, rateLimit);
// 检查是否允许请求
boolean allowed = slidingWindowCounter.isAllowedAtomic(key, rateLimit.window(), rateLimit.count());
if (!allowed) {
log.warn("接口访问被限流: key={}, method={}", key, joinPoint.getSignature().getName());
throw new RateLimitException(rateLimit.message());
}
// 继续执行原方法
return joinPoint.proceed();
}
/**
* 生成限流key
*/
private String generateKey(ProceedingJoinPoint joinPoint, RateLimit rateLimit) {
String key = rateLimit.key();
if (StringUtils.hasText(key)) {
// 如果指定了key,使用SpEL解析
EvaluationContext context = new StandardEvaluationContext();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
context.setVariable("arg" + i, args[i]);
}
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(key);
return expression.getValue(context, String.class);
}
// 根据限流类型生成key
switch (rateLimit.limitType()) {
case IP:
return getClientIp() + ":" + joinPoint.getSignature().getName();
case USER:
// 从SecurityContext或请求头中获取用户ID
String userId = getCurrentUserId();
return StringUtils.hasText(userId) ? userId : "anonymous";
case DEFAULT:
default:
// 使用方法名作为key
return joinPoint.getTarget().getClass().getSimpleName() +
"." + joinPoint.getSignature().getName();
}
}
/**
* 获取客户端IP地址
*/
private String getClientIp() {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (StringUtils.hasText(xForwardedFor) && !xForwardedFor.contains("unknown")) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = request.getHeader("X-Real-IP");
if (StringUtils.hasText(xRealIp) && !xRealIp.contains("unknown")) {
return xRealIp.trim();
}
return request.getRemoteAddr();
}
/**
* 获取当前用户ID(这里简化处理,实际项目中可能需要从SecurityContext获取)
*/
private String getCurrentUserId() {
// 这里只是一个示例,实际项目中可能需要从认证信息中获取用户ID
// 比如从Spring Security的Authentication对象中获取
return "user123"; // 示例值
}
}
关键实现细节
1. 接口应用示例
@RestController
@RequestMapping("/api")
@Slf4j
public class SecurityController {
@Autowired
private SmsService smsService;
@Autowired
private UserService userService;
@Autowired
private PaymentService paymentService;
/**
* 发送短信验证码 - 限制每分钟最多5次
*/
@PostMapping("/sms/send")
@RateLimit(key = "'sms:' + #phone", window = 60000, count = 5, message = "短信发送过于频繁,请稍后再试")
public ResponseEntity<ApiResponse<String>> sendSms(@RequestParam String phone) {
try {
log.info("发送短信验证码: phone={}", phone);
boolean success = smsService.sendVerificationCode(phone);
if (success) {
return ResponseEntity.ok(ApiResponse.success("验证码已发送", "发送成功"));
} else {
return ResponseEntity.badRequest().body(ApiResponse.error("验证码发送失败"));
}
} catch (RateLimitException e) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
.body(ApiResponse.error(e.getMessage()));
}
}
/**
* 用户登录 - 限制每分钟最多10次
*/
@PostMapping("/auth/login")
@RateLimit(key = "'login:' + #request.ipAddress", limitType = RateLimit.LimitType.IP,
window = 60000, count = 10, message = "登录尝试过于频繁,请稍后再试")
public ResponseEntity<ApiResponse<String>> login(@RequestBody LoginRequest request) {
try {
log.info("用户登录请求: username={}", request.getUsername());
String token = userService.login(request);
return ResponseEntity.ok(ApiResponse.success(token, "登录成功"));
} catch (RateLimitException e) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
.body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("登录异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("登录失败"));
}
}
/**
* 支付接口 - 限制每小时最多20次
*/
@PostMapping("/payment/create")
@RateLimit(key = "'payment:' + #request.userId", limitType = RateLimit.LimitType.USER,
window = 3600000, count = 20, message = "支付请求过于频繁,请稍后再试")
public ResponseEntity<ApiResponse<String>> createPayment(@RequestBody PaymentRequest request) {
try {
log.info("创建支付订单: userId={}, amount={}", request.getUserId(), request.getAmount());
String orderId = paymentService.createPayment(request);
return ResponseEntity.ok(ApiResponse.success(orderId, "支付订单创建成功"));
} catch (RateLimitException e) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
.body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("创建支付订单异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("支付订单创建失败"));
}
}
/**
* 高频接口示例 - 限制每秒最多100次
*/
@GetMapping("/health")
@RateLimit(key = "'health_check'", window = 1000, count = 100, message = "健康检查请求过多")
public ResponseEntity<ApiResponse<String>> healthCheck() {
return ResponseEntity.ok(ApiResponse.success("OK", "服务正常"));
}
}
// 登录请求对象
@Data
public class LoginRequest {
private String username;
private String password;
private String ipAddress; // 客户端IP
}
// 支付请求对象
@Data
public class PaymentRequest {
private String userId;
private BigDecimal amount;
private String productId;
}
2. 配置管理
# application.yml
rate-limit:
# 全局默认配置
default:
window: 60000 # 默认时间窗口1分钟
count: 10 # 默认最大请求数10次
# 特定接口配置
specific:
sms:
window: 60000
count: 5
login:
window: 60000
count: 10
payment:
window: 3600000
count: 20
# 是否启用限流
enabled: true
# 是否记录限流日志
log-enabled: true
# Redis配置
spring:
redis:
host: localhost
port: 6379
timeout: 2000ms
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
max-wait: 1000ms
// 限流配置类
@Configuration
@ConfigurationProperties(prefix = "rate-limit")
@Data
public class RateLimitProperties {
private boolean enabled = true;
private boolean logEnabled = true;
private DefaultConfig defaultConfig = new DefaultConfig();
private SpecificConfig specificConfig = new SpecificConfig();
@Data
public static class DefaultConfig {
private long window = 60000; // 1分钟
private int count = 10;
}
@Data
public static class SpecificConfig {
private InterfaceConfig sms = new InterfaceConfig();
private InterfaceConfig login = new InterfaceConfig();
private InterfaceConfig payment = new InterfaceConfig();
}
@Data
public static class InterfaceConfig {
private long window = 60000;
private int count = 10;
}
}
// 限流配置注入
@Component
public class RateLimitConfig {
@Autowired
private RateLimitProperties properties;
public boolean isEnabled() {
return properties.isEnabled();
}
public boolean isLogEnabled() {
return properties.isLogEnabled();
}
public long getDefaultWindow() {
return properties.getDefaultConfig().getWindow();
}
public int getDefaultCount() {
return properties.getDefaultConfig().getCount();
}
}
3. 监控和统计
// 限流监控服务
@Service
@Slf4j
public class RateLimitMonitor {
@Autowired
private SlidingWindowCounter slidingWindowCounter;
@Autowired
private MeterRegistry meterRegistry;
// 限流统计指标
private final Counter rateLimitedCounter;
private final Gauge activeRequestsGauge;
public RateLimitMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.rateLimitedCounter = Counter.builder("rate_limit_requests_total")
.description("被限流的请求数")
.register(meterRegistry);
this.activeRequestsGauge = Gauge.builder("active_rate_limit_keys")
.description("活跃的限流KEY数量")
.register(meterRegistry, this, RateLimitMonitor::getActiveKeyCount);
}
/**
* 记录限流事件
*/
public void recordRateLimitEvent(String key, String endpoint) {
if (meterRegistry != null) {
rateLimitedCounter.increment(
Tags.of("key", key, "endpoint", endpoint)
);
}
log.info("限流事件: key={}, endpoint={}", key, endpoint);
}
/**
* 获取活跃的限流KEY数量(示例实现,实际可能需要从Redis中统计)
*/
public double getActiveKeyCount() {
// 这里只是一个示例,实际实现可能需要查询Redis中的key数量
return 0;
}
/**
* 获取指定key的当前请求数
*/
public long getCurrentRequestCount(String key, long windowMs) {
return slidingWindowCounter.getCurrentCount(key, windowMs);
}
/**
* 获取限流统计信息
*/
public RateLimitStats getStats(String key, long windowMs) {
long currentCount = getCurrentRequestCount(key, windowMs);
return RateLimitStats.builder()
.key(key)
.currentCount(currentCount)
.windowMs(windowMs)
.allowed(currentCount < getMaxAllowedCount(key))
.build();
}
private int getMaxAllowedCount(String key) {
// 根据key的类型返回对应的最大允许次数
if (key.startsWith("sms:")) {
return 5; // 短信限制
} else if (key.startsWith("login:")) {
return 10; // 登录限制
} else if (key.startsWith("payment:")) {
return 20; // 支付限制
}
return 10; // 默认限制
}
}
// 限流统计信息
@Data
@Builder
public class RateLimitStats {
private String key;
private long currentCount;
private long windowMs;
private boolean allowed;
private long remainingCount;
private long resetTime;
}
业务场景应用
1. 登录接口防刷
@Service
@Slf4j
public class LoginProtectionService {
@Autowired
private SlidingWindowCounter slidingWindowCounter;
@Autowired
private RateLimitMonitor rateLimitMonitor;
/**
* 检查登录尝试频率
*/
public boolean checkLoginFrequency(String ip, String username) {
// IP维度的登录频率限制:1分钟内最多10次
String ipKey = "login_ip:" + ip;
boolean ipAllowed = slidingWindowCounter.isAllowed(ipKey, 60000, 10);
if (!ipAllowed) {
rateLimitMonitor.recordRateLimitEvent(ipKey, "login-by-ip");
return false;
}
// 用户维度的登录频率限制:1小时内最多20次
String userKey = "login_user:" + username;
boolean userAllowed = slidingWindowCounter.isAllowed(userKey, 3600000, 20);
if (!userAllowed) {
rateLimitMonitor.recordRateLimitEvent(userKey, "login-by-user");
return false;
}
return true;
}
/**
* 记录登录尝试
*/
public void recordLoginAttempt(String ip, String username, boolean success) {
// 记录登录尝试,无论成功与否
String ipKey = "login_ip:" + ip;
String userKey = "login_user:" + username;
// 添加到滑动窗口(即使成功也记录,用于统计)
slidingWindowCounter.isAllowedAtomic(ipKey, 60000, 10);
slidingWindowCounter.isAllowedAtomic(userKey, 3600000, 20);
if (!success) {
log.warn("登录失败记录: ip={}, username={}", ip, username);
}
}
}
2. 短信接口防刷
@Service
@Slf4j
public class SmsProtectionService {
@Autowired
private SlidingWindowCounter slidingWindowCounter;
@Autowired
private RateLimitMonitor rateLimitMonitor;
/**
* 检查短信发送频率
*/
public boolean checkSmsFrequency(String phone, String ip) {
// 手机号码维度的限制:1分钟内最多3次
String phoneKey = "sms_phone:" + phone;
boolean phoneAllowed = slidingWindowCounter.isAllowed(phoneKey, 60000, 3);
if (!phoneAllowed) {
rateLimitMonitor.recordRateLimitEvent(phoneKey, "sms-by-phone");
return false;
}
// IP维度的限制:1分钟内最多5次
String ipKey = "sms_ip:" + ip;
boolean ipAllowed = slidingWindowCounter.isAllowed(ipKey, 60000, 5);
if (!ipAllowed) {
rateLimitMonitor.recordRateLimitEvent(ipKey, "sms-by-ip");
return false;
}
return true;
}
/**
* 记录短信发送
*/
public void recordSmsSent(String phone, String ip) {
String phoneKey = "sms_phone:" + phone;
String ipKey = "sms_ip:" + ip;
slidingWindowCounter.isAllowedAtomic(phoneKey, 60000, 3);
slidingWindowCounter.isAllowedAtomic(ipKey, 60000, 5);
log.info("短信发送记录: phone={}", phone);
}
}
3. 支付接口防刷
@Service
@Slf4j
public class PaymentProtectionService {
@Autowired
private SlidingWindowCounter slidingWindowCounter;
@Autowired
private RateLimitMonitor rateLimitMonitor;
/**
* 检查支付请求频率
*/
public boolean checkPaymentFrequency(String userId, String ip) {
// 用户维度的限制:1小时内最多20次
String userKey = "payment_user:" + userId;
boolean userAllowed = slidingWindowCounter.isAllowed(userKey, 3600000, 20);
if (!userAllowed) {
rateLimitMonitor.recordRateLimitEvent(userKey, "payment-by-user");
return false;
}
// IP维度的限制:1小时内最多30次
String ipKey = "payment_ip:" + ip;
boolean ipAllowed = slidingWindowCounter.isAllowed(ipKey, 3600000, 30);
if (!ipAllowed) {
rateLimitMonitor.recordRateLimitEvent(ipKey, "payment-by-ip");
return false;
}
return true;
}
/**
* 记录支付请求
*/
public void recordPaymentRequest(String userId, String ip) {
String userKey = "payment_user:" + userId;
String ipKey = "payment_ip:" + ip;
slidingWindowCounter.isAllowedAtomic(userKey, 3600000, 20);
slidingWindowCounter.isAllowedAtomic(ipKey, 3600000, 30);
log.info("支付请求记录: userId={}", userId);
}
}
最佳实践建议
1. 参数配置建议
// 限流参数配置建议
public class RateLimitConstants {
// 短信接口
public static final long SMS_WINDOW_MS = 60000; // 1分钟
public static final int SMS_COUNT_PER_WINDOW = 3; // 最多3次
// 登录接口
public static final long LOGIN_WINDOW_MS = 60000; // 1分钟
public static final int LOGIN_COUNT_PER_WINDOW = 10; // 最多10次
// 支付接口
public static final long PAYMENT_WINDOW_MS = 3600000; // 1小时
public static final int PAYMENT_COUNT_PER_WINDOW = 20; // 最多20次
// 健康检查接口
public static final long HEALTH_WINDOW_MS = 1000; // 1秒
public static final int HEALTH_COUNT_PER_WINDOW = 100; // 最多100次
}
2. 性能优化
// 限流服务优化版本
@Service
@Slf4j
public class OptimizedRateLimitService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 使用连接池和批量操作优化性能
private final LoadingCache<String, Boolean> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofSeconds(10))
.build(this::checkRateLimit);
/**
* 带本地缓存的限流检查
* 适用于高频但不需要精确控制的场景
*/
public boolean isAllowedWithCache(String key, long windowMs, int maxCount) {
String cacheKey = key + ":" + windowMs + ":" + maxCount;
return localCache.get(cacheKey);
}
private Boolean checkRateLimit(String cacheKey) {
// 解析cacheKey获取实际参数
String[] parts = cacheKey.split(":");
String key = parts[0];
long windowMs = Long.parseLong(parts[1]);
int maxCount = Integer.parseInt(parts[2]);
return isAllowedInternal(key, windowMs, maxCount);
}
private boolean isAllowedInternal(String key, long windowMs, int maxCount) {
// 实际的限流检查逻辑
return new SlidingWindowCounter().isAllowedAtomic(key, windowMs, maxCount);
}
}
3. 监控告警
// 限流告警服务
@Component
@Slf4j
public class RateLimitAlertService {
@Autowired
private MeterRegistry meterRegistry;
/**
* 检查是否需要告警
*/
@Scheduled(fixedRate = 30000) // 每30秒检查一次
public void checkRateLimitAlerts() {
// 获取限流统计数据
Double rateLimitedCount = meterRegistry.counter("rate_limit_requests_total")
.count();
// 如果限流次数异常增多,发送告警
if (rateLimitedCount != null && rateLimitedCount > 1000) { // 假设阈值为1000
log.warn("检测到大量限流请求: count={}", rateLimitedCount);
// 发送告警通知(如钉钉、邮件等)
sendAlert("检测到大量限流请求,请检查是否有恶意攻击");
}
}
private void sendAlert(String message) {
// 实现告警发送逻辑
log.error("限流告警: {}", message);
}
}
预期效果
通过这套滑动窗口计数防刷方案,我们可以实现:
- 精准控制:精确控制任意时间窗口内的请求频率
- 资源保护:有效保护服务器资源不被恶意请求消耗
- 成本控制:减少因恶意刷取造成的业务成本
- 用户体验:保障正常用户的访问体验
- 监控完善:全面的限流监控和告警机制
这套方案让系统从"被动防御"变成了"主动防护",大大提升了系统的安全性和稳定性。
欢迎关注公众号"服务端技术精选",获取更多技术干货!
标题:SpringBoot + 接口防刷 + 滑动窗口计数:登录、短信、支付接口防暴力攻击
作者:jiangyi
地址:http://jiangyi.space/articles/2026/02/14/1770877410867.html
公众号:服务端技术精选
评论
0 评论