admin 2026-01-23 10:54:41 99女足世界杯

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

接口防刷是保障系统安全与稳定性的重要措施。恶意的高频请求不仅会消耗服务器资源,还可能导致数据异常,甚至系统瘫痪。本文将介绍在SpringBoot框架下实现接口防刷的5种技术方案。

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

最常见的防刷方案是通过自定义注解和AOP切面实现访问频率限制。这种方法简单易用,实现成本低。

实现步骤

1.1 创建限流注解

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface RateLimit {

/**

* 限制时间段,单位为秒

*/

int time() default 60;

/**

* 在限制时间段内允许的最大请求次数

*/

int count() default 10;

/**

* 限流的key,支持SpEL表达式

*/

String key() default "";

/**

* 提示信息

*/

String message() default "操作太频繁,请稍后再试";

}

1.2 实现限流切面

@Aspect

@Component

@Slf4j

public class RateLimitAspect {

@Autowired

private StringRedisTemplate redisTemplate;

@Around("@annotation(rateLimit)")

public Object around(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {

// 获取请求的方法名

String methodName = pjp.getSignature().getName();

// 获取请求的类名

String className = pjp.getTarget().getClass().getName();

// 组合限流key

String limitKey = getLimitKey(pjp, rateLimit, methodName, className);

// 获取限流参数

int time = rateLimit.time();

int count = rateLimit.count();

// 执行限流逻辑

boolean limited = isLimited(limitKey, time, count);

if (limited) {

throw new RuntimeException(rateLimit.message());

}

// 执行目标方法

return pjp.proceed();

}

private String getLimitKey(ProceedingJoinPoint pjp, RateLimit rateLimit, String methodName, String className) {

// 获取用户自定义的key

String key = rateLimit.key();

if (StringUtils.hasText(key)) {

// 支持SpEL表达式解析

StandardEvaluationContext context = new StandardEvaluationContext();

MethodSignature signature = (MethodSignature) pjp.getSignature();

String[] parameterNames = signature.getParameterNames();

Object[] args = pjp.getArgs();

for (int i = 0; i < parameterNames.length; i++) {

context.setVariable(parameterNames[i], args[i]);

}

ExpressionParser parser = new SpelExpressionParser();

Expression expression = parser.parseExpression(key);

key = expression.getValue(context, String.class);

} else {

// 默认使用类名+方法名+IP地址作为key

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

String ip = getIpAddress(request);

key = ip + ":" + className + ":" + methodName;

}

return "rate_limit:" + key;

}

private boolean isLimited(String key, int time, int count) {

// 使用Redis的计数器实现限流

try {

Long currentCount = redisTemplate.opsForValue().increment(key, 1);

// 如果是第一次访问,设置过期时间

if (currentCount == 1) {

redisTemplate.expire(key, time, TimeUnit.SECONDS);

}

return currentCount > count;

} catch (Exception e) {

log.error("限流异常", e);

return false;

}

}

private String getIpAddress(HttpServletRequest request) {

String ip = request.getHeader("X-Forwarded-For");

if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

ip = request.getHeader("Proxy-Client-IP");

}

if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

ip = request.getHeader("WL-Proxy-Client-IP");

}

if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

ip = request.getHeader("HTTP_CLIENT_IP");

}

if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

ip = request.getHeader("HTTP_X_FORWARDED_FOR");

}

if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

ip = request.getRemoteAddr();

}

return ip;

}

}

1.3 使用示例

@RestController

@RequestMapping("/api")

public class UserController {

@RateLimit(time = 60, count = 3, message = "请求太频繁,请稍后再试")

@GetMapping("/user/{id}")

public User getUser(@PathVariable Long id) {

return userService.getUser(id);

}

// 使用SpEL表达式指定key

@RateLimit(time = 60, count = 1, key = "#id + '_' + #request.remoteAddr")

@PostMapping("/user/{id}/update")

public Result updateUser(@PathVariable Long id, @RequestBody UserDTO userDTO, HttpServletRequest request) {

return userService.updateUser(id, userDTO);

}

}

