feat: 增加ip限流控制

使用系统配置增加ip限流控制
This commit is contained in:
Theo
2024-08-10 15:01:04 +08:00
parent b5ec1d7579
commit faa5f1a9a8
9 changed files with 136 additions and 6 deletions

View File

@@ -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:";
}

View File

@@ -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";
}

View File

@@ -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", "系统资源异常"),

View File

@@ -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 校验过滤器

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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();
}
/**
* 分页查询系统配置
*