Browse Source

web登录:
1、校验账号重复兼容名称重复问题;账号、邮箱、手机号等信息都需要夸租户校验;
2、存在账号相同的匹配密码相同即可登录;
3、管理端新增导入跨租户校验账号、邮箱、手机号重复
4、登录成功补充租户信息,用于前端显示
5、新增异常可填充数据object

liyanbo 1 month ago
parent
commit
8fa6d5f5e8

+ 20 - 1
byzs-framework/byzs-common/src/main/java/cn/iocoder/byzs/framework/common/exception/ServiceException.java

@@ -21,6 +21,10 @@ public final class ServiceException extends RuntimeException {
      * 错误提示
      */
     private String message;
+    /**
+     * 错误数据
+     */
+    private Object data;
 
     /**
      * 空构造方法,避免反序列化问题
@@ -38,6 +42,12 @@ public final class ServiceException extends RuntimeException {
         this.message = message;
     }
 
+    public ServiceException(Integer code, String message, Object data) {
+        this.code = code;
+        this.message = message;
+        this.data = data;
+    }
+
     public Integer getCode() {
         return code;
     }
@@ -57,4 +67,13 @@ public final class ServiceException extends RuntimeException {
         return this;
     }
 
-}
+    public Object getData() {
+        return data;
+    }
+
+    public ServiceException setData(Object data) {
+        this.data = data;
+        return this;
+    }
+
+}

+ 5 - 1
byzs-framework/byzs-common/src/main/java/cn/iocoder/byzs/framework/common/exception/util/ServiceExceptionUtil.java

@@ -26,6 +26,10 @@ public class ServiceExceptionUtil {
         return exception0(errorCode.getCode(), errorCode.getMsg(), params);
     }
 
+    public static ServiceException exception(ErrorCode errorCode, Object data) {
+        return new ServiceException(errorCode.getCode(), errorCode.getMsg(), data);
+    }
+
     public static ServiceException exception0(Integer code, String messagePattern, Object... params) {
         String message = doFormat(code, messagePattern, params);
         return new ServiceException(code, message);
@@ -74,4 +78,4 @@ public class ServiceExceptionUtil {
         return sbuf.toString();
     }
 
-}
+}

+ 9 - 1
byzs-framework/byzs-common/src/main/java/cn/iocoder/byzs/framework/common/pojo/CommonResult.java

@@ -118,4 +118,12 @@ public class CommonResult<T> implements Serializable {
         return error(serviceException.getCode(), serviceException.getMessage());
     }
 
-}
+    /**
+     * 设置数据
+     * @param data 数据
+     */
+    public void setData(Object data) {
+        this.data = (T) data;
+    }
+
+}

+ 6 - 2
byzs-framework/byzs-spring-boot-starter-web/src/main/java/cn/iocoder/byzs/framework/web/core/handler/GlobalExceptionHandler.java

@@ -275,7 +275,11 @@ public class GlobalExceptionHandler {
                 // 忽略日志,避免影响主流程
             }
         }
