diff --git a/README.md b/README.md index 0d316b6..f64f14a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
-
+
@@ -81,19 +81,20 @@ SpringBlade
## 官方产品
-| 简介 | 演示地址 |
-|---------------|------------------------------------------------------|
-| BladeX企业级开发平台 | [https://saber3.bladex.cn](https://saber3.bladex.cn) |
-| BladeX可视化数据大屏 | [https://data.bladex.cn](https://data.bladex.cn) |
-| BladeX物联网开发平台 | [https://iot.bladex.cn](https://iot.bladex.cn) |
+| 简介 | 演示地址 |
+|-----------------|------------------------------------------------------|
+| BladeX企业级开发平台 | [https://saber3.bladex.cn](https://saber3.bladex.cn) |
+| BladeX可视化数据大屏 | [https://data.bladex.cn](https://data.bladex.cn) |
+| BladeX物联网开发平台 | [https://iot.bladex.cn](https://iot.bladex.cn) |
+| BladeXAI大模型平台 | [https://aigc.bladex.cn/](https://aigc.bladex.cn/) |
## 前端项目
-| 简介 | 地址 |
-|--------------------|----------------------------------------------------------------------------------------------------|
-| 前端框架Sword(基于React) | [https://gitee.com/smallc/Sword](https://gitee.com/smallc/Sword) |
-| 前端框架Saber(基于Vue2) | [https://gitee.com/smallc/Saber](https://gitee.com/smallc/Saber) |
-| 前端框架Saber3(基于Vue3) | [https://gitee.com/smallc/Saber3](https://gitee.com/smallc/Saber/tree/3.x/) |
+| 简介 | 地址 |
+|--------------------|------------------------------------------------------------------------------|
+| 前端框架Saber3(基于Vue3) | [https://gitee.com/smallc/Saber3](https://gitee.com/smallc/Saber) |
+| 前端框架Saber(基于Vue2) | [https://gitee.com/smallc/Saber2](https://gitee.com/smallc/Saber/tree/vue2/) |
+| 前端框架Sword(基于React) | [https://gitee.com/smallc/Sword](https://gitee.com/smallc/Sword) |
## 后端项目
| 简介 | 地址 |
diff --git a/blade-auth/src/main/java/org/springblade/auth/granter/CaptchaTokenGranter.java b/blade-auth/src/main/java/org/springblade/auth/granter/CaptchaTokenGranter.java
index 4bcadba..a078197 100644
--- a/blade-auth/src/main/java/org/springblade/auth/granter/CaptchaTokenGranter.java
+++ b/blade-auth/src/main/java/org/springblade/auth/granter/CaptchaTokenGranter.java
@@ -16,6 +16,7 @@
package org.springblade.auth.granter;
import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springblade.auth.enums.BladeUserEnum;
import org.springblade.auth.utils.TokenUtil;
import org.springblade.common.cache.CacheNames;
@@ -30,16 +31,20 @@ import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
+import java.time.Duration;
+
/**
* 验证码TokenGranter
*
* @author Chill
*/
+@Slf4j
@Component
@AllArgsConstructor
public class CaptchaTokenGranter implements ITokenGranter {
public static final String GRANT_TYPE = "captcha";
+ public static final Integer FAIL_COUNT = 5;
private IUserClient userClient;
private BladeRedis bladeRedis;
@@ -53,7 +58,7 @@ public class CaptchaTokenGranter implements ITokenGranter {
String key = request.getHeader(TokenUtil.CAPTCHA_HEADER_KEY);
String code = request.getHeader(TokenUtil.CAPTCHA_HEADER_CODE);
// 获取验证码
- String redisCode = Func.toStr(bladeRedis.get(CacheNames.CAPTCHA_KEY + key));
+ String redisCode = Func.toStr(bladeRedis.getAndDel(CacheNames.CAPTCHA_KEY + key));
// 判断验证码
if (code == null || !StringUtil.equalsIgnoreCase(redisCode, code)) {
throw new ServiceException(TokenUtil.CAPTCHA_NOT_CORRECT);
@@ -62,6 +67,14 @@ public class CaptchaTokenGranter implements ITokenGranter {
String tenantId = tokenParameter.getArgs().getStr("tenantId");
String account = tokenParameter.getArgs().getStr("account");
String password = tokenParameter.getArgs().getStr("password");
+
+ // 判断登录是否锁定
+ int cnt = Func.toInt(bladeRedis.get(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, account)), 0);
+ if (cnt >= FAIL_COUNT) {
+ log.error("用户登录失败次数过多, 账号:{}, IP:{}", account, WebUtil.getIP());
+ throw new ServiceException(TokenUtil.USER_HAS_TOO_MANY_FAILS);
+ }
+
UserInfo userInfo = null;
if (Func.isNoneBlank(account, password)) {
// 获取用户类型
@@ -80,6 +93,14 @@ public class CaptchaTokenGranter implements ITokenGranter {
}
userInfo = result.isSuccess() ? result.getData() : null;
}
+
+ if (userInfo == null || userInfo.getUser() == null) {
+ // 增加错误锁定次数
+ bladeRedis.setEx(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, account), cnt + 1, Duration.ofMinutes(30));
+ } else {
+ // 成功则清除登录缓存
+ bladeRedis.del(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, account));
+ }
return userInfo;
}
diff --git a/blade-auth/src/main/java/org/springblade/auth/granter/PasswordTokenGranter.java b/blade-auth/src/main/java/org/springblade/auth/granter/PasswordTokenGranter.java
index 1db1498..201d809 100644
--- a/blade-auth/src/main/java/org/springblade/auth/granter/PasswordTokenGranter.java
+++ b/blade-auth/src/main/java/org/springblade/auth/granter/PasswordTokenGranter.java
@@ -16,28 +16,38 @@
package org.springblade.auth.granter;
import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springblade.auth.enums.BladeUserEnum;
import org.springblade.auth.utils.TokenUtil;
+import org.springblade.common.cache.CacheNames;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.secure.props.BladeAuthProperties;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DigestUtil;
import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.WebUtil;
import org.springblade.system.user.entity.UserInfo;
import org.springblade.system.user.feign.IUserClient;
import org.springframework.stereotype.Component;
+import java.time.Duration;
+
/**
* PasswordTokenGranter
*
* @author Chill
*/
+@Slf4j
@Component
@AllArgsConstructor
public class PasswordTokenGranter implements ITokenGranter {
public static final String GRANT_TYPE = "password";
+ public static final Integer FAIL_COUNT = 5;
private IUserClient userClient;
+ private BladeRedis bladeRedis;
private BladeAuthProperties authProperties;
@@ -46,6 +56,14 @@ public class PasswordTokenGranter implements ITokenGranter {
String tenantId = tokenParameter.getArgs().getStr("tenantId");
String account = tokenParameter.getArgs().getStr("account");
String password = tokenParameter.getArgs().getStr("password");
+
+ // 判断登录是否锁定
+ int cnt = Func.toInt(bladeRedis.get(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, account)), 0);
+ if (cnt >= FAIL_COUNT) {
+ log.error("用户登录失败次数过多, 账号:{}, IP:{}", account, WebUtil.getIP());
+ throw new ServiceException(TokenUtil.USER_HAS_TOO_MANY_FAILS);
+ }
+
UserInfo userInfo = null;
if (Func.isNoneBlank(account, password)) {
// 获取用户类型
@@ -64,6 +82,14 @@ public class PasswordTokenGranter implements ITokenGranter {
}
userInfo = result.isSuccess() ? result.getData() : null;
}
+
+ if (userInfo == null || userInfo.getUser() == null) {
+ // 增加错误锁定次数
+ bladeRedis.setEx(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, account), cnt + 1, Duration.ofMinutes(30));
+ } else {
+ // 成功则清除登录缓存
+ bladeRedis.del(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, account));
+ }
return userInfo;
}
diff --git a/blade-auth/src/main/java/org/springblade/auth/utils/TokenUtil.java b/blade-auth/src/main/java/org/springblade/auth/utils/TokenUtil.java
index e8d71f9..daf6794 100644
--- a/blade-auth/src/main/java/org/springblade/auth/utils/TokenUtil.java
+++ b/blade-auth/src/main/java/org/springblade/auth/utils/TokenUtil.java
@@ -47,6 +47,7 @@ public class TokenUtil {
public final static String HEADER_KEY = "Authorization";
public final static String HEADER_PREFIX = "Basic ";
public final static String ENCRYPT_PREFIX = "04";
+ public final static String USER_HAS_TOO_MANY_FAILS = "用户登录失败次数过多";
public final static String DEFAULT_AVATAR = "https://bladex.cn/images/logo.png";
/**
diff --git a/blade-common/src/main/java/org/springblade/common/cache/CacheNames.java b/blade-common/src/main/java/org/springblade/common/cache/CacheNames.java
index 1d72d07..3640896 100644
--- a/blade-common/src/main/java/org/springblade/common/cache/CacheNames.java
+++ b/blade-common/src/main/java/org/springblade/common/cache/CacheNames.java
@@ -27,6 +27,26 @@ public interface CacheNames {
String DICT_VALUE = "dict:value";
String DICT_LIST = "dict:list";
- String CAPTCHA_KEY = "blade:auth::captcha:";
+ /**
+ * 验证码key
+ */
+ String CAPTCHA_KEY = "blade:auth::blade:captcha:";
+
+ /**
+ * 登录失败key
+ */
+ String USER_FAIL_KEY = "blade:user::blade:fail:";
+
+ /**
+ * 返回租户格式的key
+ *
+ * @param tenantId 租户编号
+ * @param cacheKey 缓存key
+ * @param cacheKeyValue 缓存key值
+ * @return tenantKey
+ */
+ static String tenantKey(String tenantId, String cacheKey, String cacheKeyValue) {
+ return tenantId.concat(":").concat(cacheKey).concat(cacheKeyValue);
+ }
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java
index b2969fa..dc5c35e 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java
@@ -20,6 +20,7 @@ import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -30,8 +31,10 @@ import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
+import org.springblade.common.cache.CacheNames;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
+import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.secure.utils.SecureUtil;
@@ -39,6 +42,7 @@ import org.springblade.core.tool.api.R;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
import org.springblade.system.user.entity.User;
import org.springblade.system.excel.UserExcel;
import org.springblade.system.excel.UserImportListener;
@@ -69,6 +73,7 @@ import java.util.Map;
public class UserController {
private IUserService userService;
+ private BladeRedis bladeRedis;
/**
* 查询单条
@@ -85,7 +90,7 @@ public class UserController {
/**
* 查询单条
*/
- @ApiOperationSupport(order =2)
+ @ApiOperationSupport(order = 2)
@Operation(summary = "查看详情", description = "传入id")
@GetMapping("/info")
public R