RuoYi
2022-07-30 ff8206323dc1b88c9373a3ee5cf5cb8e86ef60d0
支持配置密码最大错误次数/锁定时间
已修改8个文件
已添加3个文件
208 ■■■■■ 文件已修改
src/main/java/com/ruoyi/common/constant/CacheConstants.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/redis/RedisCache.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/service/SysLoginService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/service/SysPasswordService.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/service/UserDetailsServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/web/domain/AjaxResult.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/CacheController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/i18n/messages.properties 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/constant/CacheConstants.java
@@ -36,4 +36,9 @@
     * é™æµ redis key
     */
    public static final String RATE_LIMIT_KEY = "rate_limit:";
    /**
     * ç™»å½•账户密码错误次数 redis key
     */
    public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
}
src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.common.exception.user;
/**
 * ç”¨æˆ·é”™è¯¯æœ€å¤§æ¬¡æ•°å¼‚常类
 *
 * @author ruoyi
 */
public class UserPasswordRetryLimitExceedException extends UserException
{
    private static final long serialVersionUID = 1L;
    public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime)
    {
        super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime });
    }
}
src/main/java/com/ruoyi/framework/redis/RedisCache.java
@@ -62,6 +62,28 @@
    }
    /**
     * èŽ·å–æœ‰æ•ˆæ—¶é—´
     *
     * @param key Redis键
     * @return æœ‰æ•ˆæ—¶é—´
     */
    public long getExpire(final String key)
    {
        return redisTemplate.getExpire(key);
    }
    /**
     * åˆ¤æ–­ key是否存在
     *
     * @param key é”®
     * @return true å­˜åœ¨ false不存在
     */
    public Boolean hasKey(String key)
    {
        return redisTemplate.hasKey(key);
    }
    /**
     * è®¾ç½®æœ‰æ•ˆæ—¶é—´
     *
     * @param key Redis键
src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.framework.security.context;
import org.springframework.security.core.Authentication;
/**
 * èº«ä»½éªŒè¯ä¿¡æ¯
 *
 * @author ruoyi
 */
public class AuthenticationContextHolder
{
    private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();
    public static Authentication getContext()
    {
        return contextHolder.get();
    }
    public static void setContext(Authentication context)
    {
        contextHolder.set(context);
    }
    public static void clearContext()
    {
        contextHolder.remove();
    }
}
src/main/java/com/ruoyi/framework/security/service/SysLoginService.java
@@ -22,6 +22,7 @@
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.redis.RedisCache;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.service.ISysConfigService;
import com.ruoyi.project.system.service.ISysUserService;
@@ -70,9 +71,10 @@
        Authentication authentication = null;
        try
        {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // è¯¥æ–¹æ³•会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
            authentication = authenticationManager.authenticate(authenticationToken);
        }
        catch (Exception e)
        {
src/main/java/com/ruoyi/framework/security/service/SysPasswordService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
package com.ruoyi.framework.security.service;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.redis.RedisCache;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.project.system.domain.SysUser;
/**
 * ç™»å½•密码方法
 *
 * @author ruoyi
 */
@Component
public class SysPasswordService
{
    @Autowired
    private RedisCache redisCache;
    @Value(value = "${user.password.maxRetryCount}")
    private int maxRetryCount;
    @Value(value = "${user.password.lockTime}")
    private int lockTime;
    /**
     * ç™»å½•账户密码错误次数缓存键名
     *
     * @param username ç”¨æˆ·å
     * @return ç¼“存键key
     */
    private String getCacheKey(String username)
    {
        return CacheConstants.PWD_ERR_CNT_KEY + username;
    }
    public void validate(SysUser user)
    {
        Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
        String username = usernamePasswordAuthenticationToken.getName();
        String password = usernamePasswordAuthenticationToken.getCredentials().toString();
        Integer retryCount = redisCache.getCacheObject(getCacheKey(username));
        if (retryCount == null)
        {
            retryCount = 0;
        }
        if (retryCount >= Integer.valueOf(maxRetryCount).intValue())
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
                    MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime)));
            throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);
        }
        if (!matches(user, password))
        {
            retryCount = retryCount + 1;
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
                    MessageUtils.message("user.password.retry.limit.count", retryCount)));
            redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
            throw new UserPasswordNotMatchException();
        }
        else
        {
            clearLoginRecordCache(username);
        }
    }
    public boolean matches(SysUser user, String rawPassword)
    {
        return SecurityUtils.matchesPassword(rawPassword, user.getPassword());
    }
    public void clearLoginRecordCache(String loginName)
    {
        if (redisCache.hasKey(getCacheKey(loginName)))
        {
            redisCache.deleteObject(getCacheKey(loginName));
        }
    }
}
src/main/java/com/ruoyi/framework/security/service/UserDetailsServiceImpl.java
@@ -25,6 +25,9 @@
    @Autowired
    private ISysUserService userService;
    @Autowired
    private SysPasswordService passwordService;
    @Autowired
    private SysPermissionService permissionService;
@@ -49,6 +52,8 @@
            throw new ServiceException("对不起,您的账号:" + username + " å·²åœç”¨");
        }
        passwordService.validate(user);
        return createLoginUser(user);
    }
src/main/java/com/ruoyi/framework/web/domain/AjaxResult.java
@@ -1,6 +1,7 @@
package com.ruoyi.framework.web.domain;
import java.util.HashMap;
import java.util.Objects;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.utils.StringUtils;
@@ -147,6 +148,26 @@
    }
    /**
     * æ˜¯å¦ä¸ºæˆåŠŸæ¶ˆæ¯
     *
     * @return ç»“æžœ
     */
    public boolean isSuccess()
    {
        return !isError();
    }
    /**
     * æ˜¯å¦ä¸ºé”™è¯¯æ¶ˆæ¯
     *
     * @return ç»“æžœ
     */
    public boolean isError()
    {
        return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG));
    }
    /**
     * æ–¹ä¾¿é“¾å¼è°ƒç”¨
     *
     * @param key é”®
src/main/java/com/ruoyi/project/monitor/controller/CacheController.java
@@ -41,6 +41,7 @@
        caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码"));
        caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交"));
        caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理"));
        caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
    }
    @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
src/main/resources/application.yml
@@ -39,6 +39,14 @@
    com.ruoyi: debug
    org.springframework: warn
# ç”¨æˆ·é…ç½®
user:
  password:
    # å¯†ç æœ€å¤§é”™è¯¯æ¬¡æ•°
    maxRetryCount: 5
    # å¯†ç é”å®šæ—¶é—´ï¼ˆé»˜è®¤10分钟)
    lockTime: 10
# Spring配置
spring:
  # èµ„源信息
src/main/resources/i18n/messages.properties
@@ -5,7 +5,7 @@
user.not.exists=用户不存在/密码错误
user.password.not.match=用户不存在/密码错误
user.password.retry.limit.count=密码输入错误{0}次
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
user.password.delete=对不起,您的账号已被删除
user.blocked=用户已封禁,请联系管理员
role.blocked=角色已封禁,请联系管理员