优缺点分析

优点:

实现简单,上手容易,单机情况下可以去掉Redis换成本地缓存实现

注解式使用,对业务代码无侵入

可以精确控制接口粒度

支持灵活的限流策略配置 缺点:

限流逻辑相对简单,无法应对复杂场景

缺少预警机制

2. 令牌桶算法实现限流

令牌桶算法是一种更加灵活的限流算法,可以允许突发流量,同时又能限制长期的平均流量。

实现步骤

2.1 引入依赖

Google提供的Guava库中包含了令牌桶实现:

com.google.guava

guava

31.1-jre

2.2 创建令牌桶限流器

@Component

public class RateLimiter {

// 使用ConcurrentHashMap存储不同接口的令牌桶

private final ConcurrentHashMap rateLimiterMap = new ConcurrentHashMap<>();

/**

* 获取特定接口的令牌桶,不存在则创建

* @param key 限流键

* @param permitsPerSecond 每秒允许的请求量

* @return 令牌桶实例

*/

public com.google.common.util.concurrent.RateLimiter getRateLimiter(String key, double permitsPerSecond) {

return rateLimiterMap.computeIfAbsent(key,

k -> com.google.common.util.concurrent.RateLimiter.create(permitsPerSecond));

}

/**

* 尝试获取令牌

* @param key 限流键

* @param permitsPerSecond 每秒允许的请求量

* @param timeout 超时时间

* @param unit 时间单位

* @return 是否获取成功

*/

public boolean tryAcquire(String key, double permitsPerSecond, long timeout, TimeUnit unit) {

com.google.common.util.concurrent.RateLimiter rateLimiter = getRateLimiter(key, permitsPerSecond);

return rateLimiter.tryAcquire(1, timeout, unit);

}

}

2.3 创建拦截器

@Component

public class TokenBucketInterceptor implements HandlerInterceptor {

@Autowired

private RateLimiter rateLimiter;

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

// 仅对API请求进行限流

String requestURI = request.getRequestURI();

if (!requestURI.startsWith("/api/")) {

return true;

}

// 获取IP地址作为限流键

String ip = getIpAddress(request);

String key = ip + ":" + requestURI;

// 尝试获取令牌,设置每秒2个请求的速率,等待100毫秒

boolean acquired = rateLimiter.tryAcquire(key, 2.0, 100, TimeUnit.MILLISECONDS);

if (!acquired) {

// 获取失败,返回限流响应

response.setContentType("application/json;charset=UTF-8");

response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());

response.getWriter().write("{\"code\":429,\"message\":\"请求过于频繁,请稍后再试\"}");

return false;

}

return true;

}

// getIpAddress方法同上

}

2.4 配置拦截器

@Configuration

public class WebConfig implements WebMvcConfigurer {

@Autowired

private TokenBucketInterceptor tokenBucketInterceptor;

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(tokenBucketInterceptor)

.addPathPatterns("/**");

}

}

优缺点分析

优点:

支持突发流量,不会完全拒绝短时高峰

平滑的限流效果,用户体验更好

可以配置不同接口的不同限流策略

无需额外的存储设施 缺点:

只适用于单机部署,分布式环境需要额外改造

重启应用后状态丢失

无法精确控制时间窗口内的请求总量

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

对于分布式系统,单机限流方案难以满足需求。利用Redis和Lua脚本可以实现高效的分布式限流。

实现步骤

3.1 定义Lua脚本

创建一个Redis限流的Lua脚本,放在resources目录下的scripts/rate_limiter.lua:

-- 限流Key

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

@Slf4j

