增加管理后台登录频次校验和连续错误登录后限制登录
修改管理后台验证码生成和登录校验
1个文件已添加
4个文件已修改
92 ■■■■■ 已修改文件
ruoyi-auth/src/main/java/com/ruoyi/auth/service/LoginRateLimitService.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysPasswordService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/LoginRateLimitFilter.java 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-auth/src/main/java/com/ruoyi/auth/service/LoginRateLimitService.java
@@ -6,41 +6,45 @@
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)
@@ -53,17 +57,6 @@
            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)
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java
@@ -47,8 +47,10 @@
     * 登录
     */
    public LoginUser login(String username, String password, HttpServletRequest request) {
        loginRateLimitService.checkLoginRate(username, request);
        username = StringUtils.trim(username);
        // 优先校验账号是否已锁定(锁定期内直接提示,避免被限流提示掩盖)
        passwordService.checkLocked(username, request);
        loginRateLimitService.checkLoginRate(username);
        // 查询用户信息
        R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
        
@@ -97,6 +99,8 @@
            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
        }
        passwordService.validate(user, password, request);
        // 登录成功,清除账号维度的登录限流计数,避免正常用户短时间再次登录被误伤
        loginRateLimitService.clearAccountRecord(username);
        recordLogService.recordLogininfor(request, user.getUserId().intValue(), username, Constants.LOGIN_SUCCESS_STATUS, "登录成功");
        return userInfo;
    }
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysPasswordService.java
@@ -41,11 +41,28 @@
        return CacheConstants.PWD_ERR_CNT_KEY + username;
    }
    /**
     * 检查账号是否处于锁定状态(锁定期内直接抛出锁定提示)。
     * 在限流之前调用,避免锁定提示被"登录尝试过于频繁"掩盖。
     *
     * @param username 用户名
     * @param request 请求
     */
    public void checkLocked(String username, HttpServletRequest request)
    {
        Integer retryCount = redisService.getIntegerCacheObject(getCacheKey(username));
        if (retryCount != null && retryCount >= maxRetryCount)
        {
            String errMsg = String.format("密码输入错误%s次,帐户锁定%s分钟", maxRetryCount, lockTime);
            throw new ServiceException(errMsg);
        }
    }
    public void validate(SysUser user, String password, HttpServletRequest request)
    {
        String username = user.getUserName();
        Integer retryCount = redisService.getCacheObject(getCacheKey(username));
        Integer retryCount = redisService.getIntegerCacheObject(getCacheKey(username));
        if (retryCount == null)
        {
ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java
@@ -84,6 +84,30 @@
            Long.class);
    /**
     * 获取缓存值并转为 Integer(使用 String 序列化读取,
     * 避免 FastJson2 对纯数字字符串反序列化的类型不确定性)
     *
     * @param key 缓存键值
     * @return 缓存值对应的 Integer,key 不存在或值非数字时返回 null
     */
    public Integer getIntegerCacheObject(final String key)
    {
        String value = stringRedisTemplate.opsForValue().get(key);
        if (value == null)
        {
            return null;
        }
        try
        {
            return Integer.valueOf(value);
        }
        catch (NumberFormatException e)
        {
            return null;
        }
    }
    /**
     * 设置有效时间
     *
     * @param key Redis键
ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/LoginRateLimitFilter.java