在开发 Web 应用时,接口防刷(防止恶意或高频请求)是一个常见的需求。以下是五种常见的实现方案,适用于 Spring Boot 项目:
![图片[1]_Spring Boot 实现接口防刷的五种方案_知途无界](https://zhituwujie.com/wp-content/uploads/2025/04/d2b5ca33bd20250410093946.png)
方案一:基于 IP 的请求限制
原理
通过记录客户端的 IP 地址,对同一 IP 的请求频率进行限制。
实现方式
- 使用
Guava
的RateLimiter
或 Redis 计数器。 - 记录每个 IP 的请求次数,设置时间窗口(如 1 分钟内最多允许 100 次请求)。
- 如果超过限制,返回错误响应(如 HTTP 429 Too Many Requests)。
代码示例
@RestControllerpublic class RateLimitController {private final Map<String, Long> ipRequestMap = new ConcurrentHashMap<>();private static final int MAX_REQUESTS = 100;private static final long TIME_WINDOW = 60 * 1000; // 1分钟@GetMapping("/api/test")public ResponseEntity<String> testEndpoint(@RequestHeader("X-Forwarded-For") String ip) {long currentTime = System.currentTimeMillis();ipRequestMap.putIfAbsent(ip, currentTime);Long firstRequestTime = ipRequestMap.get(ip);if (currentTime - firstRequestTime > TIME_WINDOW) {// 重置时间窗口ipRequestMap.put(ip, currentTime);} else if (ipRequestMap.values().stream().filter(time -> time >= firstRequestTime).count() > MAX_REQUESTS) {return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁");}return ResponseEntity.ok("请求成功");}}@RestController public class RateLimitController { private final Map<String, Long> ipRequestMap = new ConcurrentHashMap<>(); private static final int MAX_REQUESTS = 100; private static final long TIME_WINDOW = 60 * 1000; // 1分钟 @GetMapping("/api/test") public ResponseEntity<String> testEndpoint(@RequestHeader("X-Forwarded-For") String ip) { long currentTime = System.currentTimeMillis(); ipRequestMap.putIfAbsent(ip, currentTime); Long firstRequestTime = ipRequestMap.get(ip); if (currentTime - firstRequestTime > TIME_WINDOW) { // 重置时间窗口 ipRequestMap.put(ip, currentTime); } else if (ipRequestMap.values().stream().filter(time -> time >= firstRequestTime).count() > MAX_REQUESTS) { return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁"); } return ResponseEntity.ok("请求成功"); } }@RestController public class RateLimitController { private final Map<String, Long> ipRequestMap = new ConcurrentHashMap<>(); private static final int MAX_REQUESTS = 100; private static final long TIME_WINDOW = 60 * 1000; // 1分钟 @GetMapping("/api/test") public ResponseEntity<String> testEndpoint(@RequestHeader("X-Forwarded-For") String ip) { long currentTime = System.currentTimeMillis(); ipRequestMap.putIfAbsent(ip, currentTime); Long firstRequestTime = ipRequestMap.get(ip); if (currentTime - firstRequestTime > TIME_WINDOW) { // 重置时间窗口 ipRequestMap.put(ip, currentTime); } else if (ipRequestMap.values().stream().filter(time -> time >= firstRequestTime).count() > MAX_REQUESTS) { return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁"); } return ResponseEntity.ok("请求成功"); } }
优化建议
- 使用 Redis 实现分布式请求计数。
- 配合 Nginx 或网关层进行 IP 限制。
方案二:基于 Token 的限流
原理
为每个用户生成唯一的 Token,Token 附带时间戳和请求次数,服务器验证 Token 的有效性。
实现方式
- 用户登录后生成 Token,Token 中包含用户 ID 和请求计数。
- 每次请求时验证 Token,并更新计数。
- 如果计数超过限制,拒绝请求。
代码示例
@RestControllerpublic class TokenLimitController {private final Map<String, Integer> tokenMap = new ConcurrentHashMap<>();@PostMapping("/login")public String login() {// 模拟生成 TokenString token = UUID.randomUUID().toString();tokenMap.put(token, 0);return token;}@GetMapping("/api/test")public ResponseEntity<String> testEndpoint(@RequestHeader("Authorization") String token) {if (!tokenMap.containsKey(token)) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("无效的 Token");}int count = tokenMap.compute(token, (k, v) -> v == null ? 0 : v + 1);if (count > 10) { // 每 Token 限制 10 次请求return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁");}return ResponseEntity.ok("请求成功");}}@RestController public class TokenLimitController { private final Map<String, Integer> tokenMap = new ConcurrentHashMap<>(); @PostMapping("/login") public String login() { // 模拟生成 Token String token = UUID.randomUUID().toString(); tokenMap.put(token, 0); return token; } @GetMapping("/api/test") public ResponseEntity<String> testEndpoint(@RequestHeader("Authorization") String token) { if (!tokenMap.containsKey(token)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("无效的 Token"); } int count = tokenMap.compute(token, (k, v) -> v == null ? 0 : v + 1); if (count > 10) { // 每 Token 限制 10 次请求 return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁"); } return ResponseEntity.ok("请求成功"); } }@RestController public class TokenLimitController { private final Map<String, Integer> tokenMap = new ConcurrentHashMap<>(); @PostMapping("/login") public String login() { // 模拟生成 Token String token = UUID.randomUUID().toString(); tokenMap.put(token, 0); return token; } @GetMapping("/api/test") public ResponseEntity<String> testEndpoint(@RequestHeader("Authorization") String token) { if (!tokenMap.containsKey(token)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("无效的 Token"); } int count = tokenMap.compute(token, (k, v) -> v == null ? 0 : v + 1); if (count > 10) { // 每 Token 限制 10 次请求 return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁"); } return ResponseEntity.ok("请求成功"); } }
优化建议
- 使用 JWT 或 Redis 存储 Token。
- 配合前端实现 Token 的自动刷新。
方案三:基于滑动窗口算法的限流
原理
滑动窗口算法通过记录每个请求的时间戳,动态计算时间窗口内的请求次数。
实现方式
- 使用
LinkedHashMap
或 Redis 的Sorted Set
存储请求时间戳。 - 每次请求时移除窗口外的旧时间戳,计算剩余时间戳的数量。
- 如果数量超过限制,拒绝请求。
代码示例
@RestControllerpublic class SlidingWindowController {private final Map<String, LinkedList<Long>> ipRequestMap = new ConcurrentHashMap<>();private static final int MAX_REQUESTS = 100;private static final long TIME_WINDOW = 60 * 1000; // 1分钟@GetMapping("/api/test")public ResponseEntity<String> testEndpoint(@RequestHeader("X-Forwarded-For") String ip) {long currentTime = System.currentTimeMillis();ipRequestMap.computeIfAbsent(ip, k -> new LinkedList<>()).add(currentTime);LinkedList<Long> requests = ipRequestMap.get(ip);while (!requests.isEmpty() && requests.peekFirst() < currentTime - TIME_WINDOW) {requests.pollFirst();}if (requests.size() > MAX_REQUESTS) {return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁");}return ResponseEntity.ok("请求成功");}}@RestController public class SlidingWindowController { private final Map<String, LinkedList<Long>> ipRequestMap = new ConcurrentHashMap<>(); private static final int MAX_REQUESTS = 100; private static final long TIME_WINDOW = 60 * 1000; // 1分钟 @GetMapping("/api/test") public ResponseEntity<String> testEndpoint(@RequestHeader("X-Forwarded-For") String ip) { long currentTime = System.currentTimeMillis(); ipRequestMap.computeIfAbsent(ip, k -> new LinkedList<>()).add(currentTime); LinkedList<Long> requests = ipRequestMap.get(ip); while (!requests.isEmpty() && requests.peekFirst() < currentTime - TIME_WINDOW) { requests.pollFirst(); } if (requests.size() > MAX_REQUESTS) { return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁"); } return ResponseEntity.ok("请求成功"); } }@RestController public class SlidingWindowController { private final Map<String, LinkedList<Long>> ipRequestMap = new ConcurrentHashMap<>(); private static final int MAX_REQUESTS = 100; private static final long TIME_WINDOW = 60 * 1000; // 1分钟 @GetMapping("/api/test") public ResponseEntity<String> testEndpoint(@RequestHeader("X-Forwarded-For") String ip) { long currentTime = System.currentTimeMillis(); ipRequestMap.computeIfAbsent(ip, k -> new LinkedList<>()).add(currentTime); LinkedList<Long> requests = ipRequestMap.get(ip); while (!requests.isEmpty() && requests.peekFirst() < currentTime - TIME_WINDOW) { requests.pollFirst(); } if (requests.size() > MAX_REQUESTS) { return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁"); } return ResponseEntity.ok("请求成功"); } }
优化建议
- 使用 Redis 的
ZADD
和ZREMRANGEBYSCORE
实现分布式滑动窗口。
方案四:使用网关层限流
原理
在 API 网关(如 Nginx、Spring Cloud Gateway、Kong)中配置限流规则,拦截恶意请求。
实现方式
- 配置 Nginx 的
limit_req
模块。 - 使用 Spring Cloud Gateway 的
RequestRateLimiter
过滤器。
Nginx 示例
http {limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;server {location /api/ {limit_req zone=one burst=20 nodelay;}}}http { limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s; server { location /api/ { limit_req zone=one burst=20 nodelay; } } }http { limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s; server { location /api/ { limit_req zone=one burst=20 nodelay; } } }
Spring Cloud Gateway 示例
spring:cloud:gateway:routes:- id: rate_limiter_routeuri: http://example.orgfilters:- name: RequestRateLimiterargs:redis-rate-limiter.replenishRate: 10redis-rate-limiter.burstCapacity: 20predicates:- Path=/api/**spring: cloud: gateway: routes: - id: rate_limiter_route uri: http://example.org filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 predicates: - Path=/api/**spring: cloud: gateway: routes: - id: rate_limiter_route uri: http://example.org filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 predicates: - Path=/api/**
方案五:验证码机制
原理
对于高风险接口(如登录、支付),要求用户输入验证码,防止自动化脚本刷接口。
实现方式
- 集成第三方验证码服务(如阿里云、腾讯云)。
- 在关键接口前增加验证码验证逻辑。
代码示例
@RestControllerpublic class CaptchaController {@GetMapping("/api/captcha")public String generateCaptcha() {// 生成验证码并返回给前端return "模拟验证码";}@PostMapping("/api/sensitive")public ResponseEntity<String> sensitiveOperation(@RequestParam String captcha) {if (!"1234".equals(captcha)) { // 模拟验证码验证return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("验证码错误");}return ResponseEntity.ok("操作成功");}}@RestController public class CaptchaController { @GetMapping("/api/captcha") public String generateCaptcha() { // 生成验证码并返回给前端 return "模拟验证码"; } @PostMapping("/api/sensitive") public ResponseEntity<String> sensitiveOperation(@RequestParam String captcha) { if (!"1234".equals(captcha)) { // 模拟验证码验证 return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("验证码错误"); } return ResponseEntity.ok("操作成功"); } }@RestController public class CaptchaController { @GetMapping("/api/captcha") public String generateCaptcha() { // 生成验证码并返回给前端 return "模拟验证码"; } @PostMapping("/api/sensitive") public ResponseEntity<String> sensitiveOperation(@RequestParam String captcha) { if (!"1234".equals(captcha)) { // 模拟验证码验证 return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("验证码错误"); } return ResponseEntity.ok("操作成功"); } }
总结与选择
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
基于 IP 的请求限制 | 简单接口防刷 | 实现简单,无需用户登录 | 无法区分正常用户和恶意用户 |
基于 Token 的限流 | 用户登录后的接口 | 精准限流,用户体验较好 | 需要用户登录,复杂度较高 |
滑动窗口算法 | 高并发场景 | 限流精准,资源占用少 | 实现复杂 |
网关层限流 | 分布式系统 | 性能高,集中管理 | 需要额外部署网关 |
验证码机制 | 高风险接口 | 防止自动化脚本刷接口 | 用户体验较差 |
推荐:
- 对于简单接口,优先使用 基于 IP 的请求限制。
- 对于用户登录后的接口,使用 基于 Token 的限流。
- 对于高并发场景,结合 滑动窗口算法 和 网关层限流。
- 对于高风险接口,增加 验证码机制。
通过组合多种方案,可以更有效地防止接口被恶意刷取。
© 版权声明
文中内容均来源于公开资料,受限于信息的时效性和复杂性,可能存在误差或遗漏。我们已尽力确保内容的准确性,但对于因信息变更或错误导致的任何后果,本站不承担任何责任。如需引用本文内容,请注明出处并尊重原作者的版权。
THE END
暂无评论内容