public class RedisRateLimiterService {

@Autowired

private StringRedisTemplate redisTemplate;

private DefaultRedisScript rateLimiterScript;

@PostConstruct

public void init() {

// 加载Lua脚本

rateLimiterScript = new DefaultRedisScript<>();

rateLimiterScript.setLocation(new ClassPathResource("scripts/rate_limiter.lua"));

rateLimiterScript.setResultType(Long.class);

}

/**

* 尝试获取访问权限

* @param key 限流键

* @param window 时间窗口(秒)

* @param threshold 阈值

* @return 剩余可用请求数,-1表示被限流

*/

public long isAllowed(String key, int window, int threshold) {

try {

// 执行lua脚本

List keys = Collections.singletonList(key);

Long remainingCount = redisTemplate.execute(

rateLimiterScript,

keys,

String.valueOf(window),

String.valueOf(threshold),

String.valueOf(System.currentTimeMillis())

);

return remainingCount == null ? -1 : remainingCount;

} catch (Exception e) {

log.error("Redis rate limiter error", e);

// 发生异常时放行请求

return threshold;

}

}

}

3.3 创建分布式限流注解

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface DistributedRateLimit {

/**

* 限流的key前缀

*/

String prefix() default "rate:";

/**

* 时间窗口,单位秒

*/

int window() default 60;

/**

* 在时间窗口内允许的最大请求数

*/

int threshold() default 10;

/**

* 限流模式: ip - 按IP限流, user - 按用户限流, all - 接口总体限流

*/

String mode() default "ip";

}

3.4 实现分布式限流切面

@Aspect

@Component

@Slf4j

public class DistributedRateLimitAspect {

@Autowired

private RedisRateLimiterService rateLimiterService;

@Autowired(required = false)

private HttpServletRequest request;

@Around("@annotation(rateLimit)")

public Object around(ProceedingJoinPoint pjp, DistributedRateLimit rateLimit) throws Throwable {

String key = generateKey(pjp, rateLimit);

long remainingCount = rateLimiterService.isAllowed(

key,

rateLimit.window(),

rateLimit.threshold()

);

if (remainingCount < 0) {

throw new RuntimeException("接口访问过于频繁,请稍后再试");

}

// 执行目标方法

return pjp.proceed();

}

private String generateKey(ProceedingJoinPoint pjp, DistributedRateLimit rateLimit) {

String methodName = pjp.getSignature().getName();

String className = pjp.getTarget().getClass().getName();

StringBuilder key = new StringBuilder(rateLimit.prefix());

key.append(className).append(".").append(methodName);

// 根据限流模式添加不同的后缀

switch (rateLimit.mode()) {

case "ip":

// 按IP限流

key.append(":").append(getIpAddress());

break;

case "user":

// 按用户限流

Object userId = getUserId();

key.append(":").append(userId != null ? userId : "anonymous");

break;

case "all":

// 接口总体限流,不添加后缀

break;

default:

key.append(":").append(getIpAddress());

break;

}

return key.toString();

}

private String getIpAddress() {

// IP获取方法同上

if (request == null) {

return "unknown";

}

// 获取IP的代码同上一个示例

return "127.0.0.1"; // 简化处理

}

// 获取当前用户ID,根据实际认证系统实现

private Object getUserId() {

// 这里简化处理,实际中应从认证信息中获取

// 例如:SecurityContextHolder.getContext().getAuthentication().getPrincipal()

return null;

}

}

3.5 使用示例

@RestController

@RequestMapping("/api")

public class PaymentController {

@DistributedRateLimit(prefix = "pay:", window = 3600, threshold = 5, mode = "user")

@PostMapping("/payment")

public Result createPayment(@RequestBody PaymentRequest paymentRequest) {

// 创建支付业务逻辑

return paymentService.createPayment(paymentRequest);

}

@DistributedRateLimit(window = 60, threshold = 30, mode = "ip")

@GetMapping("/products")

public List getProducts() {

// 查询产品列表

return productService.findAll();

}

@DistributedRateLimit(window = 1, threshold = 100, mode = "all")

@GetMapping("/hot/resource")

public Resource getHotResource() {

// 获取热门资源

return resourceService.getHotResource();

}

}

优缺点分析

优点:

适用于分布式系统,多实例间共享限流状态

支持多种限流模式:按IP、用户、接口总量等

基于滑动窗口,计数更精确

