feat: 增加ip限流控制
使用系统配置增加ip限流控制
This commit is contained in:
@@ -12,4 +12,14 @@ public interface RedisKeyConstants {
|
||||
* 系统配置Redis-key
|
||||
*/
|
||||
String SYSTEM_CONFIG_KEY = "system:config";
|
||||
|
||||
/**
|
||||
* IP限流Redis-key
|
||||
*/
|
||||
String IP_RATE_LIMITER_KEY = "ip:rate:limiter:";
|
||||
|
||||
/**
|
||||
* 防重复提交Redis-key
|
||||
*/
|
||||
String RESUBMIT_LOCK_PREFIX = "resubmit:lock:";
|
||||
}
|
||||
|
||||
@@ -23,4 +23,16 @@ public interface SystemConstants {
|
||||
*/
|
||||
String ROOT_ROLE_CODE = "ROOT";
|
||||
|
||||
/**
|
||||
* IP限流最大分钟数配置系统配置KEY
|
||||
*/
|
||||
String CONFIG_IP_RATE_LIMIT_MINUTE_KEY = "IP_RATE_LIMIT_MINUTE";
|
||||
|
||||
/**
|
||||
* IP限流次数配置系统配置KEY
|
||||
* 在最大分钟数内,允许访问的次数
|
||||
* @since 1.0.0
|
||||
*/
|
||||
String CONFIG_IP_RATE_LIMIT_COUNT_KEY = "IP_RATE_LIMIT_COUNT";
|
||||
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ public enum ResultCode implements IResultCode, Serializable {
|
||||
SYSTEM_EXECUTION_TIMEOUT("B0100", "系统执行超时"),
|
||||
SYSTEM_ORDER_PROCESSING_TIMEOUT("B0100", "系统订单处理超时"),
|
||||
|
||||
SYSTEM_DISASTER_RECOVERY_TRIGGER("B0200", "系统容灾功能被出发"),
|
||||
FLOW_LIMITING("B0210", "系统限流"),
|
||||
SYSTEM_DISASTER_RECOVERY_TRIGGER("B0200", "系统容灾功能被触发"),
|
||||
FLOW_LIMITING("B0210", "系统限流,请稍后再试"),
|
||||
DEGRADATION("B0220", "系统功能降级"),
|
||||
|
||||
SYSTEM_RESOURCE_ERROR("B0300", "系统资源异常"),
|
||||
|
||||
@@ -4,10 +4,12 @@ import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.youlai.system.common.constant.SecurityConstants;
|
||||
import com.youlai.system.config.property.SecurityProperties;
|
||||
import com.youlai.system.filter.RedisRateLimiterFilter;
|
||||
import com.youlai.system.security.exception.MyAccessDeniedHandler;
|
||||
import com.youlai.system.security.exception.MyAuthenticationEntryPoint;
|
||||
import com.youlai.system.filter.JwtValidationFilter;
|
||||
import com.youlai.system.filter.CaptchaValidationFilter;
|
||||
import com.youlai.system.service.SysConfigService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -43,6 +45,7 @@ public class SecurityConfig {
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final CodeGenerator codeGenerator;
|
||||
private final SecurityProperties securityProperties;
|
||||
private final SysConfigService sysConfigService;
|
||||
|
||||
|
||||
|
||||
@@ -64,7 +67,8 @@ public class SecurityConfig {
|
||||
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
||||
|
||||
;
|
||||
|
||||
// 限流过滤器
|
||||
http.addFilterBefore(new RedisRateLimiterFilter(redisTemplate, sysConfigService), UsernamePasswordAuthenticationFilter.class);
|
||||
// 验证码校验过滤器
|
||||
http.addFilterBefore(new CaptchaValidationFilter(redisTemplate, codeGenerator), UsernamePasswordAuthenticationFilter.class);
|
||||
// JWT 校验过滤器
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.youlai.system.filter;
|
||||
|
||||
import com.youlai.system.common.constant.RedisKeyConstants;
|
||||
import com.youlai.system.common.constant.SystemConstants;
|
||||
import com.youlai.system.common.result.ResultCode;
|
||||
import com.youlai.system.service.SysConfigService;
|
||||
import com.youlai.system.util.IPUtils;
|
||||
import com.youlai.system.util.ResponseUtils;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* IP限流过滤器
|
||||
*
|
||||
* @author Theo
|
||||
* @since 2024/08/10 14:38
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedisRateLimiterFilter extends OncePerRequestFilter {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final SysConfigService sysConfigService;
|
||||
|
||||
public RedisRateLimiterFilter(RedisTemplate<String, Object> redisTemplate, SysConfigService sysConfigService) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.sysConfigService = sysConfigService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认是否限流方法
|
||||
* 默认情况下:限制同一个IP在一分钟内只能访问10次,可以通过修改系统配置进行调整
|
||||
* 这里也可以进行扩展,比如redis记录同一个ip每天出发限流的上限次数,记录在redis中,达到某个阈值后,进行永久封禁这·
|
||||
*
|
||||
* @param ip ip地址
|
||||
* @return 是否限流
|
||||
*/
|
||||
public boolean rateLimit(String ip) {
|
||||
String key = RedisKeyConstants.IP_RATE_LIMITER_KEY + ip;
|
||||
Long count = redisTemplate.opsForValue().increment(key);
|
||||
if (count == null || count == 1) {
|
||||
Object ipRateLimitMinute = sysConfigService.getSystemConfig(SystemConstants.CONFIG_IP_RATE_LIMIT_MINUTE_KEY);
|
||||
long expire = 1;
|
||||
if(ipRateLimitMinute != null){
|
||||
expire = Long.parseLong(ipRateLimitMinute.toString());
|
||||
}else {
|
||||
log.warn("[RedisRateLimiterFilter.rateLimit]系统配置中未配置IP限流最大分钟数,使用默认分钟数:{},请检查配置项:{}",
|
||||
expire,SystemConstants.CONFIG_IP_RATE_LIMIT_MINUTE_KEY);
|
||||
}
|
||||
redisTemplate.expire(key,expire, TimeUnit.MINUTES);
|
||||
}
|
||||
Object systemConfig = sysConfigService.getSystemConfig(SystemConstants.CONFIG_IP_RATE_LIMIT_COUNT_KEY);
|
||||
long limit = 10;
|
||||
if(systemConfig != null){
|
||||
limit = Long.parseLong(systemConfig.toString());
|
||||
}else{
|
||||
log.warn("[RedisRateLimiterFilter.rateLimit]系统配置中未配置IP限流次数配置,使用默认次数:{},请检查配置项:{}",
|
||||
limit,SystemConstants.CONFIG_IP_RATE_LIMIT_COUNT_KEY);
|
||||
}
|
||||
return count != null && count > limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* IP限流过滤器
|
||||
* 默认情况下:限制同一个IP在一分钟内只能访问10次,可以通过修改系统配置进行调整
|
||||
*
|
||||
* @param request 请求体
|
||||
* @param response 响应体
|
||||
* @param filterChain 过滤器链
|
||||
*/
|
||||
@Override
|
||||
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
|
||||
@NotNull FilterChain filterChain) throws ServletException, IOException {
|
||||
String ip = IPUtils.getIpAddr(request);
|
||||
if (rateLimit(ip)) {
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.FLOW_LIMITING);
|
||||
return;
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.youlai.system.plugin.norepeat.aspect;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.jwt.JWTUtil;
|
||||
import cn.hutool.jwt.RegisteredPayload;
|
||||
import com.youlai.system.common.constant.RedisKeyConstants;
|
||||
import com.youlai.system.common.constant.SecurityConstants;
|
||||
import com.youlai.system.exception.BusinessException;
|
||||
import com.youlai.system.common.result.ResultCode;
|
||||
@@ -36,7 +37,6 @@ import java.util.concurrent.TimeUnit;
|
||||
public class DuplicateSubmitAspect {
|
||||
|
||||
private final RedissonClient redissonClient;
|
||||
private static final String RESUBMIT_LOCK_PREFIX = "LOCK:RESUBMIT:";
|
||||
|
||||
/**
|
||||
* 防重复提交切点
|
||||
@@ -74,7 +74,7 @@ public class DuplicateSubmitAspect {
|
||||
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
|
||||
// 从 JWT Token 中获取 jti
|
||||
String jti = (String) JWTUtil.parseToken(token).getPayload(RegisteredPayload.JWT_ID);
|
||||
resubmitLockKey = RESUBMIT_LOCK_PREFIX + jti + ":" + request.getMethod() + "-" + request.getRequestURI();
|
||||
resubmitLockKey = RedisKeyConstants.RESUBMIT_LOCK_PREFIX + jti + ":" + request.getMethod() + "-" + request.getRequestURI();
|
||||
}
|
||||
return resubmitLockKey;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.youlai.system.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
@@ -10,6 +9,7 @@ import com.youlai.system.converter.SysConfigConverter;
|
||||
import com.youlai.system.model.form.ConfigForm;
|
||||
import com.youlai.system.model.query.ConfigPageQuery;
|
||||
import com.youlai.system.security.util.SecurityUtils;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
@@ -40,6 +40,15 @@ public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfig
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
|
||||
/**
|
||||
* 系统启动完成后,加载系统配置到缓存
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
refreshCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询系统配置
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user