diff --git a/src/main/java/cn/lihongjie/coal/common/Constants.java b/src/main/java/cn/lihongjie/coal/common/Constants.java index 1687781f..7c08bc10 100644 --- a/src/main/java/cn/lihongjie/coal/common/Constants.java +++ b/src/main/java/cn/lihongjie/coal/common/Constants.java @@ -14,8 +14,23 @@ public class Constants { public static final String HTTP_HEADER_CLIENT_SIGN = "X-Client-Sign"; public static final String HTTP_HEADER_CLIENT_RANDOM = "X-Client-Random"; public static final String HTTP_HEADER_TS = "X-TS"; - public static final String RATE_LIMIT_GLOBAL_SESSION_PREFIX = "global-session-rl-"; - public static final String RATE_LIMIT_GLOBAL_USER_PREFIX = "global-user-rl-"; + public static final String RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX = "global-session-min-rl-"; + public static final String RATE_LIMIT_GLOBAL_ANONYMOUS_MIN_PREFIX = "global-anonymous-min-rl-"; + public static final String RATE_LIMIT_GLOBAL_USER_MIN_PREFIX = "global-user-min-rl-"; + + + + public static final String RATE_LIMIT_GLOBAL_SESSION_HOUR_PREFIX = "global-session-hour-rl-"; + public static final String RATE_LIMIT_GLOBAL_ANONYMOUS_HOUR_PREFIX = "global-anonymous-hour-rl-"; + public static final String RATE_LIMIT_GLOBAL_USER_HOUR_PREFIX = "global-user-hour-rl-"; + + + + public static final String RATE_LIMIT_GLOBAL_SESSION_DAY_PREFIX = "global-session-day-rl-"; + public static final String RATE_LIMIT_GLOBAL_ANONYMOUS_DAY_PREFIX = "global-anonymous-day-rl-"; + public static final String RATE_LIMIT_GLOBAL_USER_DAY_PREFIX = "global-user-day-rl-"; + + public static final String CACHE_ADDRESS_TYPE = "addressType"; public static final String CACHE_CLIENT_RANDOM_PREFIX = "clientRandom::"; public static final String HTTP_HEADER_RESPONSE_TIME = "X-Response-Time"; @@ -29,4 +44,12 @@ public class Constants { public static String SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_MIN = "session_global_rate_limit_per_min"; public static String SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_MIN = "user_global_rate_limit_per_min"; public static String SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_MIN = "anonymous_global_rate_limit_per_min"; + + public static String SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_HOUR = "session_global_rate_limit_per_hour"; + public static String SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_HOUR = "user_global_rate_limit_per_hour"; + public static String SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_HOUR = "anonymous_global_rate_limit_per_hour"; + + public static String SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_DAY = "session_global_rate_limit_per_day"; + public static String SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_DAY = "user_global_rate_limit_per_day"; + public static String SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_DAY = "anonymous_global_rate_limit_per_day"; } diff --git a/src/main/java/cn/lihongjie/coal/filter/RateLimitFilter.java b/src/main/java/cn/lihongjie/coal/filter/RateLimitFilter.java index c34b22d3..d608a716 100644 --- a/src/main/java/cn/lihongjie/coal/filter/RateLimitFilter.java +++ b/src/main/java/cn/lihongjie/coal/filter/RateLimitFilter.java @@ -7,6 +7,7 @@ import cn.lihongjie.coal.common.RequestUtils; import cn.lihongjie.coal.exception.BizException; import cn.lihongjie.coal.ip.IpQueryService; import cn.lihongjie.coal.loginUser.service.LoginUserService; +import cn.lihongjie.coal.ratelimit.RateLimiterService; import cn.lihongjie.coal.sysconfig.service.SysConfigService; import cn.lihongjie.coal.syslog.service.SysLogService; @@ -22,9 +23,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.http.entity.ContentType; -import org.redisson.api.RRateLimiter; -import org.redisson.api.RateIntervalUnit; -import org.redisson.api.RateType; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; @@ -46,6 +44,8 @@ public class RateLimitFilter extends OncePerRequestFilter { @Autowired IpQueryService ipQueryService; @Autowired LoginUserService loginUserService; + @Autowired RateLimiterService rateLimiterService; + @Override public void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) @@ -63,55 +63,46 @@ public class RateLimitFilter extends OncePerRequestFilter { if (StringUtils.isNotEmpty(sessionId)) { - RRateLimiter sessionRL = - redissonClient.getRateLimiter( - Constants.RATE_LIMIT_GLOBAL_SESSION_PREFIX + sessionId); - boolean acquire = sessionRL.tryAcquire(1); - if (!acquire) { - sysLogService.saveSysLog(request, "限流模块", "会话限流", ""); + RateLimiterService.RateLimitAcquireResult acquireSessionRL = rateLimiterService.acquireSessionRL(sessionId); + if (acquireSessionRL != RateLimiterService.RateLimitAcquireResult.SUCCESS ) { + sysLogService.saveSysLog(request, "限流模块", "会话限流", acquireSessionRL.name()); writeResponse(new BizException("当前会话请求被限流,请稍后再试"), response); log.warn( - "会话限流: sessionId {} user {} request {}", + "会话限流: sessionId {} user {} request {} type {}", sessionId, Ctx.currentUser().getUsername(), + acquireSessionRL.name(), request.getRequestURI()); return; } - RRateLimiter userRL = - redissonClient.getRateLimiter( - Constants.RATE_LIMIT_GLOBAL_USER_PREFIX + Ctx.getUserId()); - - boolean acquire2 = userRL.tryAcquire(1); - if (!acquire2) { - sysLogService.saveSysLog(request, "限流模块", "用户限流", ""); - writeResponse(new BizException("当前用户请求被限流,请稍后再试"), response); + RateLimiterService.RateLimitAcquireResult acquireUserRL = rateLimiterService.acquireUserRL(Ctx.currentUser().getId()); + if (acquireUserRL != RateLimiterService.RateLimitAcquireResult.SUCCESS ) { + sysLogService.saveSysLog(request, "限流模块", "用户限流", acquireSessionRL.name()); + writeResponse(new BizException("当前会话请求被限流,请稍后再试"), response); log.warn( - "用户限流: sessionId {} user {} request {}", + "用户限流: sessionId {} user {} request {} type {}", sessionId, Ctx.currentUser().getUsername(), + acquireSessionRL.name(), request.getRequestURI()); return; } } else { - RRateLimiter rateLimiter = redissonClient.getRateLimiter("global-iprl-" + ip); - rateLimiter.trySetRate( - RateType.OVERALL, - Integer.parseInt( - sysConfigService.getConfigVal( - Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_MIN)), - 1, - RateIntervalUnit.MINUTES); - boolean acquire = rateLimiter.tryAcquire(1); - if (!acquire) { - sysLogService.saveSysLog(request, "限流模块", "IP限流", ""); + rateLimiterService.initAnonymousIpRL(ip); + + + RateLimiterService.RateLimitAcquireResult acquire = rateLimiterService.acquireIpRL(ip); + + if (acquire != RateLimiterService.RateLimitAcquireResult.SUCCESS) { + sysLogService.saveSysLog(request, "限流模块", "IP限流", acquire.name()); writeResponse(new BizException("请求被限流,请稍后再试"), response); - log.warn("ip {} request {} is rate limited", ip, request.getRequestURI()); + log.warn("ip {} request {} type {} is rate limited", ip, request.getRequestURI(), acquire.name()); return; } } diff --git a/src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java b/src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java index 6ec732c8..e0da6486 100644 --- a/src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java +++ b/src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java @@ -12,6 +12,7 @@ import cn.lihongjie.coal.loginUser.entity.LoginUserEntity; import cn.lihongjie.coal.loginUser.mapper.LoginUserMapper; import cn.lihongjie.coal.loginUser.repository.LoginUserRepository; import cn.lihongjie.coal.loginUserHis.service.LoginUserHisService; +import cn.lihongjie.coal.ratelimit.RateLimiterService; import cn.lihongjie.coal.session.SessionService; import cn.lihongjie.coal.sysconfig.service.SysConfigService; import cn.lihongjie.coal.user.service.UserService; @@ -27,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -45,8 +47,7 @@ import java.util.stream.Collectors; @Service @Slf4j @Transactional -public -class LoginUserService extends BaseService { +public class LoginUserService extends BaseService { public static final String CACHE_LOGIN_USER_BY_ID = "loginUserById"; @Autowired SysConfigService sysConfigService; @Autowired ApplicationContext applicationContext; @@ -207,22 +208,27 @@ class LoginUserService extends BaseService return dto; } + @Autowired ApplicationEventPublisher applicationEventPublisher; + + @Autowired SessionService sessionService; + + @Autowired UserService userService; + @Autowired RateLimiterService rateLimiterService; + public void deleteLogin(String sessionId) { - try{ + try { + LoginUserEntity loginUser = this.get(sessionId); - this.repository.deleteById(sessionId); - }catch (Exception e){ + rateLimiterService.destroyRL(sessionId, loginUser.getUser().getId()); + this.repository.deleteById(sessionId); + } catch (Exception e) { } this.clearCache(sessionId); } - @Autowired SessionService sessionService; - - @Autowired UserService userService; - public void logout(IdRequest request) { String sessionId = request.getId(); if (sessionId != null) { @@ -237,7 +243,6 @@ class LoginUserService extends BaseService } } - public void adminLogout(IdRequest request) { logout(request); diff --git a/src/main/java/cn/lihongjie/coal/ratelimit/RateLimiterService.java b/src/main/java/cn/lihongjie/coal/ratelimit/RateLimiterService.java new file mode 100644 index 00000000..1973fea8 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/ratelimit/RateLimiterService.java @@ -0,0 +1,305 @@ +package cn.lihongjie.coal.ratelimit; + +import static cn.lihongjie.coal.common.Constants.*; + +import cn.lihongjie.coal.common.Constants; +import cn.lihongjie.coal.sysconfig.service.SysConfigService; + +import lombok.extern.slf4j.Slf4j; + +import org.apache.commons.lang3.StringUtils; +import org.redisson.api.RRateLimiter; +import org.redisson.api.RateIntervalUnit; +import org.redisson.api.RateType; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class RateLimiterService { + + @Autowired RedissonClient redissonClient; + + @Autowired SysConfigService sysConfigService; + + public void initAnonymousIpRL(String ip) { + + if (StringUtils.isEmpty(ip)) { + throw new RuntimeException("ip is empty"); + } + + RRateLimiter rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_MIN_PREFIX + ip); + rateLimiter.trySetRate( + RateType.OVERALL, + Integer.parseInt( + sysConfigService.getConfigVal( + Constants.SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_MIN)), + 1, + RateIntervalUnit.MINUTES); + + + + rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_HOUR_PREFIX + ip); + rateLimiter.trySetRate( + RateType.OVERALL, + Integer.parseInt( + sysConfigService.getConfigVal( + SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_HOUR)), + 1, + RateIntervalUnit.HOURS); + + + + rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_DAY_PREFIX + ip); + rateLimiter.trySetRate( + RateType.OVERALL, + Integer.parseInt( + sysConfigService.getConfigVal( + SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_DAY)), + 1, + RateIntervalUnit.DAYS); + } + + public RateLimitAcquireResult acquireIpRL(String ip) { + + if (StringUtils.isEmpty(ip)) { + throw new RuntimeException("ip is empty"); + } + + RRateLimiter rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_MIN_PREFIX + ip); + + if (!rateLimiter.tryAcquire(1)) { + return RateLimitAcquireResult.MIN; + } + + rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_HOUR_PREFIX + ip); + + if (!rateLimiter.tryAcquire(1)) { + return RateLimitAcquireResult.HOUR; + } + + rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_DAY_PREFIX + ip); + + if (!rateLimiter.tryAcquire(1)) { + return RateLimitAcquireResult.DAY; + } + + return RateLimitAcquireResult.SUCCESS; + + } + + public RateLimitAcquireResult acquireSessionRL(String session) { + + if (StringUtils.isEmpty(session)) { + throw new RuntimeException("session is empty"); + } + + RRateLimiter rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX + session); + + if (!rateLimiter.tryAcquire(1)) { + return RateLimitAcquireResult.MIN; + } + + rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_SESSION_HOUR_PREFIX + session); + + if (!rateLimiter.tryAcquire(1)) { + return RateLimitAcquireResult.HOUR; + } + + rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_SESSION_DAY_PREFIX + session); + + if (!rateLimiter.tryAcquire(1)) { + return RateLimitAcquireResult.DAY; + } + return RateLimitAcquireResult.SUCCESS; + + + } + + public RateLimitAcquireResult acquireUserRL(String userId) { + + if (StringUtils.isEmpty(userId)) { + throw new RuntimeException("userId is empty"); + } + + RRateLimiter rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_USER_MIN_PREFIX + userId); + + if (!rateLimiter.tryAcquire(1)) { + return RateLimitAcquireResult.MIN; + } + + rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_USER_HOUR_PREFIX + userId); + + if (!rateLimiter.tryAcquire(1)) { + return RateLimitAcquireResult.HOUR; + } + + rateLimiter = + redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_USER_DAY_PREFIX + userId); + + if (!rateLimiter.tryAcquire(1)) { + return RateLimitAcquireResult.DAY; + } + + return RateLimitAcquireResult.SUCCESS; + } + + public void initRL(String sessionId, String userId) { + + if (StringUtils.isEmpty(sessionId)) { + throw new RuntimeException("sessionId is empty"); + } + + if (StringUtils.isEmpty(userId)) { + throw new RuntimeException("userId is empty"); + } + + // 初始化限流 + RRateLimiter sessionMinRL = + redissonClient.getRateLimiter( + Constants.RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX + sessionId); + sessionMinRL.trySetRate( + RateType.OVERALL, + Integer.parseInt( + sysConfigService.getConfigVal( + Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_MIN)), + 1, + RateIntervalUnit.MINUTES); + + RRateLimiter sessionHourRL = + redissonClient.getRateLimiter( + Constants.RATE_LIMIT_GLOBAL_SESSION_HOUR_PREFIX + sessionId); + sessionHourRL.trySetRate( + RateType.OVERALL, + Integer.parseInt( + sysConfigService.getConfigVal( + Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_HOUR)), + 1, + RateIntervalUnit.HOURS); + + RRateLimiter sessionDayRL = + redissonClient.getRateLimiter( + Constants.RATE_LIMIT_GLOBAL_SESSION_DAY_PREFIX + sessionId); + sessionDayRL.trySetRate( + RateType.OVERALL, + Integer.parseInt( + sysConfigService.getConfigVal( + Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_DAY)), + 1, + RateIntervalUnit.DAYS); + + RRateLimiter userMinRL = + redissonClient.getRateLimiter(Constants.RATE_LIMIT_GLOBAL_USER_MIN_PREFIX + userId); + userMinRL.trySetRate( + RateType.OVERALL, + Integer.parseInt( + sysConfigService.getConfigVal( + Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_MIN)), + 1, + RateIntervalUnit.MINUTES); + + RRateLimiter userHourRL = + redissonClient.getRateLimiter( + Constants.RATE_LIMIT_GLOBAL_USER_HOUR_PREFIX + userId); + userHourRL.trySetRate( + RateType.OVERALL, + Integer.parseInt( + sysConfigService.getConfigVal( + Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_HOUR)), + 1, + RateIntervalUnit.HOURS); + + RRateLimiter userDayRL = + redissonClient.getRateLimiter(Constants.RATE_LIMIT_GLOBAL_USER_DAY_PREFIX + userId); + userDayRL.trySetRate( + RateType.OVERALL, + Integer.parseInt( + sysConfigService.getConfigVal( + Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_DAY)), + 1, + RateIntervalUnit.DAYS); + } + + public void destroyRL(String sessionId, String userId) { + + if (StringUtils.isEmpty(sessionId)) { + throw new RuntimeException("sessionId is empty"); + } + + if (StringUtils.isEmpty(userId)) { + throw new RuntimeException("userId is empty"); + } + + try { + + RRateLimiter sessionMinRL = + redissonClient.getRateLimiter( + Constants.RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX + sessionId); + sessionMinRL.delete(); + } catch (Exception e) { + log.warn("", e); + } + try { + + RRateLimiter sessionHourRL = + redissonClient.getRateLimiter( + Constants.RATE_LIMIT_GLOBAL_SESSION_HOUR_PREFIX + sessionId); + sessionHourRL.delete(); + } catch (Exception e) { + log.warn("", e); + } + try { + + RRateLimiter sessionDayRL = + redissonClient.getRateLimiter( + Constants.RATE_LIMIT_GLOBAL_SESSION_DAY_PREFIX + sessionId); + sessionDayRL.delete(); + } catch (Exception e) { + log.warn("", e); + } + try { + + RRateLimiter userMinRL = + redissonClient.getRateLimiter( + Constants.RATE_LIMIT_GLOBAL_USER_MIN_PREFIX + userId); + userMinRL.delete(); + } catch (Exception e) { + log.warn("", e); + } + try { + + RRateLimiter userHourRL = + redissonClient.getRateLimiter( + Constants.RATE_LIMIT_GLOBAL_USER_HOUR_PREFIX + userId); + userHourRL.delete(); + } catch (Exception e) { + log.warn("", e); + } + try { + + RRateLimiter userDayRL = + redissonClient.getRateLimiter( + Constants.RATE_LIMIT_GLOBAL_USER_DAY_PREFIX + userId); + userDayRL.delete(); + } catch (Exception e) { + log.warn("", e); + } + } + + public enum RateLimitAcquireResult { + SUCCESS, + MIN, HOUR, DAY, + } +} diff --git a/src/main/java/cn/lihongjie/coal/session/SessionService.java b/src/main/java/cn/lihongjie/coal/session/SessionService.java index 29a991f1..cb58684f 100644 --- a/src/main/java/cn/lihongjie/coal/session/SessionService.java +++ b/src/main/java/cn/lihongjie/coal/session/SessionService.java @@ -12,6 +12,7 @@ import cn.lihongjie.coal.loginUserHis.entity.LoginUserHisEntity; import cn.lihongjie.coal.loginUserHis.service.LoginUserHisService; import cn.lihongjie.coal.organization.entity.OrganizationEntity; import cn.lihongjie.coal.organization.service.OrganizationService; +import cn.lihongjie.coal.ratelimit.RateLimiterService; import cn.lihongjie.coal.sysconfig.service.SysConfigService; import cn.lihongjie.coal.user.dto.UserDto; import cn.lihongjie.coal.user.entity.UserEntity; @@ -28,9 +29,6 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.redisson.api.RRateLimiter; -import org.redisson.api.RateIntervalUnit; -import org.redisson.api.RateType; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; @@ -97,6 +95,8 @@ public class SessionService { @Autowired RedissonClient redissonClient; + @Autowired RateLimiterService rateLimiterService; + @SneakyThrows public void login(LoginDto dto) { HttpServletRequest request = @@ -182,29 +182,11 @@ public class SessionService { his.setSessionId(sessionId); - // 初始化限流 - RRateLimiter sessionRL = redissonClient.getRateLimiter(Constants.RATE_LIMIT_GLOBAL_SESSION_PREFIX + sessionId); - sessionRL.trySetRate( - RateType.OVERALL, - Integer.parseInt( - sysConfigService.getConfigVal( - Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_MIN)), - 1, - RateIntervalUnit.MINUTES); - - - RRateLimiter userRL = redissonClient.getRateLimiter(Constants.RATE_LIMIT_GLOBAL_USER_PREFIX + user.getId()); - userRL.trySetRate( - RateType.OVERALL, - Integer.parseInt( - sysConfigService.getConfigVal( - Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_MIN)), - 1, - RateIntervalUnit.MINUTES); loginUserHisService.save(his); + rateLimiterService.initRL(sessionId, user.getId()); } catch (Exception e) { diff --git a/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java b/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java index 91409403..c9ea6bf8 100644 --- a/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java +++ b/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java @@ -117,6 +117,63 @@ public class SysConfigService extends BaseService