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