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=è§è²å·²å°ç¦ï¼è¯·è系管çå