From f1bdb285c951f7aef6f1f217773afb7737779435 Mon Sep 17 00:00:00 2001
From: puzhibing <393733352@qq.com>
Date: 星期三, 26 八月 2026 09:03:30 +0800
Subject: [PATCH] 增加管理后台登录频次校验和连续错误登录后限制登录 修改管理后台验证码生成和登录校验
---
.gitignore | 3
ruoyi-auth/src/main/java/com/ruoyi/auth/form/LoginBody.java | 30 +++++++
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java | 2
ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java | 86 +++++++++++----------
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java | 8 +
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysPasswordService.java | 13 ++-
ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/RouterFunctionConfiguration.java | 3
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java | 19 ++++
ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java | 20 +++++
9 files changed, 130 insertions(+), 54 deletions(-)
diff --git a/.gitignore b/.gitignore
index 09e64c6..ac69452 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,4 +17,5 @@
*.yaml
*.yml
-*.log
\ No newline at end of file
+*.log
+*.codebuddy
\ No newline at end of file
diff --git a/ruoyi-auth/src/main/java/com/ruoyi/auth/form/LoginBody.java b/ruoyi-auth/src/main/java/com/ruoyi/auth/form/LoginBody.java
index b12fb31..0967989 100644
--- a/ruoyi-auth/src/main/java/com/ruoyi/auth/form/LoginBody.java
+++ b/ruoyi-auth/src/main/java/com/ruoyi/auth/form/LoginBody.java
@@ -17,6 +17,16 @@
*/
private String password;
+ /**
+ * 验证码
+ */
+ private String code;
+
+ /**
+ * 验证码唯一标识
+ */
+ private String uuid;
+
public String getUsername()
{
return username;
@@ -36,4 +46,24 @@
{
this.password = password;
}
+
+ public String getCode()
+ {
+ return code;
+ }
+
+ public void setCode(String code)
+ {
+ this.code = code;
+ }
+
+ public String getUuid()
+ {
+ return uuid;
+ }
+
+ public void setUuid(String uuid)
+ {
+ this.uuid = uuid;
+ }
}
diff --git a/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java b/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java
index bc201c9..94db0b3 100644
--- a/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java
+++ b/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java
@@ -39,18 +39,22 @@
@Autowired
private RedisService redisService;
+
+ @Autowired
+ private LoginRateLimitService loginRateLimitService;
/**
* 登录
*/
public LoginUser login(String username, String password, HttpServletRequest request) {
- username = username.trim();
+ loginRateLimitService.checkLoginRate(username, request);
+ username = StringUtils.trim(username);
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) {
recordLogService.recordLogininfor(request, null, username, Constants.LOGIN_FAIL_STATUS, "登录用户不存在");
- throw new ServiceException("用户名/密码错误");
+ throw new ServiceException("用户名或密码错误");
}
LoginUser userInfo = userResult.getData();
SysUser user = userResult.getData().getSysUser();
diff --git a/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysPasswordService.java b/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysPasswordService.java
index bb85a0f..f746379 100644
--- a/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysPasswordService.java
+++ b/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysPasswordService.java
@@ -61,10 +61,15 @@
if (!matches(user, password))
{
- retryCount = retryCount + 1;
- recordLogService.recordLogininfor(request, user.getUserId().intValue(), username, Constants.LOGIN_FAIL_STATUS, String.format("密码输入错误%s次", retryCount));
- redisService.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
- throw new ServiceException("用户名/密码错误");
+ retryCount = (int) redisService.incrementCacheValue(getCacheKey(username), lockTime, TimeUnit.MINUTES);
+ redisService.expire(getCacheKey(username), lockTime, TimeUnit.MINUTES);
+ String errMsg = String.format("密码输入错误%s次", retryCount);
+ recordLogService.recordLogininfor(request, user.getUserId().intValue(), username, Constants.LOGIN_FAIL_STATUS, errMsg);
+ if (retryCount >= maxRetryCount)
+ {
+ throw new ServiceException(String.format("密码输入错误%s次,帐户锁定%s分钟", maxRetryCount, lockTime));
+ }
+ throw new ServiceException("用户名或密码错误");
}
else
{
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
index 72bde3b..516d612 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
@@ -24,9 +24,24 @@
public final static int PASSWORD_MAX_RETRY_COUNT = 5;
/**
- * 密码锁定时间,默认10(分钟)
+ * 密码锁定时间,默认15(分钟)
*/
- public final static long PASSWORD_LOCK_TIME = 10;
+ public final static long PASSWORD_LOCK_TIME = 15;
+
+ /**
+ * 登录限流缓存前缀
+ */
+ public static final String LOGIN_RATE_LIMIT_KEY = "login_rate_limit:";
+
+ /**
+ * 登录限流窗口,默认60(秒)
+ */
+ public static final long LOGIN_RATE_LIMIT_WINDOW = 60;
+
+ /**
+ * 单个维度在限流窗口内允许的最大登录次数
+ */
+ public static final int LOGIN_RATE_LIMIT_MAX_COUNT = 5;
/**
* 权限缓存前缀
diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java
index 5d3ad86..4c02c90 100644
--- a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java
+++ b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java
@@ -1,6 +1,7 @@
package com.ruoyi.common.redis.service;
import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -11,6 +12,7 @@
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
/**
@@ -50,6 +52,24 @@
}
/**
+ * 缓存值原子递增,并在首次递增时设置有效时间
+ *
+ * @param key 缓存键值
+ * @param timeout 时间
+ * @param timeUnit 时间颗粒度
+ * @return 递增后的值
+ */
+ public long incrementCacheValue(final String key, final long timeout, final TimeUnit timeUnit)
+ {
+ String scriptText = "local count = redis.call('incr', KEYS[1]); "
+ + "if count == 1 then redis.call('pexpire', KEYS[1], ARGV[1]); end; return count;";
+ DefaultRedisScript<Long> script = new DefaultRedisScript<>(scriptText, Long.class);
+ long timeoutMillis = timeUnit.toMillis(timeout);
+ Long count = (Long) redisTemplate.execute(script, Collections.singletonList(key), timeoutMillis);
+ return count == null ? 0L : count;
+ }
+
+ /**
* 设置有效时间
*
* @param key Redis键
diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/RouterFunctionConfiguration.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/RouterFunctionConfiguration.java
index 05bdc0b..8cdce62 100644
--- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/RouterFunctionConfiguration.java
+++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/RouterFunctionConfiguration.java
@@ -6,7 +6,6 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
@@ -43,7 +42,7 @@
@Bean
public RouterFunction routerFunction() {
return RouterFunctions.route(
- RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
+ RequestPredicates.GET("/code"),
validateCodeHandler);
}
diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java
index 02d7bb4..1a469d2 100644
--- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java
+++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java
@@ -1,8 +1,6 @@
package com.ruoyi.gateway.filter;
-import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
-import java.util.concurrent.atomic.AtomicReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
@@ -10,13 +8,16 @@
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
+
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.gateway.config.properties.CaptchaProperties;
import com.ruoyi.gateway.service.ValidateCodeService;
+
import reactor.core.publisher.Flux;
/**
@@ -26,44 +27,45 @@
*/
@Component
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> {
- private final static String[] VALIDATE_URL = new String[]{"/auth/login", "/auth/register"};
- private static final String CODE = "code";
- private static final String UUID = "uuid";
- @Autowired
- private ValidateCodeService validateCodeService;
- @Autowired
- private CaptchaProperties captchaProperties;
-
- @Override
- public GatewayFilter apply(Object config) {
- return (exchange, chain) -> {
- ServerHttpRequest request = exchange.getRequest();
-
- // 非登录/注册请求或验证码关闭,不处理
- if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled()) {
- return chain.filter(exchange);
- }
-
- try {
- String rspStr = resolveBodyFromRequest(request);
- JSONObject obj = JSON.parseObject(rspStr);
- validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID));
- } catch (Exception e) {
- return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
- }
- return chain.filter(exchange);
- };
- }
-
- private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
- // 获取请求体
- Flux<DataBuffer> body = serverHttpRequest.getBody();
- AtomicReference<String> bodyRef = new AtomicReference<>();
- body.subscribe(buffer -> {
- CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
- DataBufferUtils.release(buffer);
- bodyRef.set(charBuffer.toString());
- });
- return bodyRef.get();
- }
+ private static final String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" };
+ private static final String CODE = "code";
+ private static final String UUID = "uuid";
+
+ @Autowired
+ private ValidateCodeService validateCodeService;
+
+ @Autowired
+ private CaptchaProperties captchaProperties;
+
+ @Override
+ public GatewayFilter apply(Object config) {
+ return (exchange, chain) -> {
+ ServerHttpRequest request = exchange.getRequest();
+ if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL)
+ || !captchaProperties.getEnabled()) {
+ return chain.filter(exchange);
+ }
+
+ return DataBufferUtils.join(request.getBody()).flatMap(dataBuffer -> {
+ byte[] body = new byte[dataBuffer.readableByteCount()];
+ dataBuffer.read(body);
+ DataBufferUtils.release(dataBuffer);
+
+ try {
+ JSONObject json = JSON.parseObject(new String(body, StandardCharsets.UTF_8));
+ validateCodeService.checkCaptcha(json.getString(CODE), json.getString(UUID));
+ } catch (Exception e) {
+ return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
+ }
+
+ ServerHttpRequest decoratedRequest = new ServerHttpRequestDecorator(request) {
+ @Override
+ public Flux<DataBuffer> getBody() {
+ return Flux.just(exchange.getResponse().bufferFactory().wrap(body));
+ }
+ };
+ return chain.filter(exchange.mutate().request(decoratedRequest).build());
+ });
+ };
+ }
}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
index 849327f..4371be0 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
@@ -492,7 +492,7 @@
public R<LoginUser> info(@PathVariable("username") String username) {
SysUser sysUser = userService.selectUserByUserName(username);
if (StringUtils.isNull(sysUser)) {
- return R.fail("用户名/密码错误");
+ return R.fail("用户名或密码错误");
}
// 角色集合
Set<String> roles = permissionService.getRolePermission(sysUser);
--
Gitblit v1.7.1