使用Lua脚本保证原子性,避免竞态条件 缺点:

强依赖Redis

实现复杂度较高

4. 集成Sentinel实现接口防刷

阿里巴巴开源的Sentinel是一个强大的流量控制组件,提供了丰富的限流、熔断、系统保护等功能。

实现步骤

4.1 添加依赖

com.alibaba.cloud

spring-cloud-starter-alibaba-sentinel

2021.0.4.0

4.2 配置Sentinel

在application.properties中添加配置:

# Sentinel 控制台地址

spring.cloud.sentinel.transport.dashboard=localhost:8080

# 取消Sentinel控制台懒加载

spring.cloud.sentinel.eager=true

# 应用名称

spring.application.name=my-application

4.3 创建Sentinel配置

@Configuration

public class SentinelConfig {

@Bean

public SentinelResourceAspect sentinelResourceAspect() {

return new SentinelResourceAspect();

}

@PostConstruct

public void init() {

// 定义流控规则

initFlowRules();

}

private void initFlowRules() {

List rules = new ArrayList<>();

// 为/api/user接口设置流控规则

FlowRule userRule = new FlowRule();

userRule.setResource(/api/user);

userRule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 基于QPS限流

userRule.setCount(10); // 每秒允许10个请求

rules.add(userRule);

// 为/api/order接口设置流控规则

FlowRule orderRule = new FlowRule();

orderRule.setResource(/api/order);

orderRule.setGrade(RuleConstant.FLOW_GRADE_QPS);

orderRule.setCount(5); // 每秒允许5个请求

orderRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); // 预热模式

orderRule.setWarmUpPeriodSec(10); // 10秒预热期

rules.add(orderRule);

// 加载规则

FlowRuleManager.loadRules(rules);

}

}

4.4 创建URL资源解析器

@Component

public class UrlCleaner implements RequestOriginParser {

@Override

public String parseOrigin(HttpServletRequest request) {

// 获取请求的URL路径

String path = request.getRequestURI();

// 可以添加更复杂的解析逻辑,例如:

// 1. 去除路径变量:/api/user/123 -> /api/user/{id}

// 2. 添加请求方法前缀:GET:/api/user

return path;

}

}

4.5 创建全局异常处理器

@RestControllerAdvice

public class SentinelExceptionHandler {

@ExceptionHandler(BlockException.class)

public Result handleBlockException(BlockException e) {

String message = 请求过于频繁,请稍后再试;

if (e instanceof FlowException) {

message = 接口限流: + message;

} else if (e instanceof DegradeException) {

message = 服务降级:系统繁忙,请稍后再试;

} else if (e instanceof ParamFlowException) {

message = 热点参数限流:请求过于频繁;

} else if (e instanceof SystemBlockException) {

message = 系统保护:系统资源不足;

} else if (e instanceof AuthorityException) {

message = 授权控制:没有访问权限;

}

return Result.error(429, message);

}

}

4.6 使用@SentinelResource注解

@RestController

@RequestMapping(/api)

public class UserController {

// 使用资源名定义限流资源

@SentinelResource(value = getUserById,

blockHandler = getUserBlockHandler,

fallback = getUserFallback)

@GetMapping(/user/{id})

public User getUser(@PathVariable Long id) {

return userService.getUser(id);

}

// 限流处理方法

public User getUserBlockHandler(Long id, BlockException e) {

log.warn(Get user request blocked: {}, id, e);

throw new RuntimeException(请求频率过高,请稍后再试);

}

// 异常回退方法

public User getUserFallback(Long id, Throwable t) {

log.error(Get user failed: {}, id, t);

User fallbackUser = new User();

fallbackUser.setId(id);

fallbackUser.setName(Unknown);

return fallbackUser;

}

}

4.7 更复杂的限流规则配置

@Service

@Slf4j

