正在显示
10 个修改的文件
包含
184 行增加
和
3 行删除
| @@ -41,6 +41,7 @@ public class CacheController | @@ -41,6 +41,7 @@ public class CacheController | ||
| 41 | caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); | 41 | caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); |
| 42 | caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交")); | 42 | caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交")); |
| 43 | caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理")); | 43 | caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理")); |
| 44 | + caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数")); | ||
| 44 | } | 45 | } |
| 45 | 46 | ||
| 46 | @PreAuthorize("@ss.hasPermi('monitor:cache:list')") | 47 | @PreAuthorize("@ss.hasPermi('monitor:cache:list')") |
| @@ -39,6 +39,14 @@ logging: | @@ -39,6 +39,14 @@ logging: | ||
| 39 | com.ruoyi: debug | 39 | com.ruoyi: debug |
| 40 | org.springframework: warn | 40 | org.springframework: warn |
| 41 | 41 | ||
| 42 | +# 用户配置 | ||
| 43 | +user: | ||
| 44 | + password: | ||
| 45 | + # 密码最大错误次数 | ||
| 46 | + maxRetryCount: 5 | ||
| 47 | + # 密码锁定时间(默认10分钟) | ||
| 48 | + lockTime: 10 | ||
| 49 | + | ||
| 42 | # Spring配置 | 50 | # Spring配置 |
| 43 | spring: | 51 | spring: |
| 44 | # 资源信息 | 52 | # 资源信息 |
| @@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效 | @@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效 | ||
| 5 | user.not.exists=用户不存在/密码错误 | 5 | user.not.exists=用户不存在/密码错误 |
| 6 | user.password.not.match=用户不存在/密码错误 | 6 | user.password.not.match=用户不存在/密码错误 |
| 7 | user.password.retry.limit.count=密码输入错误{0}次 | 7 | user.password.retry.limit.count=密码输入错误{0}次 |
| 8 | -user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟 | 8 | +user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟 |
| 9 | user.password.delete=对不起,您的账号已被删除 | 9 | user.password.delete=对不起,您的账号已被删除 |
| 10 | user.blocked=用户已封禁,请联系管理员 | 10 | user.blocked=用户已封禁,请联系管理员 |
| 11 | role.blocked=角色已封禁,请联系管理员 | 11 | role.blocked=角色已封禁,请联系管理员 |
| @@ -36,4 +36,9 @@ public class CacheConstants | @@ -36,4 +36,9 @@ public class CacheConstants | ||
| 36 | * 限流 redis key | 36 | * 限流 redis key |
| 37 | */ | 37 | */ |
| 38 | public static final String RATE_LIMIT_KEY = "rate_limit:"; | 38 | public static final String RATE_LIMIT_KEY = "rate_limit:"; |
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * 登录账户密码错误次数 redis key | ||
| 42 | + */ | ||
| 43 | + public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; | ||
| 39 | } | 44 | } |
| @@ -75,6 +75,28 @@ public class RedisCache | @@ -75,6 +75,28 @@ public class RedisCache | ||
| 75 | } | 75 | } |
| 76 | 76 | ||
| 77 | /** | 77 | /** |
| 78 | + * 获取有效时间 | ||
| 79 | + * | ||
| 80 | + * @param key Redis键 | ||
| 81 | + * @return 有效时间 | ||
| 82 | + */ | ||
| 83 | + public long getExpire(final String key) | ||
| 84 | + { | ||
| 85 | + return redisTemplate.getExpire(key); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + /** | ||
| 89 | + * 判断 key是否存在 | ||
| 90 | + * | ||
| 91 | + * @param key 键 | ||
| 92 | + * @return true 存在 false不存在 | ||
| 93 | + */ | ||
| 94 | + public Boolean hasKey(String key) | ||
| 95 | + { | ||
| 96 | + return redisTemplate.hasKey(key); | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + /** | ||
| 78 | * 获得缓存的基本对象。 | 100 | * 获得缓存的基本对象。 |
| 79 | * | 101 | * |
| 80 | * @param key 缓存键值 | 102 | * @param key 缓存键值 |
| 1 | +package com.ruoyi.common.exception.user; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 用户错误最大次数异常类 | ||
| 5 | + * | ||
| 6 | + * @author ruoyi | ||
| 7 | + */ | ||
| 8 | +public class UserPasswordRetryLimitExceedException extends UserException | ||
| 9 | +{ | ||
| 10 | + private static final long serialVersionUID = 1L; | ||
| 11 | + | ||
| 12 | + public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) | ||
| 13 | + { | ||
| 14 | + super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime }); | ||
| 15 | + } | ||
| 16 | +} |
ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java
0 → 100644
| 1 | +package com.ruoyi.framework.security.context; | ||
| 2 | + | ||
| 3 | +import org.springframework.security.core.Authentication; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * 身份验证信息 | ||
| 7 | + * | ||
| 8 | + * @author ruoyi | ||
| 9 | + */ | ||
| 10 | +public class AuthenticationContextHolder | ||
| 11 | +{ | ||
| 12 | + private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>(); | ||
| 13 | + | ||
| 14 | + public static Authentication getContext() | ||
| 15 | + { | ||
| 16 | + return contextHolder.get(); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + public static void setContext(Authentication context) | ||
| 20 | + { | ||
| 21 | + contextHolder.set(context); | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + public static void clearContext() | ||
| 25 | + { | ||
| 26 | + contextHolder.remove(); | ||
| 27 | + } | ||
| 28 | +} |
| @@ -23,6 +23,7 @@ import com.ruoyi.common.utils.StringUtils; | @@ -23,6 +23,7 @@ import com.ruoyi.common.utils.StringUtils; | ||
| 23 | import com.ruoyi.common.utils.ip.IpUtils; | 23 | import com.ruoyi.common.utils.ip.IpUtils; |
| 24 | import com.ruoyi.framework.manager.AsyncManager; | 24 | import com.ruoyi.framework.manager.AsyncManager; |
| 25 | import com.ruoyi.framework.manager.factory.AsyncFactory; | 25 | import com.ruoyi.framework.manager.factory.AsyncFactory; |
| 26 | +import com.ruoyi.framework.security.context.AuthenticationContextHolder; | ||
| 26 | import com.ruoyi.system.service.ISysConfigService; | 27 | import com.ruoyi.system.service.ISysConfigService; |
| 27 | import com.ruoyi.system.service.ISysUserService; | 28 | import com.ruoyi.system.service.ISysUserService; |
| 28 | 29 | ||
| @@ -70,9 +71,10 @@ public class SysLoginService | @@ -70,9 +71,10 @@ public class SysLoginService | ||
| 70 | Authentication authentication = null; | 71 | Authentication authentication = null; |
| 71 | try | 72 | try |
| 72 | { | 73 | { |
| 74 | + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); | ||
| 75 | + AuthenticationContextHolder.setContext(authenticationToken); | ||
| 73 | // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername | 76 | // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername |
| 74 | - authentication = authenticationManager | ||
| 75 | - .authenticate(new UsernamePasswordAuthenticationToken(username, password)); | 77 | + authentication = authenticationManager.authenticate(authenticationToken); |
| 76 | } | 78 | } |
| 77 | catch (Exception e) | 79 | catch (Exception e) |
| 78 | { | 80 | { |
| 1 | +package com.ruoyi.framework.web.service; | ||
| 2 | + | ||
| 3 | +import java.util.concurrent.TimeUnit; | ||
| 4 | +import org.springframework.beans.factory.annotation.Autowired; | ||
| 5 | +import org.springframework.beans.factory.annotation.Value; | ||
| 6 | +import org.springframework.security.core.Authentication; | ||
| 7 | +import org.springframework.stereotype.Component; | ||
| 8 | +import com.ruoyi.common.constant.CacheConstants; | ||
| 9 | +import com.ruoyi.common.constant.Constants; | ||
| 10 | +import com.ruoyi.common.core.domain.entity.SysUser; | ||
| 11 | +import com.ruoyi.common.core.redis.RedisCache; | ||
| 12 | +import com.ruoyi.common.exception.user.UserPasswordNotMatchException; | ||
| 13 | +import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException; | ||
| 14 | +import com.ruoyi.common.utils.MessageUtils; | ||
| 15 | +import com.ruoyi.common.utils.SecurityUtils; | ||
| 16 | +import com.ruoyi.framework.manager.AsyncManager; | ||
| 17 | +import com.ruoyi.framework.manager.factory.AsyncFactory; | ||
| 18 | +import com.ruoyi.framework.security.context.AuthenticationContextHolder; | ||
| 19 | + | ||
| 20 | +/** | ||
| 21 | + * 登录密码方法 | ||
| 22 | + * | ||
| 23 | + * @author ruoyi | ||
| 24 | + */ | ||
| 25 | +@Component | ||
| 26 | +public class SysPasswordService | ||
| 27 | +{ | ||
| 28 | + @Autowired | ||
| 29 | + private RedisCache redisCache; | ||
| 30 | + | ||
| 31 | + @Value(value = "${user.password.maxRetryCount}") | ||
| 32 | + private int maxRetryCount; | ||
| 33 | + | ||
| 34 | + @Value(value = "${user.password.lockTime}") | ||
| 35 | + private int lockTime; | ||
| 36 | + | ||
| 37 | + /** | ||
| 38 | + * 登录账户密码错误次数缓存键名 | ||
| 39 | + * | ||
| 40 | + * @param username 用户名 | ||
| 41 | + * @return 缓存键key | ||
| 42 | + */ | ||
| 43 | + private String getCacheKey(String username) | ||
| 44 | + { | ||
| 45 | + return CacheConstants.PWD_ERR_CNT_KEY + username; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public void validate(SysUser user) | ||
| 49 | + { | ||
| 50 | + Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext(); | ||
| 51 | + String username = usernamePasswordAuthenticationToken.getName(); | ||
| 52 | + String password = usernamePasswordAuthenticationToken.getCredentials().toString(); | ||
| 53 | + | ||
| 54 | + Integer retryCount = redisCache.getCacheObject(getCacheKey(username)); | ||
| 55 | + | ||
| 56 | + if (retryCount == null) | ||
| 57 | + { | ||
| 58 | + retryCount = 0; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) | ||
| 62 | + { | ||
| 63 | + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, | ||
| 64 | + MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime))); | ||
| 65 | + throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + if (!matches(user, password)) | ||
| 69 | + { | ||
| 70 | + retryCount = retryCount + 1; | ||
| 71 | + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, | ||
| 72 | + MessageUtils.message("user.password.retry.limit.count", retryCount))); | ||
| 73 | + redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES); | ||
| 74 | + throw new UserPasswordNotMatchException(); | ||
| 75 | + } | ||
| 76 | + else | ||
| 77 | + { | ||
| 78 | + clearLoginRecordCache(username); | ||
| 79 | + } | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + public boolean matches(SysUser user, String rawPassword) | ||
| 83 | + { | ||
| 84 | + return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + public void clearLoginRecordCache(String loginName) | ||
| 88 | + { | ||
| 89 | + if (redisCache.hasKey(getCacheKey(loginName))) | ||
| 90 | + { | ||
| 91 | + redisCache.deleteObject(getCacheKey(loginName)); | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | +} |
| @@ -28,6 +28,9 @@ public class UserDetailsServiceImpl implements UserDetailsService | @@ -28,6 +28,9 @@ public class UserDetailsServiceImpl implements UserDetailsService | ||
| 28 | private ISysUserService userService; | 28 | private ISysUserService userService; |
| 29 | 29 | ||
| 30 | @Autowired | 30 | @Autowired |
| 31 | + private SysPasswordService passwordService; | ||
| 32 | + | ||
| 33 | + @Autowired | ||
| 31 | private SysPermissionService permissionService; | 34 | private SysPermissionService permissionService; |
| 32 | 35 | ||
| 33 | @Override | 36 | @Override |
| @@ -50,6 +53,8 @@ public class UserDetailsServiceImpl implements UserDetailsService | @@ -50,6 +53,8 @@ public class UserDetailsServiceImpl implements UserDetailsService | ||
| 50 | throw new ServiceException("对不起,您的账号:" + username + " 已停用"); | 53 | throw new ServiceException("对不起,您的账号:" + username + " 已停用"); |
| 51 | } | 54 | } |
| 52 | 55 | ||
| 56 | + passwordService.validate(user); | ||
| 57 | + | ||
| 53 | return createLoginUser(user); | 58 | return createLoginUser(user); |
| 54 | } | 59 | } |
| 55 | 60 |
-
请 注册 或 登录 后发表评论