-        return CommonResult.error(ex.getCode(), ex.getMessage());
+        CommonResult<?> result = CommonResult.error(ex.getCode(), ex.getMessage());
+        if (ex.getData() != null) {
+            result.setData(ex.getData());
+        }
+        return result;
     }
 
     /**
@@ -360,4 +364,4 @@ public class GlobalExceptionHandler {
         return null;
     }
 
-}
+}

+ 2 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/controller/admin/auth/vo/AuthLoginRespVO.java

@@ -27,4 +27,6 @@ public class AuthLoginRespVO {
     @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime expiresTime;
 
+    @Schema(description = "租户id", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String tenantId;
 }

+ 4 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/dal/mysql/user/AdminUserMapper.java

@@ -17,6 +17,10 @@ public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
         return selectOne(AdminUserDO::getUsername, username);
     }
 
+    default List<AdminUserDO> selectListByUsername(String username) {
+        return selectList(AdminUserDO::getUsername, username);
+    }
+
     default AdminUserDO selectByEmail(String email) {
         return selectOne(AdminUserDO::getEmail, email);
     }

+ 22 - 5
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/auth/AdminAuthServiceImpl.java

@@ -7,6 +7,7 @@ import cn.iocoder.byzs.framework.common.util.monitor.TracerUtils;
 import cn.iocoder.byzs.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.byzs.framework.common.util.validation.ValidationUtils;
 import cn.iocoder.byzs.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.byzs.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.byzs.framework.web.core.util.WebFrameworkUtils;
 import cn.iocoder.byzs.module.system.api.logger.dto.LoginLogCreateReqDTO;
 import cn.iocoder.byzs.module.system.api.sms.SmsCodeApi;
@@ -86,13 +87,26 @@ public class AdminAuthServiceImpl implements AdminAuthService {
     @Override
     public AdminUserDO authenticate(String username, String password) {
         final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
+
         // 校验账号是否存在
-        AdminUserDO user = userService.getUserByUsername(username);
-        if (user == null) {
+        List<AdminUserDO> userList = TenantUtils.executeIgnore(() ->
+                userService.getUserListByUsername(username)
+        );
+        if (userList.isEmpty()) {
             createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
             throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
         }
-        if (!userService.isPasswordMatch(password, user.getPassword())) {
+
+        AdminUserDO user = userList.get(0);
+        boolean isMatch = true;
+        for (AdminUserDO userDO : userList) {
+            if (userService.isPasswordMatch(password, userDO.getPassword())) {
+                isMatch = false;
+                user = userDO;
+                break;
+            }
+        }
+        if (isMatch) {
             createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
             throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
         }
@@ -143,7 +157,10 @@ public class AdminAuthServiceImpl implements AdminAuthService {
                     throw exception(AUTH_LOGIN_IP_NOT_AUTHORIZED_NOT_MOBILE_NOT_EXISTS);
                 }
 
-                throw exception(AUTH_LOGIN_IP_NOT_AUTHORIZED);
+                // 构建返回数据
+                java.util.Map<String, Object> data = new java.util.HashMap<>();
+                data.put("mobile", user.getMobile());
+                throw exception(AUTH_LOGIN_IP_NOT_AUTHORIZED, data);
             }
 
             // 删除用户之前的所有令牌,实现单点登录(默认租户用户可以多设备登录)
@@ -340,4 +357,4 @@ public class AdminAuthServiceImpl implements AdminAuthService {
 
         userService.updateUserPassword(userByMobile.getId(), reqVO.getPassword());
     }
-}
+}

+ 29 - 3
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/oauth2/OAuth2TokenServiceImpl.java

@@ -186,7 +186,13 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
                 .setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes())
                 .setRefreshToken(refreshTokenDO.getRefreshToken())
                 .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds()));
-        accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
+        // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
+        Long tenantId = TenantContextHolder.getTenantId();
+        if (tenantId == null) {
+            // 从用户信息中获取租户编号
+            tenantId = refreshTokenDO.getTenantId();
+        }
+        accessTokenDO.setTenantId(tenantId);
         oauth2AccessTokenMapper.insert(accessTokenDO);
         // 记录到 Redis 中
         oauth2AccessTokenRedisDAO.set(accessTokenDO);
@@ -198,6 +204,16 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
                 .setUserId(userId).setUserType(userType)
                 .setClientId(clientDO.getClientId()).setScopes(scopes)
                 .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getRefreshTokenValiditySeconds()));
+        // 手动设置租户编号
+        Long tenantId = TenantContextHolder.getTenantId();
+        if (tenantId == null) {
+            // 从用户信息中获取租户编号
+            cn.iocoder.byzs.module.system.dal.dataobject.user.AdminUserDO user = adminUserService.getUser(userId);
+            if (user != null) {
+                tenantId = user.getTenantId();
+            }
+        }
+        refreshToken.setTenantId(tenantId);
         oauth2RefreshTokenMapper.insert(refreshToken);
         return refreshToken;
     }
@@ -205,8 +221,18 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
     private OAuth2AccessTokenDO convertToAccessToken(OAuth2RefreshTokenDO refreshTokenDO) {
         OAuth2AccessTokenDO accessTokenDO = BeanUtils.toBean(refreshTokenDO, OAuth2AccessTokenDO.class)
                 .setAccessToken(refreshTokenDO.getRefreshToken());
-        TenantUtils.execute(refreshTokenDO.getTenantId(),
+        // 确保租户编号存在
+        Long tenantId = refreshTokenDO.getTenantId();
+        if (tenantId == null) {
+            // 从用户信息中获取租户编号
+            cn.iocoder.byzs.module.system.dal.dataobject.user.AdminUserDO user = adminUserService.getUser(refreshTokenDO.getUserId());
+            if (user != null) {
+                tenantId = user.getTenantId();
+            }
+        }
+        TenantUtils.execute(tenantId,
                         () -> accessTokenDO.setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType())));
+        accessTokenDO.setTenantId(tenantId);
         return accessTokenDO;
     }
 
@@ -237,4 +263,4 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
         return IdUtil.fastSimpleUUID();
     }
 
-}
+}

+ 8 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/user/AdminUserService.java

@@ -103,6 +103,14 @@ public interface AdminUserService {
      */
     AdminUserDO getUserByUsername(String username);
 