public class SentinelRuleService {

public void initComplexFlowRules() {

List rules = new ArrayList<>();

// 基于QPS + 调用关系的限流规则

FlowRule apiRule = new FlowRule();

apiRule.setResource(/api/data);

apiRule.setGrade(RuleConstant.FLOW_GRADE_QPS);

apiRule.setCount(20);

// 限制调用来源

apiRule.setLimitApp(frontend); // 只限制来自前端应用的调用

// 流控策略:关联资源

apiRule.setStrategy(RuleConstant.STRATEGY_RELATE);

apiRule.setRefResource(/api/important); // 当important接口QPS高时,限制data接口

rules.add(apiRule);

// 基于并发线程数的限流

FlowRule threadRule = new FlowRule();

threadRule.setResource(/api/heavy-task);

threadRule.setGrade(RuleConstant.FLOW_GRADE_THREAD); // 基于线程数

threadRule.setCount(5); // 最多5个线程同时处理

rules.add(threadRule);

// 加载规则

FlowRuleManager.loadRules(rules);

}

public void initHotspotRules() {

// 热点参数限流规则

List rules = new ArrayList<>();

ParamFlowRule rule = new ParamFlowRule(/api/product);

// 对第0个参数(productId)进行限流

rule.setParamIdx(0);

rule.setCount(5);

// 特例配置

ParamFlowItem item1 = new ParamFlowItem();

item1.setObject(1); // productId = 1的商品

item1.setCount(10); // 可以有更高的QPS

ParamFlowItem item2 = new ParamFlowItem();

item2.setObject(2); // productId = 2的商品

item2.setCount(2); // 更严格的限制

rule.setParamFlowItemList(Arrays.asList(item1, item2));

rules.add(rule);

ParamFlowRuleManager.loadRules(rules);

}

}

优缺点分析

优点:

功能全面,支持QPS限流、并发线程数限流、热点参数限流等

支持多种控制策略:直接拒绝、预热、排队等

提供控制台可视化管理

支持动态规则调整

可与Spring Cloud体系无缝集成

缺点:

学习曲线较陡峭

分布式场景下需要额外配置规则持久化

引入了额外的依赖

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

对于某些敏感操作(如登录、注册、支付等),可以结合验证码和行为分析来防止恶意请求。

实现步骤

5.1 图形验证码实现

首先添加依赖:

com.github.whvcse

easy-captcha

1.6.2

5.2 创建验证码服务

@Service

public class CaptchaService {

@Autowired

private StringRedisTemplate redisTemplate;

private static final long CAPTCHA_EXPIRE_TIME = 5 * 60; // 5分钟

/**

* 生成验证码

* @param request HTTP请求

* @param response HTTP响应

* @return 验证码Base64字符串

*/

public String generateCaptcha(HttpServletRequest request, HttpServletResponse response) {

// 生成验证码

SpecCaptcha captcha = new SpecCaptcha(130, 48, 5);

// 生成验证码ID

String captchaId = UUID.randomUUID().toString();

// 将验证码存入Redis

redisTemplate.opsForValue().set(

captcha:+ captchaId,

captcha.text().toLowerCase(),

CAPTCHA_EXPIRE_TIME,

TimeUnit.SECONDS

);

// 设置Cookie

Cookie cookie = new Cookie(captchaId, captchaId);

cookie.setMaxAge((int) CAPTCHA_EXPIRE_TIME);

cookie.setPath(/);

response.addCookie(cookie);

// 返回Base64编码的验证码图片

return captcha.toBase64();

}

/**

* 验证验证码

* @param request HTTP请求

* @param captchaCode 用户输入的验证码

* @return 是否验证通过

*/

public boolean validateCaptcha(HttpServletRequest request, String captchaCode) {

// 从Cookie获取验证码ID

Cookie[] cookies = request.getCookies();

String captchaId = null;

if (cookies != null) {

for (Cookie cookie : cookies) {

if (captchaId.equals(cookie.getName())) {

captchaId = cookie.getValue();

break;

}

}

}

if (captchaId == null) {

return false;

}

// 从Redis获取正确的验证码

String key = captcha:+ captchaId;

String correctCode = redisTemplate.opsForValue().get(key);

// 验证成功后删除验证码

if (correctCode != null && correctCode.equals(captchaCode.toLowerCase())) {

redisTemplate.delete(key);

return true;

}

return false;

}

}

