| | |
| | | import java.util.Locale; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import com.ruoyi.common.core.constant.CacheConstants; |
| | | import com.ruoyi.common.core.exception.ServiceException; |
| | | import com.ruoyi.common.core.utils.StringUtils; |
| | | import com.ruoyi.common.core.utils.ip.IpUtils; |
| | | import com.ruoyi.common.redis.service.RedisService; |
| | | |
| | | /** |
| | | * 登录频率限制服务 |
| | | * 登录频率限制服务(账号维度) |
| | | */ |
| | | @Component |
| | | public class LoginRateLimitService |
| | | { |
| | | private static final String UNKNOWN = "unknown"; |
| | | private static final String SESSION_HEADER = "X-Login-Session"; |
| | | |
| | | @Autowired |
| | | private RedisService redisService; |
| | | |
| | | /** |
| | | * 按 IP、账号、登录会话三个维度限制登录尝试频率。 |
| | | * 按账号维度限制登录尝试频率。 |
| | | * IP、登录会话维度已由网关 LoginRateLimitFilter 在验证码校验之前处理 |
| | | * (验证码错误的请求不会到达本服务,若在 auth 重复计数会与网关双重计数)。 |
| | | */ |
| | | public void checkLoginRate(String username, HttpServletRequest request) |
| | | public void checkLoginRate(String username) |
| | | { |
| | | String normalizedUsername = StringUtils.defaultString(username).trim().toLowerCase(Locale.ROOT); |
| | | String ip = StringUtils.defaultIfEmpty(IpUtils.getIpAddr(request), UNKNOWN); |
| | | String sessionId = resolveSessionId(request); |
| | | |
| | | checkDimension("ip", ip); |
| | | checkDimension("account", normalizedUsername); |
| | | checkDimension("session", sessionId); |
| | | } |
| | | |
| | | /** |
| | | * 清除账号维度的登录限流计数(登录成功后调用)。 |
| | | */ |
| | | public void clearAccountRecord(String username) |
| | | { |
| | | String normalizedUsername = StringUtils.defaultString(username).trim().toLowerCase(Locale.ROOT); |
| | | String key = CacheConstants.LOGIN_RATE_LIMIT_KEY + "account:" + digest(normalizedUsername); |
| | | if (redisService.hasKey(key)) |
| | | { |
| | | redisService.deleteObject(key); |
| | | } |
| | | } |
| | | |
| | | private void checkDimension(String dimension, String identity) |
| | |
| | | long retryAfter = remainingSeconds > 0 ? remainingSeconds : CacheConstants.LOGIN_RATE_LIMIT_WINDOW; |
| | | throw new ServiceException(String.format("登录尝试过于频繁,请%s秒后重试", retryAfter)); |
| | | } |
| | | } |
| | | |
| | | private String resolveSessionId(HttpServletRequest request) |
| | | { |
| | | String sessionId = request.getHeader(SESSION_HEADER); |
| | | if (StringUtils.isNotEmpty(sessionId)) |
| | | { |
| | | return sessionId; |
| | | } |
| | | String userAgent = StringUtils.defaultString(request.getHeader("User-Agent")); |
| | | return IpUtils.getIpAddr(request) + "|" + userAgent; |
| | | } |
| | | |
| | | private String digest(String value) |