ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/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')") ruoyi-admin/src/main/resources/application.yml
@@ -39,6 +39,14 @@ com.ruoyi: debug org.springframework: warn # 用户配置 user: password: # 密码最大错误次数 maxRetryCount: 5 # 密码锁定时间(默认10分钟) lockTime: 10 # Spring配置 spring: # 资源信息 ruoyi-admin/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=角色已封禁,请联系管理员 ruoyi-common/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:"; } ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
@@ -75,6 +75,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 缓存键值 ruoyi-common/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 }); } } ruoyi-framework/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(); } } ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
@@ -23,6 +23,7 @@ import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.framework.security.context.AuthenticationContextHolder; import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.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) { ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java
对比新文件 @@ -0,0 +1,94 @@ package com.ruoyi.framework.web.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.core.domain.entity.SysUser; import com.ruoyi.common.core.redis.RedisCache; 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.security.context.AuthenticationContextHolder; /** * 登录密码方法 * * @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)); } } } ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java
@@ -26,6 +26,9 @@ @Autowired private ISysUserService userService; @Autowired private SysPasswordService passwordService; @Autowired private SysPermissionService permissionService; @@ -50,6 +53,8 @@ throw new ServiceException("对不起,您的账号:" + username + " 已停用"); } passwordService.validate(user); return createLoginUser(user); }