5.3 创建验证码控制器

@RestController

@RequestMapping(/api/captcha)

public class CaptchaController {

@Autowired

private CaptchaService captchaService;

@GetMapping

public Map getCaptcha(HttpServletRequest request, HttpServletResponse response) {

String base64 = captchaService.generateCaptcha(request, response);

return Map.of(captcha, base64);

}

}

5.4 创建验证码注解

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface CaptchaRequired {

String captchaParam() default captchaCode;

}

5.5 实现验证码拦截器

@Component

public class CaptchaInterceptor implements HandlerInterceptor {

@Autowired

private CaptchaService captchaService;

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

if (!(handler instanceof HandlerMethod)) {

return true;

}

HandlerMethod handlerMethod = (HandlerMethod) handler;

CaptchaRequired captchaRequired = handlerMethod.getMethodAnnotation(CaptchaRequired.class);

if (captchaRequired == null) {

return true;

}

// 获取验证码参数

String captchaParam = captchaRequired.captchaParam();

String captchaCode = request.getParameter(captchaParam);

if (StringUtils.hasText(captchaCode)) {

// 验证验证码

boolean valid = captchaService.validateCaptcha(request, captchaCode);

if (valid) {

return true;

}

}

// 验证失败

response.setContentType(application/json;charset=UTF-8);

response.setStatus(HttpStatus.BAD_REQUEST.value());

response.getWriter().write({code:400,message:验证码错误或已过期});

return false;

}

}

5.6 创建行为分析服务

@Service

@Slf4j

public class BehaviorAnalysisService {

@Autowired

private StringRedisTemplate redisTemplate;

/**

* 检查是否是可疑的机器行为

* @param request HTTP请求

* @return 是否可疑

*/

public boolean isSuspicious(HttpServletRequest request) {

// 1. 获取客户端信息

String ip = getIpAddress(request);

String userAgent = request.getHeader(User-Agent);

String requestId = request.getSession().getId();

// 2. 检查访问频率

String freqKey = behavior:freq:+ ip;

Long count = redisTemplate.opsForValue().increment(freqKey, 1);

redisTemplate.expire(freqKey, 1, TimeUnit.MINUTES);

if (count != null && count > 30) {

log.warn(访问频率异常: IP={}, count={}, ip, count);

return true;

}

// 3. 检查User-Agent

if (userAgent == null || isBotUserAgent(userAgent)) {

log.warn(可疑的User-Agent: {}, userAgent);

return true;

}

// 4. 检查请求时间模式

String timeKey = behavior:time:+ ip;

long now = System.currentTimeMillis();

String lastTimeStr = redisTemplate.opsForValue().get(timeKey);

if (lastTimeStr != null) {

long lastTime = Long.parseLong(lastTimeStr);

long interval = now - lastTime;

// 如果请求间隔非常均匀,可能是机器人

if (isUniformInterval(ip, interval)) {

log.warn(请求间隔异常均匀: IP={}, interval={}, ip, interval);

return true;

}

}

redisTemplate.opsForValue().set(timeKey, String.valueOf(now), 10, TimeUnit.MINUTES);

// 更多高级检测逻辑...

return false;

}

/**

* 检查是否是机器人UA

*/

private boolean isBotUserAgent(String userAgent) {

String ua = userAgent.toLowerCase();

return ua.contains(bot) || ua.contains(spider) || ua.contains(crawl) ||

ua.isEmpty() || ua.length() < 40;

}

/**

* 检查请求间隔是否异常均匀

*/

private boolean isUniformInterval(String ip, long interval) {

String key = behavior:intervals:+ ip;

// 获取最近的几个间隔

List intervalStrs = redisTemplate.opsForList().range(key, 0, 4);

redisTemplate.opsForList().leftPush(key, String.valueOf(interval));

redisTemplate.opsForList().trim(key, 0, 9); // 只保留最近10个

redisTemplate.expire(key, 10, TimeUnit.MINUTES);

if (intervalStrs == null || intervalStrs.size() < 5) {

return false;

}

// 计算间隔的方差,方差小说明请求间隔很均匀

List intervals = intervalStrs.stream()

.map(Long::parseLong)

.collect(Collectors.toList());

double mean = intervals.stream().mapToLong(Long::longValue).average().orElse(0);

double variance = intervals.stream()

.mapToDouble(i -> Math.pow(i - mean, 2))

.average()

.orElse(0);

return variance < 100; // 方差阈值,需要根据实际情况调整

}

// getIpAddress方法同上

}

