From ff8206323dc1b88c9373a3ee5cf5cb8e86ef60d0 Mon Sep 17 00:00:00 2001 From: RuoYi <yzz_ivy@163.com> Date: 星期六, 30 七月 2022 14:00:32 +0800 Subject: [PATCH] 支持配置密码最大错误次数/锁定时间 --- src/main/resources/i18n/messages.properties | 2 src/main/java/com/ruoyi/project/monitor/controller/CacheController.java | 1 src/main/java/com/ruoyi/framework/redis/RedisCache.java | 22 +++++ src/main/java/com/ruoyi/common/constant/CacheConstants.java | 5 + src/main/java/com/ruoyi/framework/security/service/UserDetailsServiceImpl.java | 5 + src/main/java/com/ruoyi/framework/web/domain/AjaxResult.java | 21 +++++ src/main/java/com/ruoyi/framework/security/service/SysLoginService.java | 6 + src/main/java/com/ruoyi/framework/security/service/SysPasswordService.java | 94 +++++++++++++++++++++++ src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java | 28 +++++++ src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java | 16 ++++ src/main/resources/application.yml | 8 ++ 11 files changed, 205 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/src/main/java/com/ruoyi/common/constant/CacheConstants.java index 7ea15aa..c89692c 100644 --- a/src/main/java/com/ruoyi/common/constant/CacheConstants.java +++ b/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:"; } diff --git a/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java b/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java new file mode 100644 index 0000000..0de8d24 --- /dev/null +++ b/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 }); + } +} diff --git a/src/main/java/com/ruoyi/framework/redis/RedisCache.java b/src/main/java/com/ruoyi/framework/redis/RedisCache.java index e701bcc..1e674db 100644 --- a/src/main/java/com/ruoyi/framework/redis/RedisCache.java +++ b/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閿� diff --git a/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java b/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java new file mode 100644 index 0000000..5ee5bbd --- /dev/null +++ b/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(); + } +} diff --git a/src/main/java/com/ruoyi/framework/security/service/SysLoginService.java b/src/main/java/com/ruoyi/framework/security/service/SysLoginService.java index f7c195c..3261d3b 100644 --- a/src/main/java/com/ruoyi/framework/security/service/SysLoginService.java +++ b/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); // 璇ユ柟娉曚細鍘昏皟鐢║serDetailsServiceImpl.loadUserByUsername - authentication = authenticationManager - .authenticate(new UsernamePasswordAuthenticationToken(username, password)); + authentication = authenticationManager.authenticate(authenticationToken); } catch (Exception e) { diff --git a/src/main/java/com/ruoyi/framework/security/service/SysPasswordService.java b/src/main/java/com/ruoyi/framework/security/service/SysPasswordService.java new file mode 100644 index 0000000..c50a073 --- /dev/null +++ b/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 缂撳瓨閿甼ey + */ + 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)); + } + } +} diff --git a/src/main/java/com/ruoyi/framework/security/service/UserDetailsServiceImpl.java b/src/main/java/com/ruoyi/framework/security/service/UserDetailsServiceImpl.java index 861e3ec..d48c4c9 100644 --- a/src/main/java/com/ruoyi/framework/security/service/UserDetailsServiceImpl.java +++ b/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); } diff --git a/src/main/java/com/ruoyi/framework/web/domain/AjaxResult.java b/src/main/java/com/ruoyi/framework/web/domain/AjaxResult.java index 51253c6..cba99ba 100644 --- a/src/main/java/com/ruoyi/framework/web/domain/AjaxResult.java +++ b/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 閿� diff --git a/src/main/java/com/ruoyi/project/monitor/controller/CacheController.java b/src/main/java/com/ruoyi/project/monitor/controller/CacheController.java index 37bf080..8dce0e6 100644 --- a/src/main/java/com/ruoyi/project/monitor/controller/CacheController.java +++ b/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')") diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6d00acb..214c5f2 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -39,6 +39,14 @@ com.ruoyi: debug org.springframework: warn +# 鐢ㄦ埛閰嶇疆 +user: + password: + # 瀵嗙爜鏈�澶ч敊璇鏁� + maxRetryCount: 5 + # 瀵嗙爜閿佸畾鏃堕棿锛堥粯璁�10鍒嗛挓锛� + lockTime: 10 + # Spring閰嶇疆 spring: # 璧勬簮淇℃伅 diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 5a41f8c..b7433dc 100644 --- a/src/main/resources/i18n/messages.properties +++ b/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=瑙掕壊宸插皝绂侊紝璇疯仈绯荤鐞嗗憳 -- Gitblit v1.9.3