+    /**
+     * 通过用户名查询用户列表
+     *
+     * @param username 用户名
+     * @return 用户对象列表
+     */
+    List<AdminUserDO> getUserListByUsername(String username);
+
     /**
      * 通过手机号获取用户
      *

+ 28 - 5
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/user/AdminUserServiceImpl.java

@@ -11,6 +11,7 @@ import cn.iocoder.byzs.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
 import cn.iocoder.byzs.framework.common.util.validation.ValidationUtils;
 import cn.iocoder.byzs.framework.datapermission.core.util.DataPermissionUtils;
+import cn.iocoder.byzs.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.byzs.module.infra.api.config.ConfigApi;
 import cn.iocoder.byzs.module.system.controller.admin.auth.vo.AuthRegisterReqVO;
 import cn.iocoder.byzs.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
@@ -255,6 +256,11 @@ public class AdminUserServiceImpl implements AdminUserService {
         return userMapper.selectByUsername(username);
     }
 
+    @Override
+    public List<AdminUserDO> getUserListByUsername(String username) {
+        return userMapper.selectListByUsername(username);
+    }
+
     @Override
     public AdminUserDO getUserByMobile(String mobile) {
         return userMapper.selectByMobile(mobile);
@@ -380,7 +386,12 @@ public class AdminUserServiceImpl implements AdminUserService {
         if (StrUtil.isBlank(username)) {
             return;
         }
-        AdminUserDO user = userMapper.selectByUsername(username);
+        // 跳过租户过滤,查询所有租户的用户
+        AdminUserDO user = null;
+        List<AdminUserDO> userList = TenantUtils.executeIgnore(() ->
+                userMapper.selectListByUsername(username)
+        );
+        user = userList.get(0);
         if (user == null) {
             return;
         }
@@ -398,7 +409,11 @@ public class AdminUserServiceImpl implements AdminUserService {
         if (StrUtil.isBlank(email)) {
             return;
         }
-        AdminUserDO user = userMapper.selectByEmail(email);
+
+        // 跳过租户过滤,查询所有租户的用户
+        AdminUserDO user = TenantUtils.executeIgnore(() ->
+                userMapper.selectByEmail(email)
+        );
         if (user == null) {
             return;
         }
@@ -416,7 +431,10 @@ public class AdminUserServiceImpl implements AdminUserService {
         if (StrUtil.isBlank(mobile)) {
             return;
         }
-        AdminUserDO user = userMapper.selectByMobile(mobile);
+        // 跳过租户过滤,查询所有租户的用户
+        AdminUserDO user = TenantUtils.executeIgnore(() ->
+                userMapper.selectByMobile(mobile)
+        );
         if (user == null) {
             return;
         }
@@ -479,13 +497,18 @@ public class AdminUserServiceImpl implements AdminUserService {
             }
 
             // 2.2.1 判断如果不存在,在进行插入
-            AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
-            if (existUser == null) {
+            // 跳过租户过滤,查询所有租户的用户
+            AdminUserDO existUser = null;
+            List<AdminUserDO> userList = TenantUtils.executeIgnore(() ->
+                    getUserListByUsername(importUser.getUsername())
+            );
+            if (userList.isEmpty()) {
                 userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class)
                         .setPassword(encodePassword(initPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组
                 respVO.getCreateUsernames().add(importUser.getUsername());
                 return;
             }
+            existUser = userList.get(0);
             // 2.2.2 如果存在,判断是否允许更新
             if (!isUpdateSupport) {
                 respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg());

+ 23 - 34
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/login/WebLoginController.java

@@ -14,11 +14,13 @@ import cn.iocoder.byzs.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO;
 import cn.iocoder.byzs.module.system.controller.admin.auth.vo.AuthSmsSendReqVO;
 import cn.iocoder.byzs.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.byzs.module.system.dal.dataobject.tenant.TenantDO;
+import cn.iocoder.byzs.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.byzs.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.byzs.module.system.service.auth.AdminAuthService;
 import cn.iocoder.byzs.module.system.service.permission.PermissionService;
 import cn.iocoder.byzs.module.system.service.permission.RoleService;
 import cn.iocoder.byzs.module.system.service.tenant.TenantService;
+import cn.iocoder.byzs.module.system.service.user.AdminUserService;
 import cn.iocoder.byzs.module.web.controller.admin.login.vo.WebLoginVO;
 import cn.iocoder.byzs.module.web.controller.admin.login.vo.WebRegisterVO;
 import cn.iocoder.byzs.module.web.service.login.WebLoginServiceImpl;
@@ -53,6 +55,8 @@ public class WebLoginController {
     @Resource
     private RoleService roleService;
     @Resource
+    private AdminUserService adminUserService;
+    @Resource
     private WebLoginServiceImpl webLoginServiceImpl;
 
 
@@ -76,20 +80,23 @@ public class WebLoginController {
 
     @PostMapping("/login")
     @PermitAll
+    @TenantIgnore
     @Operation(summary = "使用账号密码登录")
     public CommonResult<WebLoginVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
-        return success(setAuthRoleVO(authService.login(reqVO)));
+        return success(buildWebLoginVO(authService.login(reqVO)));
     }
 
     @PostMapping("/sms-login")
     @PermitAll
+    @TenantIgnore
     @Operation(summary = "使用短信验证码登录")
     public CommonResult<WebLoginVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
-        return success(setAuthRoleVO(authService.smsLogin(reqVO)));
+        return success(buildWebLoginVO(authService.smsLogin(reqVO)));
     }
 
     @PostMapping("/send-sms-code")
     @PermitAll
+    @TenantIgnore
     @Operation(summary = "发送手机验证码")
     public CommonResult<Boolean> sendLoginSmsCode(@RequestBody @Valid AuthSmsSendReqVO reqVO) {
         authService.sendSmsCode(reqVO);
@@ -115,43 +122,25 @@ public class WebLoginController {
         return success(roleRouteSet);
     }
 
-    /**
-     * 填充课程数据权限
-     * @param login
-     * @return
-     */
-    private WebLoginVO setAuthRoleVO(AuthLoginRespVO login) {
+    // 填充租户
+    private WebLoginVO buildWebLoginVO(AuthLoginRespVO login) {
+        // 转换为 WebLoginVO
         WebLoginVO webLoginVO = BeanUtils.toBean(login, WebLoginVO.class);
 
-        // 已经在后台读取,这里无需重复读取
-        if (true) {
-            return webLoginVO;
+        // 确保租户ID存在
+        if (webLoginVO.getTenantId() == null) {
+            AdminUserDO user = adminUserService.getUser(webLoginVO.getUserId());
+            if (user != null) {
+                webLoginVO.setTenantId(user.getTenantId());
+            }
         }
 
-        // 获得角色列表
-        Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(login.getUserId());
-        List<RoleDO> roles = roleService.getRoleList(roleIds);
-        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色
-
-        // 将所有角色的dataScopeCourseIds拼接成一个Set集合
-        Set<Long> allDataScopeCourseIds = new HashSet<>();
-        Set<Long> allDataScopeBlocklyIds = new HashSet<>();
-        for (RoleDO role : roles) {
-            // 填充课程数据权限
-            Set<Long> dataScopeCourseIds = role.getDataScopeCourseIds();
-            if (dataScopeCourseIds != null && !dataScopeCourseIds.isEmpty()) {
-                allDataScopeCourseIds.addAll(dataScopeCourseIds);
-            }
-            //blocklu课程数据权限
-            Set<Long> dataScopeBlocklyIds = role.getDataScopeBlocklyIds();
-            if (dataScopeBlocklyIds != null && !dataScopeBlocklyIds.isEmpty()) {
-                allDataScopeBlocklyIds.addAll(dataScopeBlocklyIds);
-            }
+        // 填充租户名称
+        TenantDO tenant = tenantService.getTenant(webLoginVO.getTenantId());
+        if (tenant != null) {
+            webLoginVO.setTenantName(tenant.getName());
         }
 
-        //填充课程数据权限
-        webLoginVO.setCourseDataScope(allDataScopeCourseIds);
-        webLoginVO.setBlocklyDataScope(allDataScopeBlocklyIds);
         return webLoginVO;
     }
-}
+}

+ 4 - 4
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/login/vo/WebLoginVO.java

@@ -22,9 +22,9 @@ public class WebLoginVO {
     @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime expiresTime;
 
-    @Schema(description = "课程权限")
-    private Set<Long> courseDataScope;
+    @Schema(description = "租户id", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long tenantId;
 
-    @Schema(description = "blockly课程权限")
-    private Set<Long> blocklyDataScope;
+    @Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String tenantName;
 }