5.7 创建行为分析拦截器

@Component

public class BehaviorAnalysisInterceptor implements HandlerInterceptor {

@Autowired

private BehaviorAnalysisService behaviorAnalysisService;

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

// 对于需要保护的端点进行检查

String path = request.getRequestURI();

if (path.startsWith(/api/) && isPotentialRiskEndpoint(path)) {

boolean suspicious = behaviorAnalysisService.isSuspicious(request);

if (suspicious) {

// 需要验证码或其他额外验证

response.setContentType(application/json;charset=UTF-8);

response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());

response.getWriter().write({code:429,message:检测到异常访问,请进行验证,needCaptcha:true});

return false;

}

}

return true;

}

/**

* 判断是否是高风险端点

*/

private boolean isPotentialRiskEndpoint(String path) {

return path.contains(/login) ||

path.contains(/register) ||

path.contains(/payment) ||

path.contains(/order) ||

path.contains(/password);

}

}

5.8 使用示例

@RestController

@RequestMapping(/api)

public class UserController {

@CaptchaRequired

@PostMapping(/login)

public Result login(@RequestParam String username,

@RequestParam String password,

@RequestParam String captchaCode) {

// 登录逻辑

return userService.login(username, password);

}

@CaptchaRequired

@PostMapping(/register)

public Result register(@RequestBody UserRegisterDTO registerDTO,

@RequestParam String captchaCode) {

// 注册逻辑

return userService.register(registerDTO);

}

}

优缺点分析

优点:

能有效区分人类用户和自动化脚本

对恶意用户有较强的阻止作用

针对敏感操作提供额外安全层

可以实现自适应安全策略

缺点:

增加了用户操作成本,可能影响用户体验

实现复杂,需要前后端配合

某些验证码可能被OCR技术破解

行为分析可能产生误判

方案对比与选择

方案

实现难度

防刷效果

分布式支持

用户体验

适用场景

基于注解的访问频率限制

需Redis

一般

一般接口,简单场景

令牌桶算法

中高

单机

允许突发流量的场景

分布式限流(Redis+Lua)

支持

一般

分布式系统,精确限流

Sentinel

中高

需额外配置

可配置

复杂系统,多维度防护

验证码与行为分析

支持

较差

敏感操作,关键业务

总结

接口防刷是一个系统性工程,需要考虑多方面因素:安全性、用户体验、性能开销和运维复杂度等。本文介绍的5种方案各有优缺点,可以根据实际需求灵活选择和组合。

无论采用哪种方案,接口防刷都应该遵循以下原则:

最小影响原则:尽量不影响正常用户的体验

梯度防护原则:根据接口的重要程度采用不同强度的防护措施

可监控原则:提供充分的监控和告警机制

灵活调整原则:支持动态调整防护参数和策略

通过合理实施接口防刷策略,可以有效提高系统的安全性和稳定性,为用户提供更好的服务体验。

除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接本文链接:https://www.lifengdi.com/article/4429

相关文章结合Apollo配置中心实现日志级别动态配置SpringBoot基于redis的分布式锁的实现(源码)SpringBoot常用注解CompletableFuture使用详解SpringBoot 中内置的 49 个常用工具类

Copyright © 2088 世界杯金靴奖_2014年巴西世界杯预选赛 - omdzds.com All Rights Reserved.
友情链接