Explorar o código

!166 用户 Token 采用 OAuth2 的 Access Token + Refresh Token,提升安全性
Merge pull request !166 from 芋道源码/feature/1.6.2

芋道源码 %!s(int64=2) %!d(string=hai) anos
pai
achega
190150d1f4
Modificáronse 100 ficheiros con 2339 adicións e 2133 borrados
  1. 0 12
      yudao-dependencies/pom.xml
  2. 7 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  3. 26 13
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRule.java
  4. 2 3
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/service/DeptDataPermissionFrameworkService.java
  5. 27 13
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRuleTest.java
  6. 4 4
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantContextWebFilter.java
  7. 10 5
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java
  8. 4 11
      yudao-framework/yudao-spring-boot-starter-security/pom.xml
  9. 0 13
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java
  10. 4 27
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java
  11. 3 29
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
  12. 3 76
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java
  13. 0 149
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java
  14. 0 43
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java
  15. 39 10
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
  16. 0 40
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java
  17. 0 45
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java
  18. 2 13
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
  19. 8 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java
  20. 44 2
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java
  21. 3 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
  22. 9 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
  23. 0 1
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
  24. 8 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
  25. 34 13
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
  26. 14 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java
  27. 3 12
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
  28. 6 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java
  29. 28 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/config/SecurityConfiguration.java
  30. 4 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/core/package-info.java
  31. 23 18
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java
  32. 73 138
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
  33. 9 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java
  34. 16 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java
  35. 4 5
      yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java
  36. 0 7
      yudao-module-system/yudao-module-system-api/pom.xml
  37. 49 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java
  38. 0 56
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java
  39. 28 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java
  40. 35 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java
  41. 39 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java
  42. 4 2
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
  43. 17 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientIdEnum.java
  44. 0 2
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java
  45. 48 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java
  46. 0 47
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java
  47. 3 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http
  48. 42 23
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
  49. 74 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2ClientController.java
  50. 24 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2Controller.java
  51. 50 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2TokenController.java
  52. 0 79
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java
  53. 14 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginRespVO.java
  54. 51 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientBaseVO.java
  55. 12 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientCreateReqVO.java
  56. 19 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientPageReqVO.java
  57. 20 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientRespVO.java
  58. 14 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientUpdateReqVO.java
  59. 0 38
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageItemRespVO.java
  60. 0 20
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageReqVO.java
  61. 23 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenPageReqVO.java
  62. 41 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenRespVO.java
  63. 2 12
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java
  64. 33 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/OAuth2ClientConvert.java
  65. 22 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/OAuth2TokenConvert.java
  66. 0 15
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java
  67. 61 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java
  68. 73 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java
  69. 62 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java
  70. 52 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java
  71. 0 71
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java
  72. 33 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java
  73. 25 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2ClientMapper.java
  74. 20 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java
  75. 0 37
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java
  76. 4 4
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
  77. 0 52
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/LoginUserRedisDAO.java
  78. 54 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java
  79. 2 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java
  80. 0 32
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java
  81. 26 19
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java
  82. 81 161
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
  83. 14 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminOAuth2Service.java
  84. 66 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java
  85. 77 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java
  86. 11 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2CodeService.java
  87. 77 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java
  88. 163 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java
  89. 0 77
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java
  90. 0 177
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java
  91. 28 26
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java
  92. 1 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java
  93. 9 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java
  94. 21 9
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
  95. 104 153
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java
  96. 128 0
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImplTest.java
  97. 0 250
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java
  98. 65 53
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java
  99. 1 0
      yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql
  100. 0 0
      yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql

+ 0 - 12
yudao-dependencies/pom.xml

@@ -22,7 +22,6 @@
         <swagger-annotations.version>1.5.22</swagger-annotations.version>
         <swagger-annotations.version>1.5.22</swagger-annotations.version>
         <servlet.versoin>2.5</servlet.versoin>
         <servlet.versoin>2.5</servlet.versoin>
         <!-- DB 相关 -->
         <!-- DB 相关 -->
-        <mysql.version>5.1.46</mysql.version>
         <druid.version>1.2.8</druid.version>
         <druid.version>1.2.8</druid.version>
         <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
         <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
         <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
         <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
@@ -76,12 +75,6 @@
                 <version>${spring.boot.version}</version>
                 <version>${spring.boot.version}</version>
                 <type>pom</type>
                 <type>pom</type>
                 <scope>import</scope>
                 <scope>import</scope>
-                <exclusions>
-                    <exclusion>
-                        <groupId>mysql</groupId>
-                        <artifactId>mysql-connector-java</artifactId>
-                    </exclusion>
-                </exclusions>
             </dependency>
             </dependency>
 
 
             <!-- 业务组件 -->
             <!-- 业务组件 -->
@@ -180,11 +173,6 @@
             </dependency>
             </dependency>
 
 
             <dependency>
             <dependency>
-                <groupId>mysql</groupId>
-                <artifactId>mysql-connector-java</artifactId>
-                <version>${mysql.version}</version>
-            </dependency>
-            <dependency>
                 <groupId>com.alibaba</groupId>
                 <groupId>com.alibaba</groupId>
                 <artifactId>druid-spring-boot-starter</artifactId>
                 <artifactId>druid-spring-boot-starter</artifactId>
                 <version>${druid.version}</version>
                 <version>${druid.version}</version>

+ 7 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -54,6 +54,13 @@ public class CollectionUtils {
         return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
         return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
     }
     }
 
 
+    public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
+        if (CollUtil.isEmpty(from)) {
+            return new ArrayList<>();
+        }
+        return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
+    }
+
     public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
     public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
         if (CollUtil.isEmpty(from)) {
         if (CollUtil.isEmpty(from)) {
             return new HashSet<>();
             return new HashSet<>();

+ 26 - 13
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRule.java

@@ -1,11 +1,13 @@
 package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
 package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
 
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
-import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
+import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
@@ -13,7 +15,6 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
-import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import net.sf.jsqlparser.expression.Alias;
 import net.sf.jsqlparser.expression.Alias;
 import net.sf.jsqlparser.expression.Expression;
 import net.sf.jsqlparser.expression.Expression;
@@ -24,10 +25,7 @@ import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
 import net.sf.jsqlparser.expression.operators.relational.InExpression;
 import net.sf.jsqlparser.expression.operators.relational.InExpression;
 
 
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 
 /**
 /**
  * 基于部门的 {@link DataPermissionRule} 数据权限规则实现
  * 基于部门的 {@link DataPermissionRule} 数据权限规则实现
@@ -50,6 +48,11 @@ import java.util.Set;
 @Slf4j
 @Slf4j
 public class DeptDataPermissionRule implements DataPermissionRule {
 public class DeptDataPermissionRule implements DataPermissionRule {
 
 
+    /**
+     * LoginUser 的 Context 缓存 Key
+     */
+    protected static final String CONTEXT_KEY = DeptDataPermissionRule.class.getSimpleName();
+
     private static final String DEPT_COLUMN_NAME = "dept_id";
     private static final String DEPT_COLUMN_NAME = "dept_id";
     private static final String USER_COLUMN_NAME = "user_id";
     private static final String USER_COLUMN_NAME = "user_id";
 
 
@@ -90,13 +93,23 @@ public class DeptDataPermissionRule implements DataPermissionRule {
         if (loginUser == null) {
         if (loginUser == null) {
             return null;
             return null;
         }
         }
+        // 只有管理员类型的用户,才进行数据权限的处理
+        if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {
+            return null;
+        }
 
 
         // 获得数据权限
         // 获得数据权限
-        DeptDataPermissionRespDTO deptDataPermission = deptDataPermissionService.getDeptDataPermission(loginUser);
+        DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
+        // 从上下文中拿不到,则调用逻辑进行获取
         if (deptDataPermission == null) {
         if (deptDataPermission == null) {
-            log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
-            throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
-                    loginUser.getId(), tableName, tableAlias.getName()));
+            deptDataPermission = deptDataPermissionService.getDeptDataPermission(loginUser.getId());
+            if (deptDataPermission == null) {
+                log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
+                throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
+                        loginUser.getId(), tableName, tableAlias.getName()));
+            }
+            // 添加到上下文中,避免重复计算
+            loginUser.setContext(CONTEXT_KEY, deptDataPermission);
         }
         }
 
 
         // 情况一,如果是 ALL 可查看全部,则无需拼接条件
         // 情况一,如果是 ALL 可查看全部,则无需拼接条件
@@ -111,8 +124,8 @@ public class DeptDataPermissionRule implements DataPermissionRule {
         }
         }
 
 
         // 情况三,拼接 Dept 和 User 的条件,最后组合
         // 情况三,拼接 Dept 和 User 的条件,最后组合
-        Expression deptExpression = this.buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
-        Expression userExpression = this.buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
+        Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
+        Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
         if (deptExpression == null && userExpression == null) {
         if (deptExpression == null && userExpression == null) {
             // TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据
             // TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据
             log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",
             log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",

+ 2 - 3
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/service/DeptDataPermissionFrameworkService.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.framework.datapermission.core.dept.service;
 package cn.iocoder.yudao.framework.datapermission.core.dept.service;
 
 
 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
 
 
 /**
 /**
  * 基于部门的数据权限 Framework Service 接口
  * 基于部门的数据权限 Framework Service 接口
@@ -14,9 +13,9 @@ public interface DeptDataPermissionFrameworkService {
     /**
     /**
      * 获得登陆用户的部门数据权限
      * 获得登陆用户的部门数据权限
      *
      *
-     * @param loginUser 登陆用户
+     * @param userId 用户编号
      * @return 部门数据权限
      * @return 部门数据权限
      */
      */
-    DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser);
+    DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
 
 
 }
 }

+ 27 - 13
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRuleTest.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
 
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ReflectUtil;
 import cn.hutool.core.util.ReflectUtil;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
 import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
@@ -69,7 +70,8 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             Alias tableAlias = new Alias("u");
             // mock 方法
             // mock 方法
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
 
 
             // 调用
             // 调用
@@ -88,16 +90,18 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO().setAll(true);
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO().setAll(true);
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
 
 
             // 调用
             // 调用
             Expression expression = rule.getExpression(tableName, tableAlias);
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             // 断言
             assertNull(expression);
             assertNull(expression);
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
         }
     }
     }
 
 
@@ -109,16 +113,18 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO();
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO();
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
 
 
             // 调用
             // 调用
             Expression expression = rule.getExpression(tableName, tableAlias);
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             // 断言
             assertEquals("null = null", expression.toString());
             assertEquals("null = null", expression.toString());
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
         }
     }
     }
 
 
@@ -130,17 +136,19 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
                     .setDeptIds(SetUtils.asSet(10L, 20L)).setSelf(true);
                     .setDeptIds(SetUtils.asSet(10L, 20L)).setSelf(true);
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
 
 
             // 调用
             // 调用
             Expression expression = rule.getExpression(tableName, tableAlias);
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             // 断言
             assertSame(EXPRESSION_NULL, expression);
             assertSame(EXPRESSION_NULL, expression);
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
         }
     }
     }
 
 
@@ -152,12 +160,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
                     .setSelf(true);
                     .setSelf(true);
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
             // 添加 user 字段配置
             // 添加 user 字段配置
             rule.addUserColumn("t_user", "id");
             rule.addUserColumn("t_user", "id");
 
 
@@ -165,6 +174,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             Expression expression = rule.getExpression(tableName, tableAlias);
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             // 断言
             assertEquals("u.id = 1", expression.toString());
             assertEquals("u.id = 1", expression.toString());
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
         }
     }
     }
 
 
@@ -176,12 +186,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
                     .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L));
                     .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L));
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
             // 添加 dept 字段配置
             // 添加 dept 字段配置
             rule.addDeptColumn("t_user", "dept_id");
             rule.addDeptColumn("t_user", "dept_id");
 
 
@@ -189,6 +200,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             Expression expression = rule.getExpression(tableName, tableAlias);
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             // 断言
             assertEquals("u.dept_id IN (10, 20)", expression.toString());
             assertEquals("u.dept_id IN (10, 20)", expression.toString());
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
         }
     }
     }
 
 
@@ -200,12 +212,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             String tableName = "t_user";
             String tableName = "t_user";
             Alias tableAlias = new Alias("u");
             Alias tableAlias = new Alias("u");
             // mock 方法(LoginUser)
             // mock 方法(LoginUser)
-            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
+            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
+                    .setUserType(UserTypeEnum.ADMIN.getValue()));
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
             // mock 方法(DeptDataPermissionRespDTO)
             // mock 方法(DeptDataPermissionRespDTO)
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
             DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
                     .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L)).setSelf(true);
                     .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L)).setSelf(true);
-            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
+            when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
             // 添加 user 字段配置
             // 添加 user 字段配置
             rule.addUserColumn("t_user", "id");
             rule.addUserColumn("t_user", "id");
             // 添加 dept 字段配置
             // 添加 dept 字段配置
@@ -215,6 +228,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
             Expression expression = rule.getExpression(tableName, tableAlias);
             Expression expression = rule.getExpression(tableName, tableAlias);
             // 断言
             // 断言
             assertEquals("u.dept_id IN (10, 20) OR u.id = 1", expression.toString());
             assertEquals("u.dept_id IN (10, 20) OR u.id = 1", expression.toString());
+            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
         }
         }
     }
     }
 
 

+ 4 - 4
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantContextWebFilter.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.framework.tenant.core.web;
 package cn.iocoder.yudao.framework.tenant.core.web;
 
 
-import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 
 import javax.servlet.FilterChain;
 import javax.servlet.FilterChain;
@@ -24,9 +24,9 @@ public class TenantContextWebFilter extends OncePerRequestFilter {
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
             throws ServletException, IOException {
             throws ServletException, IOException {
         // 设置
         // 设置
-        String tenantId = request.getHeader(HEADER_TENANT_ID);
-        if (StrUtil.isNotEmpty(tenantId)) {
-            TenantContextHolder.setTenantId(Long.valueOf(tenantId));
+        Long tenantId = WebFrameworkUtils.getTenantId(request);
+        if (tenantId != null) {
+            TenantContextHolder.setTenantId(tenantId);
         }
         }
         try {
         try {
             chain.doFilter(request, response);
             chain.doFilter(request, response);

+ 10 - 5
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java

@@ -27,9 +27,11 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
 
 
     @Override
     @Override
     protected void doInit() {
     protected void doInit() {
-        // 补全风格。例如说 Linux 是 /,Windows 是 \
-        if (!config.getBasePath().endsWith(File.separator)) {
-            config.setBasePath(config.getBasePath() + File.separator);
+        // 把配置的 \ 替换成 /, 如果路径配置 \a\test, 替换成 /a/test, 替换方法已经处理 null 情况
+        config.setBasePath(StrUtil.replace(config.getBasePath(), StrUtil.BACKSLASH, StrUtil.SLASH));
+        // ftp的路径是 / 结尾
+        if (!config.getBasePath().endsWith(StrUtil.SLASH)) {
+            config.setBasePath(config.getBasePath() + StrUtil.SLASH);
         }
         }
         // 初始化 Ftp 对象
         // 初始化 Ftp 对象
         this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
         this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
@@ -42,6 +44,7 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
         String filePath = getFilePath(path);
         String filePath = getFilePath(path);
         String fileName = FileUtil.getName(filePath);
         String fileName = FileUtil.getName(filePath);
         String dir = StrUtil.removeSuffix(filePath, fileName);
         String dir = StrUtil.removeSuffix(filePath, fileName);
+        ftp.reconnectIfTimeout();
         boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));
         boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));
         if (!success) {
         if (!success) {
             throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
             throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
@@ -53,6 +56,7 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
     @Override
     @Override
     public void delete(String path) {
     public void delete(String path) {
         String filePath = getFilePath(path);
         String filePath = getFilePath(path);
+        ftp.reconnectIfTimeout();
         ftp.delFile(filePath);
         ftp.delFile(filePath);
     }
     }
 
 
@@ -60,8 +64,9 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
     public byte[] getContent(String path) {
     public byte[] getContent(String path) {
         String filePath = getFilePath(path);
         String filePath = getFilePath(path);
         String fileName = FileUtil.getName(filePath);
         String fileName = FileUtil.getName(filePath);
-        String dir = StrUtil.removeSuffix(path, fileName);
+        String dir = StrUtil.removeSuffix(filePath, fileName);
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ftp.reconnectIfTimeout();
         ftp.download(dir, fileName, out);
         ftp.download(dir, fileName, out);
         return out.toByteArray();
         return out.toByteArray();
     }
     }
@@ -70,4 +75,4 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
         return config.getBasePath() + path;
         return config.getBasePath() + path;
     }
     }
 
 
-}
+}

+ 4 - 11
yudao-framework/yudao-spring-boot-starter-security/pom.xml

@@ -44,18 +44,11 @@
             <artifactId>spring-boot-starter-security</artifactId>
             <artifactId>spring-boot-starter-security</artifactId>
         </dependency>
         </dependency>
 
 
-        <!-- TODO 芋艿: -->
+        <!-- 业务组件 -->
         <dependency>
         <dependency>
-            <groupId>org.activiti</groupId>
-            <artifactId>activiti-engine</artifactId>
-            <version>7.1.0.M6</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>*</groupId>
-                    <artifactId>*</artifactId>
-                </exclusion>
-            </exclusions>
-            <optional>true</optional>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行 Token 的校验 -->
+            <version>${revision}</version>
         </dependency>
         </dependency>
     </dependencies>
     </dependencies>
 
 

+ 0 - 13
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java

@@ -6,7 +6,6 @@ import org.springframework.validation.annotation.Validated;
 
 
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.NotNull;
-import java.time.Duration;
 
 
 @ConfigurationProperties(prefix = "yudao.security")
 @ConfigurationProperties(prefix = "yudao.security")
 @Validated
 @Validated
@@ -18,18 +17,6 @@ public class SecurityProperties {
      */
      */
     @NotEmpty(message = "Token Header 不能为空")
     @NotEmpty(message = "Token Header 不能为空")
     private String tokenHeader;
     private String tokenHeader;
-    /**
-     * Token 过期时间
-     */
-    @NotNull(message = "Token 过期时间不能为空")
-    private Duration tokenTimeout;
-    /**
-     * Session 过期时间
-     *
-     * 当 User 用户超过当前时间未操作,则 Session 会过期
-     */
-    @NotNull(message = "Session 过期时间不能为空")
-    private Duration sessionTimeout;
 
 
     /**
     /**
      * mock 模式的开关
      * mock 模式的开关

+ 4 - 27
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java

@@ -1,15 +1,12 @@
 package cn.iocoder.yudao.framework.security.config;
 package cn.iocoder.yudao.framework.security.config;
 
 
 import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect;
 import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect;
-import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
 import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
 import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
 import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
 import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
 import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
 import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
-import cn.iocoder.yudao.framework.security.core.handler.LogoutSuccessHandlerImpl;
-import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
-import cn.iocoder.yudao.framework.web.config.WebProperties;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
+import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
 import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
 import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
@@ -19,10 +16,8 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.access.AccessDeniedHandler;
 import org.springframework.security.web.access.AccessDeniedHandler;
-import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
-import java.util.List;
 
 
 /**
 /**
  * Spring Security 自动配置类,主要用于相关组件的配置
  * Spring Security 自动配置类,主要用于相关组件的配置
@@ -64,14 +59,6 @@ public class YudaoSecurityAutoConfiguration {
     }
     }
 
 
     /**
     /**
-     * 退出处理类 Bean
-     */
-    @Bean
-    public LogoutSuccessHandler logoutSuccessHandler(MultiUserDetailsAuthenticationProvider authenticationProvider) {
-        return new LogoutSuccessHandlerImpl(securityProperties, authenticationProvider);
-    }
-
-    /**
      * Spring Security 加密器
      * Spring Security 加密器
      * 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器
      * 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器
      *
      *
@@ -86,19 +73,9 @@ public class YudaoSecurityAutoConfiguration {
      * Token 认证过滤器 Bean
      * Token 认证过滤器 Bean
      */
      */
     @Bean
     @Bean
-    public TokenAuthenticationFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,
-                                                               GlobalExceptionHandler globalExceptionHandler) {
-        return new TokenAuthenticationFilter(securityProperties, authenticationProvider, globalExceptionHandler);
-    }
-
-    /**
-     * 身份验证的 Provider Bean,通过它实现账号 + 密码的认证
-     */
-    @Bean
-    public MultiUserDetailsAuthenticationProvider authenticationProvider(
-            List<SecurityAuthFrameworkService> securityFrameworkServices,
-            WebProperties webProperties, PasswordEncoder passwordEncoder) {
-        return new MultiUserDetailsAuthenticationProvider(securityFrameworkServices, webProperties, passwordEncoder);
+    public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler,
+                                                               OAuth2TokenApi oauth2TokenApi) {
+        return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi);
     }
     }
 
 
     /**
     /**

+ 3 - 29
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java

@@ -1,7 +1,5 @@
 package cn.iocoder.yudao.framework.security.config;
 package cn.iocoder.yudao.framework.security.config;
 
 
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
 import cn.iocoder.yudao.framework.web.config.WebProperties;
 import cn.iocoder.yudao.framework.web.config.WebProperties;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -9,7 +7,6 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -17,7 +14,6 @@ import org.springframework.security.config.http.SessionCreationPolicy;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.access.AccessDeniedHandler;
 import org.springframework.security.web.access.AccessDeniedHandler;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
 import java.util.List;
 import java.util.List;
@@ -34,8 +30,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
     @Resource
     @Resource
     private WebProperties webProperties;
     private WebProperties webProperties;
 
 
-    @Resource
-    private MultiUserDetailsAuthenticationProvider authenticationProvider;
     /**
     /**
      * 认证失败处理类 Bean
      * 认证失败处理类 Bean
      */
      */
@@ -47,11 +41,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
     @Resource
     @Resource
     private AccessDeniedHandler accessDeniedHandler;
     private AccessDeniedHandler accessDeniedHandler;
     /**
     /**
-     * 退出处理类 Bean
-     */
-    @Resource
-    private LogoutSuccessHandler logoutSuccessHandler;
-    /**
      * Token 认证过滤器 Bean
      * Token 认证过滤器 Bean
      */
      */
     @Resource
     @Resource
@@ -77,14 +66,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
     }
     }
 
 
     /**
     /**
-     * 身份认证接口
-     */
-    @Override
-    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
-        auth.authenticationProvider(authenticationProvider);
-    }
-
-    /**
      * 配置 URL 的安全配置
      * 配置 URL 的安全配置
      *
      *
      * anyRequest          |   匹配所有请求路径
      * anyRequest          |   匹配所有请求路径
@@ -114,11 +95,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
                 .headers().frameOptions().disable().and()
                 .headers().frameOptions().disable().and()
                 // 一堆自定义的 Spring Security 处理器
                 // 一堆自定义的 Spring Security 处理器
                 .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
                 .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
-                    .accessDeniedHandler(accessDeniedHandler).and()
-                // 登出地址的配置
-                .logout().logoutSuccessHandler(logoutSuccessHandler).logoutRequestMatcher(request -> // 匹配多种用户类型的登出
-                        StrUtil.equalsAny(request.getRequestURI(), buildAdminApi("/system/logout"),
-                                                                   buildAppApi("/member/logout")));
+                    .accessDeniedHandler(accessDeniedHandler);
+                // 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高
 
 
         // 设置每个请求的权限
         // 设置每个请求的权限
         httpSecurity
         httpSecurity
@@ -140,11 +118,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
         // 添加 JWT Filter
         // 添加 JWT Filter
         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
     }
     }
-
-    private String buildAdminApi(String url) {
-        return webProperties.getAdminApi().getPrefix() + url;
-    }
-
+    
     private String buildAppApi(String url) {
     private String buildAppApi(String url) {
         return webProperties.getAppApi().getPrefix() + url;
         return webProperties.getAppApi().getPrefix() + url;
     }
     }

+ 3 - 76
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java

@@ -1,14 +1,12 @@
 package cn.iocoder.yudao.framework.security.core;
 package cn.iocoder.yudao.framework.security.core;
 
 
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.map.MapUtil;
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
 import lombok.Data;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
 
 
-import java.util.*;
+import java.util.HashMap;
+import java.util.Map;
 
 
 /**
 /**
  * 登录用户信息
  * 登录用户信息
@@ -16,7 +14,7 @@ import java.util.*;
  * @author 芋道源码
  * @author 芋道源码
  */
  */
 @Data
 @Data
-public class LoginUser implements UserDetails {
+public class LoginUser {
 
 
     /**
     /**
      * 用户编号
      * 用户编号
@@ -29,38 +27,10 @@ public class LoginUser implements UserDetails {
      */
      */
     private Integer userType;
     private Integer userType;
     /**
     /**
-     * 最后更新时间
-     */
-    private Date updateTime;
-
-    /**
-     * 用户名
-     */
-    private String username;
-    /**
-     * 密码
-     */
-    private String password;
-    /**
-     * 状态
-     */
-    private Integer status;
-    /**
      * 租户编号
      * 租户编号
      */
      */
     private Long tenantId;
     private Long tenantId;
 
 
-    // ========== UserTypeEnum.ADMIN 独有字段 ==========
-    // TODO 芋艿:可以通过定义一个 Map<String, String> exts 的方式,去除管理员的字段。不过这样会导致系统比较复杂,所以暂时不去掉先;
-    /**
-     * 角色编号数组
-     */
-    private Set<Long> roleIds;
-    /**
-     * 部门编号
-     */
-    private Long deptId;
-
     // ========== 上下文 ==========
     // ========== 上下文 ==========
     /**
     /**
      * 上下文字段,不进行持久化
      * 上下文字段,不进行持久化
@@ -70,49 +40,6 @@ public class LoginUser implements UserDetails {
     @JsonIgnore
     @JsonIgnore
     private Map<String, Object> context;
     private Map<String, Object> context;
 
 
-    @Override
-    @JsonIgnore// 避免序列化
-    public String getPassword() {
-        return password;
-    }
-
-    @Override
-    public String getUsername() {
-        return username;
-    }
-
-    @Override
-    @JsonIgnore// 避免序列化
-    public boolean isEnabled() {
-        return CommonStatusEnum.ENABLE.getStatus().equals(status);
-    }
-
-    @Override
-    @JsonIgnore// 避免序列化
-    public Collection<? extends GrantedAuthority> getAuthorities() {
-        return new HashSet<>();
-    }
-
-    @Override
-    @JsonIgnore// 避免序列化
-    public boolean isAccountNonExpired() {
-        return true; // 返回 true,不依赖 Spring Security 判断
-    }
-
-    @Override
-    @JsonIgnore// 避免序列化
-    public boolean isAccountNonLocked() {
-        return true; // 返回 true,不依赖 Spring Security 判断
-    }
-
-    @Override
-    @JsonIgnore// 避免序列化
-    public boolean isCredentialsNonExpired() {
-        return true;  // 返回 true,不依赖 Spring Security 判断
-    }
-
-    // ========== 上下文 ==========
-
     public void setContext(String key, Object value) {
     public void setContext(String key, Object value) {
         if (context == null) {
         if (context == null) {
             context = new HashMap<>();
             context = new HashMap<>();

+ 0 - 149
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java

@@ -1,149 +0,0 @@
-package cn.iocoder.yudao.framework.security.core.authentication;
-
-import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
-import cn.iocoder.yudao.framework.web.config.WebProperties;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.crypto.password.PasswordEncoder;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * 支持多用户类型的 AuthenticationProvider 实现类
- *
- * 为什么不用 {@link org.springframework.security.authentication.ProviderManager} 呢?
- * 原因是,需要每个用户类型实现对应的 {@link AuthenticationProvider} + authentication,略显麻烦。实际,也是可以实现的。
- *
- * 另外,额外支持 verifyTokenAndRefresh 校验令牌、logout 登出、mockLogin 模拟登陆等操作。
- * 实际上,它就是 {@link SecurityAuthFrameworkService} 定义的三个接口。
- * 因为需要支持多种类型,所以需要根据请求的 URL,判断出对应的用户类型,从而使用对应的 SecurityAuthFrameworkService 是吸纳
- *
- * @see cn.iocoder.yudao.framework.common.enums.UserTypeEnum
- * @author 芋道源码
- */
-public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
-
-    private final Map<UserTypeEnum, SecurityAuthFrameworkService> services = new HashMap<>();
-
-    private final WebProperties properties;
-
-    private final PasswordEncoder passwordEncoder;
-
-    public MultiUserDetailsAuthenticationProvider(List<SecurityAuthFrameworkService> serviceList,
-                                                  WebProperties properties, PasswordEncoder passwordEncoder) {
-        serviceList.forEach(service -> services.put(service.getUserType(), service));
-        this.properties = properties;
-        this.passwordEncoder = passwordEncoder;
-    }
-
-    // ========== AuthenticationProvider 相关 ==========
-
-    @Override
-    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
-            throws AuthenticationException {
-        // 执行用户的加载
-        return selectService(authentication).loadUserByUsername(username);
-    }
-
-    private SecurityAuthFrameworkService selectService(UsernamePasswordAuthenticationToken authentication) {
-        // 第一步,获得用户类型
-        UserTypeEnum userType = getUserType(authentication);
-        // 第二步,获得 SecurityAuthFrameworkService
-        SecurityAuthFrameworkService service = services.get(userType);
-        Assert.notNull(service, "用户类型({}) 找不到 SecurityAuthFrameworkService 实现类", userType);
-        return service;
-    }
-
-    private UserTypeEnum getUserType(UsernamePasswordAuthenticationToken authentication) {
-        Assert.isInstanceOf(MultiUsernamePasswordAuthenticationToken.class, authentication);
-        MultiUsernamePasswordAuthenticationToken multiAuthentication = (MultiUsernamePasswordAuthenticationToken) authentication;
-        UserTypeEnum userType = multiAuthentication.getUserType();
-        Assert.notNull(userType, "用户类型不能为空");
-        return userType;
-    }
-
-    @Override // copy 自 DaoAuthenticationProvider 的 additionalAuthenticationChecks 方法
-    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
-            throws AuthenticationException {
-        // 校验 credentials
-        if (authentication.getCredentials() == null) {
-            this.logger.debug("Failed to authenticate since no credentials provided");
-            throw new BadCredentialsException(this.messages.getMessage(
-                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
-        }
-        // 校验 password
-        String presentedPassword = authentication.getCredentials().toString();
-        if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
-            this.logger.debug("Failed to authenticate since password does not match stored value");
-            throw new BadCredentialsException(this.messages.getMessage(
-                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
-        }
-    }
-
-    // ========== SecurityAuthFrameworkService 相关 ==========
-
-    /**
-     * 校验 token 的有效性,并获取用户信息
-     * 通过后,刷新 token 的过期时间
-     *
-     * @param request 请求
-     * @param token token
-     * @return 用户信息
-     */
-    public LoginUser verifyTokenAndRefresh(HttpServletRequest request, String token) {
-        return selectService(request).verifyTokenAndRefresh(token);
-    }
-
-    /**
-     * 模拟指定用户编号的 LoginUser
-     *
-     * @param request 请求
-     * @param userId 用户编号
-     * @return 登录用户
-     */
-    public LoginUser mockLogin(HttpServletRequest request, Long userId) {
-        return selectService(request).mockLogin(userId);
-    }
-
-    /**
-     * 基于 token 退出登录
-     *
-     * @param request 请求
-     * @param token token
-     */
-    public void logout(HttpServletRequest request, String token) {
-        selectService(request).logout(token);
-    }
-
-    private SecurityAuthFrameworkService selectService(HttpServletRequest request) {
-        // 第一步,获得用户类型
-        UserTypeEnum userType = getUserType(request);
-        // 第二步,获得 SecurityAuthFrameworkService
-        SecurityAuthFrameworkService service = services.get(userType);
-        Assert.notNull(service, "URI({}) 用户类型({}) 找不到 SecurityAuthFrameworkService 实现类",
-                request.getRequestURI(), userType);
-        return service;
-    }
-
-    private UserTypeEnum getUserType(HttpServletRequest request) {
-        if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) {
-            return UserTypeEnum.ADMIN;
-        }
-        if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) {
-            return UserTypeEnum.MEMBER;
-        }
-        throw new IllegalArgumentException(StrUtil.format("URI({}) 找不到匹配的用户类型", request.getRequestURI()));
-    }
-
-}

+ 0 - 43
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java

@@ -1,43 +0,0 @@
-package cn.iocoder.yudao.framework.security.core.authentication;
-
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import lombok.Getter;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.GrantedAuthority;
-
-import java.util.Collection;
-
-/**
- * 支持多用户的 UsernamePasswordAuthenticationToken 实现类
- *
- * @author 芋道源码
- */
-@Getter
-public class MultiUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
-
-    /**
-     * 用户类型
-     */
-    private UserTypeEnum userType;
-
-    public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials) {
-        super(principal, credentials);
-    }
-
-    public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
-                                                    Collection<? extends GrantedAuthority> authorities) {
-        super(principal, credentials, authorities);
-    }
-
-    public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials, UserTypeEnum userType) {
-        super(principal, credentials);
-        this.userType = userType;
-    }
-
-    public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
-                                                    Collection<? extends GrantedAuthority> authorities, UserTypeEnum userType) {
-        super(principal, credentials, authorities);
-        this.userType = userType;
-    }
-
-}

+ 39 - 10
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java

@@ -1,14 +1,19 @@
 package cn.iocoder.yudao.framework.security.core.filter;
 package cn.iocoder.yudao.framework.security.core.filter;
 
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
 import cn.iocoder.yudao.framework.security.core.LoginUser;
 import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
+import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
+import org.springframework.security.access.AccessDeniedException;
 import org.springframework.web.filter.OncePerRequestFilter;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 
 import javax.servlet.FilterChain;
 import javax.servlet.FilterChain;
@@ -28,24 +33,26 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
 
 
     private final SecurityProperties securityProperties;
     private final SecurityProperties securityProperties;
 
 
-    private final MultiUserDetailsAuthenticationProvider authenticationProvider;
-
     private final GlobalExceptionHandler globalExceptionHandler;
     private final GlobalExceptionHandler globalExceptionHandler;
 
 
+    private final OAuth2TokenApi oauth2TokenApi;
+
     @Override
     @Override
     @SuppressWarnings("NullableProblems")
     @SuppressWarnings("NullableProblems")
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
             throws ServletException, IOException {
             throws ServletException, IOException {
         String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
         String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
         if (StrUtil.isNotEmpty(token)) {
         if (StrUtil.isNotEmpty(token)) {
+            Integer userType = WebFrameworkUtils.getLoginUserType(request);
             try {
             try {
-                // 验证 token 有效性
-                LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token);
-                // 模拟 Login 功能,方便日常开发调试
+                // 1.1 基于 token 构建登录用户
+                LoginUser loginUser = buildLoginUserByToken(token, userType);
+                // 1.2 模拟 Login 功能,方便日常开发调试
                 if (loginUser == null) {
                 if (loginUser == null) {
-                    loginUser = mockLoginUser(request, token);
+                    loginUser = mockLoginUser(request, token, userType);
                 }
                 }
-                // 设置当前用户
+
+                // 2. 设置当前用户
                 if (loginUser != null) {
                 if (loginUser != null) {
                     SecurityFrameworkUtils.setLoginUser(loginUser, request);
                     SecurityFrameworkUtils.setLoginUser(loginUser, request);
                 }
                 }
@@ -60,6 +67,25 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
         chain.doFilter(request, response);
         chain.doFilter(request, response);
     }
     }
 
 
+    private LoginUser buildLoginUserByToken(String token, Integer userType) {
+        try {
+            OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token);
+            if (accessToken == null) {
+                return null;
+            }
+            // 用户类型不匹配,无权限
+            if (ObjectUtil.notEqual(accessToken.getUserType(), userType)) {
+                throw new AccessDeniedException("错误的用户类型");
+            }
+            // 构建登录用户
+            return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
+                    .setTenantId(accessToken.getTenantId());
+        } catch (ServiceException serviceException) {
+            // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
+            return null;
+        }
+    }
+
     /**
     /**
      * 模拟登录用户,方便日常开发调试
      * 模拟登录用户,方便日常开发调试
      *
      *
@@ -67,9 +93,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
      *
      *
      * @param request 请求
      * @param request 请求
      * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
      * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
+     * @param userType 用户类型
      * @return 模拟的 LoginUser
      * @return 模拟的 LoginUser
      */
      */
-    private LoginUser mockLoginUser(HttpServletRequest request, String token) {
+    private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) {
         if (!securityProperties.getMockEnable()) {
         if (!securityProperties.getMockEnable()) {
             return null;
             return null;
         }
         }
@@ -77,8 +104,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
         if (!token.startsWith(securityProperties.getMockSecret())) {
         if (!token.startsWith(securityProperties.getMockSecret())) {
             return null;
             return null;
         }
         }
+        // 构建模拟用户
         Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
         Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
-        return authenticationProvider.mockLogin(request, userId);
+        return new LoginUser().setId(userId).setUserType(userType)
+                .setTenantId(WebFrameworkUtils.getTenantId(request));
     }
     }
 
 
 }
 }

+ 0 - 40
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java

@@ -1,40 +0,0 @@
-package cn.iocoder.yudao.framework.security.core.handler;
-
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
-import cn.iocoder.yudao.framework.security.config.SecurityProperties;
-import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
-import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
-import lombok.AllArgsConstructor;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-
-/**
- * 自定义退出处理器
- *
- * @author ruoyi
- */
-@AllArgsConstructor
-public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
-
-    private final SecurityProperties securityProperties;
-
-    private final MultiUserDetailsAuthenticationProvider authenticationProvider;
-
-    @Override
-    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
-        // 执行退出
-        String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
-        if (StrUtil.isNotBlank(token)) {
-            authenticationProvider.logout(request, token);
-        }
-        // 返回成功
-        ServletUtils.writeJSON(response, CommonResult.success(null));
-    }
-
-}

+ 0 - 45
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java

@@ -1,45 +0,0 @@
-package cn.iocoder.yudao.framework.security.core.service;
-
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import org.springframework.security.core.userdetails.UserDetailsService;
-
-/**
- * Security 框架 Auth Service 接口,定义不同用户类型的 {@link UserTypeEnum} 需要实现的方法
- *
- * @author 芋道源码
- */
-public interface SecurityAuthFrameworkService extends UserDetailsService {
-
-    /**
-     * 校验 token 的有效性,并获取用户信息
-     * 通过后,刷新 token 的过期时间
-     *
-     * @param token token
-     * @return 用户信息
-     */
-    LoginUser verifyTokenAndRefresh(String token);
-
-    /**
-     * 模拟指定用户编号的 LoginUser
-     *
-     * @param userId 用户编号
-     * @return 登录用户
-     */
-    LoginUser mockLogin(Long userId);
-
-    /**
-     * 基于 token 退出登录
-     *
-     * @param token token
-     */
-    void logout(String token);
-
-    /**
-     * 获得用户类型。每个用户类型,对应一个 SecurityAuthFrameworkService 实现类。
-     *
-     * @return 用户类型
-     */
-    UserTypeEnum getUserType();
-
-}

+ 2 - 13
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java

@@ -11,7 +11,7 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS
 import org.springframework.util.StringUtils;
 import org.springframework.util.StringUtils;
 
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequest;
-import java.util.Set;
+import java.util.Collections;
 
 
 /**
 /**
  * 安全服务工具类
  * 安全服务工具类
@@ -80,17 +80,6 @@ public class SecurityFrameworkUtils {
     }
     }
 
 
     /**
     /**
-     * 获得当前用户的角色编号数组
-     *
-     * @return 角色编号数组
-     */
-    @Nullable
-    public static Set<Long> getLoginUserRoleIds() {
-        LoginUser loginUser = getLoginUser();
-        return loginUser != null ? loginUser.getRoleIds() : null;
-    }
-
-    /**
      * 设置当前用户
      * 设置当前用户
      *
      *
      * @param loginUser 登录用户
      * @param loginUser 登录用户
@@ -110,7 +99,7 @@ public class SecurityFrameworkUtils {
     private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
     private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
         // 创建 UsernamePasswordAuthenticationToken 对象
         // 创建 UsernamePasswordAuthenticationToken 对象
         UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
         UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
-                loginUser, null, loginUser.getAuthorities());
+                loginUser, null, Collections.emptyList());
         authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
         authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
         return authenticationToken;
         return authenticationToken;
     }
     }

+ 8 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
 import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
 import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -65,6 +66,13 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
         return new GlobalResponseBodyHandler();
         return new GlobalResponseBodyHandler();
     }
     }
 
 
+    @Bean
+    @SuppressWarnings("InstantiationOfUtilityClass")
+    public WebFrameworkUtils webFrameworkUtils(WebProperties webProperties) {
+        // 由于 WebFrameworkUtils 需要使用到 webProperties 属性,所以注册为一个 Bean
+        return new WebFrameworkUtils(webProperties);
+    }
+
     // ========== Filter 相关 ==========
     // ========== Filter 相关 ==========
 
 
     /**
     /**

+ 44 - 2
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java

@@ -1,7 +1,9 @@
 package cn.iocoder.yudao.framework.web.core.util;
 package cn.iocoder.yudao.framework.web.core.util;
 
 
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.web.config.WebProperties;
 import org.springframework.web.context.request.RequestAttributes;
 import org.springframework.web.context.request.RequestAttributes;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 import org.springframework.web.context.request.ServletRequestAttributes;
@@ -21,16 +23,43 @@ public class WebFrameworkUtils {
 
 
     private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result";
     private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result";
 
 
+    private static final String HEADER_TENANT_ID = "tenant-id";
+
+    private static WebProperties properties;
+
+    public WebFrameworkUtils(WebProperties webProperties) {
+        WebFrameworkUtils.properties = webProperties;
+    }
+
+    /**
+     * 获得租户编号,从 header 中
+     * 考虑到其它 framework 组件也会使用到租户编号,所以不得不放在 WebFrameworkUtils 统一提供
+     *
+     * @param request 请求
+     * @return 租户编号
+     */
+    public static Long getTenantId(HttpServletRequest request) {
+        String tenantId = request.getHeader(HEADER_TENANT_ID);
+        return StrUtil.isNotEmpty(tenantId) ? Long.valueOf(tenantId) : null;
+    }
+
     public static void setLoginUserId(ServletRequest request, Long userId) {
     public static void setLoginUserId(ServletRequest request, Long userId) {
         request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId);
         request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId);
     }
     }
 
 
+    /**
+     * 设置用户类型
+     *
+     * @param request 请求
+     * @param userType 用户类型
+     */
     public static void setLoginUserType(ServletRequest request, Integer userType) {
     public static void setLoginUserType(ServletRequest request, Integer userType) {
         request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType);
         request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType);
     }
     }
 
 
     /**
     /**
      * 获得当前用户的编号,从请求中
      * 获得当前用户的编号,从请求中
+     * 注意:该方法仅限于 framework 框架使用!!!
      *
      *
      * @param request 请求
      * @param request 请求
      * @return 用户编号
      * @return 用户编号
@@ -43,7 +72,8 @@ public class WebFrameworkUtils {
     }
     }
 
 
     /**
     /**
-     * 获得当前用户的类型,从请求中
+     * 获得当前用户的类型
+     * 注意:该方法仅限于 web 相关的 framework 组件使用!!!
      *
      *
      * @param request 请求
      * @param request 请求
      * @return 用户编号
      * @return 用户编号
@@ -52,7 +82,19 @@ public class WebFrameworkUtils {
         if (request == null) {
         if (request == null) {
             return null;
             return null;
         }
         }
-        return (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);
+        // 1. 优先,从 Attribute 中获取
+        Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);
+        if (userType != null) {
+            return userType;
+        }
+        // 2. 其次,基于 URL 前缀的约定
+        if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) {
+            return UserTypeEnum.ADMIN.getValue();
+        }
+        if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) {
+            return UserTypeEnum.MEMBER.getValue();
+        }
+        return null;
     }
     }
 
 
     public static Integer getLoginUserType() {
     public static Integer getLoginUserType() {

+ 3 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java

@@ -4,6 +4,7 @@ import cn.hutool.core.io.IoUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO;
 import cn.iocoder.yudao.module.infra.convert.file.FileConvert;
 import cn.iocoder.yudao.module.infra.convert.file.FileConvert;
@@ -42,8 +43,9 @@ public class FileController {
             @ApiImplicitParam(name = "file", value = "文件附件", required = true, dataTypeClass = MultipartFile.class),
             @ApiImplicitParam(name = "file", value = "文件附件", required = true, dataTypeClass = MultipartFile.class),
             @ApiImplicitParam(name = "path", value = "文件路径", example = "yudaoyuanma.png", dataTypeClass = String.class)
             @ApiImplicitParam(name = "path", value = "文件路径", example = "yudaoyuanma.png", dataTypeClass = String.class)
     })
     })
+    @OperateLog(logArgs = false) // 上传文件,没有记录操作日志的必要
     public CommonResult<String> uploadFile(@RequestParam("file") MultipartFile file,
     public CommonResult<String> uploadFile(@RequestParam("file") MultipartFile file,
-                                           @RequestParam("path") String path) throws Exception {
+                                           @RequestParam(value = "path", required = false) String path) throws Exception {
         return success(fileService.createFile(path, IoUtil.readBytes(file.getInputStream())));
         return success(fileService.createFile(path, IoUtil.readBytes(file.getInputStream())));
     }
     }
 
 

+ 9 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java

@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.infra.service.file;
 
 
 import cn.hutool.core.io.FileTypeUtil;
 import cn.hutool.core.io.FileTypeUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.file.core.client.FileClient;
 import cn.iocoder.yudao.framework.file.core.client.FileClient;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
@@ -36,6 +38,12 @@ public class FileServiceImpl implements FileService {
 
 
     @Override
     @Override
     public String createFile(String path, byte[] content) throws Exception {
     public String createFile(String path, byte[] content) throws Exception {
+        // 计算默认的 path 名
+        String type = FileTypeUtil.getType(new ByteArrayInputStream(content));
+        if (StrUtil.isEmpty(path)) {
+            path = DigestUtil.md5Hex(content) + '.' + type;
+        }
+
         // 上传到文件存储器
         // 上传到文件存储器
         FileClient client = fileConfigService.getMasterFileClient();
         FileClient client = fileConfigService.getMasterFileClient();
         Assert.notNull(client, "客户端(master) 不能为空");
         Assert.notNull(client, "客户端(master) 不能为空");
@@ -46,7 +54,7 @@ public class FileServiceImpl implements FileService {
         file.setConfigId(client.getId());
         file.setConfigId(client.getId());
         file.setPath(path);
         file.setPath(path);
         file.setUrl(url);
         file.setUrl(url);
-        file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content)));
+        file.setType(type);
         file.setSize(content.length);
         file.setSize(content.length);
         fileMapper.insert(file);
         fileMapper.insert(file);
         return url;
         return url;

+ 0 - 1
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java

@@ -17,7 +17,6 @@ public interface ErrorCodeConstants {
     // ========== AUTH 模块 1004003000 ==========
     // ========== AUTH 模块 1004003000 ==========
     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");
     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");
     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用");
     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用");
-    ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1004003002, "登录失败"); // 登录失败的兜底,未知原因
     ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期");
     ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期");
     ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定");
     ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定");
 
 

+ 8 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http

@@ -1,5 +1,5 @@
 ### 请求 /login 接口 => 成功
 ### 请求 /login 接口 => 成功
-POST {{appApi}}/member/login
+POST {{appApi}}/member/auth/login
 Content-Type: application/json
 Content-Type: application/json
 tenant-id: {{appTenentId}}
 tenant-id: {{appTenentId}}
 
 
@@ -19,7 +19,7 @@ tenant-id: {{appTenentId}}
 }
 }
 
 
 ### 请求 /sms-login 接口 => 成功
 ### 请求 /sms-login 接口 => 成功
-POST {{appApi}}/member/sms-login
+POST {{appApi}}/member/auth/sms-login
 Content-Type: application/json
 Content-Type: application/json
 tenant-id: {{appTenentId}}
 tenant-id: {{appTenentId}}
 
 
@@ -29,7 +29,12 @@ tenant-id: {{appTenentId}}
 }
 }
 
 
 ### 请求 /logout 接口 => 成功
 ### 请求 /logout 接口 => 成功
-POST {{appApi}}/member/logout
+POST {{appApi}}/member/auth/logout
 Content-Type: application/json
 Content-Type: application/json
 Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66
 Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66
 tenant-id: {{appTenentId}}
 tenant-id: {{appTenentId}}
+
+### 请求 /auth/refresh-token 接口 => 成功
+POST {{appApi}}/member/auth/refresh-token?refreshToken=bc43d929094849a28b3a69f6e6940d70
+Content-Type: application/json
+tenant-id: {{appTenentId}}

+ 34 - 13
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java

@@ -1,7 +1,11 @@
 package cn.iocoder.yudao.module.member.controller.app.auth;
 package cn.iocoder.yudao.module.member.controller.app.auth;
 
 
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.framework.security.config.SecurityProperties;
 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 import cn.iocoder.yudao.module.member.service.auth.MemberAuthService;
 import cn.iocoder.yudao.module.member.service.auth.MemberAuthService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.Api;
@@ -13,11 +17,10 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
 import javax.validation.Valid;
 
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
-import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
 
 @Api(tags = "用户 APP - 认证")
 @Api(tags = "用户 APP - 认证")
@@ -30,19 +33,39 @@ public class AppAuthController {
     @Resource
     @Resource
     private MemberAuthService authService;
     private MemberAuthService authService;
 
 
+    @Resource
+    private SecurityProperties securityProperties;
+
     @PostMapping("/login")
     @PostMapping("/login")
     @ApiOperation("使用手机 + 密码登录")
     @ApiOperation("使用手机 + 密码登录")
     public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
     public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
-        String token = authService.login(reqVO, getClientIP(), getUserAgent());
-        return success(AppAuthLoginRespVO.builder().token(token).build());
+        return success(authService.login(reqVO));
+    }
+
+    @PostMapping("/logout")
+    @ApiOperation("登出系统")
+    public CommonResult<Boolean> logout(HttpServletRequest request) {
+        String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
+        if (StrUtil.isNotBlank(token)) {
+            authService.logout(token);
+        }
+        return success(true);
+    }
+
+    @PostMapping("/refresh-token")
+    @ApiOperation("刷新令牌")
+    @ApiImplicitParam(name = "refreshToken", value = "刷新令牌", required = true, dataTypeClass = String.class)
+    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+    public CommonResult<AppAuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
+        return success(authService.refreshToken(refreshToken));
     }
     }
 
 
+    // ========== 短信登录相关 ==========
+
     @PostMapping("/sms-login")
     @PostMapping("/sms-login")
     @ApiOperation("使用手机 + 验证码登录")
     @ApiOperation("使用手机 + 验证码登录")
     public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
     public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
-        String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
-        // 返回结果
-        return success(AppAuthLoginRespVO.builder().token(token).build());
+        return success(authService.smsLogin(reqVO));
     }
     }
 
 
     @PostMapping("/send-sms-code")
     @PostMapping("/send-sms-code")
@@ -83,16 +106,14 @@ public class AppAuthController {
 
 
     @PostMapping("/social-quick-login")
     @PostMapping("/social-quick-login")
     @ApiOperation(value = "社交快捷登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户")
     @ApiOperation(value = "社交快捷登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户")
-    public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialQuickLoginReqVO reqVO) {
-        String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent());
-        return success(AppAuthLoginRespVO.builder().token(token).build());
+    public CommonResult<AppAuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AppAuthSocialQuickLoginReqVO reqVO) {
+        return success(authService.socialQuickLogin(reqVO));
     }
     }
 
 
     @PostMapping("/social-bind-login")
     @PostMapping("/social-bind-login")
     @ApiOperation(value = "社交绑定登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定")
     @ApiOperation(value = "社交绑定登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定")
-    public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialBindLoginReqVO reqVO) {
-        String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent());
-        return success(AppAuthLoginRespVO.builder().token(token).build());
+    public CommonResult<AppAuthLoginRespVO> socialBindLogin(@RequestBody @Valid AppAuthSocialBindLoginReqVO reqVO) {
+        return success(authService.socialBindLogin(reqVO));
     }
     }
 
 
 }
 }

+ 14 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java

@@ -7,14 +7,25 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.NoArgsConstructor;
 
 
-@ApiModel("用户 APP - 手机密码登录 Response VO")
+import java.util.Date;
+
+@ApiModel("用户 APP - 登录 Response VO")
 @Data
 @Data
 @NoArgsConstructor
 @NoArgsConstructor
 @AllArgsConstructor
 @AllArgsConstructor
 @Builder
 @Builder
 public class AppAuthLoginRespVO {
 public class AppAuthLoginRespVO {
 
 
-    @ApiModelProperty(value = "token", required = true, example = "yudaoyuanma")
-    private String token;
+    @ApiModelProperty(value = "用户编号", required = true, example = "1024")
+    private Long userId;
+
+    @ApiModelProperty(value = "访问令牌", required = true, example = "happy")
+    private String accessToken;
+
+    @ApiModelProperty(value = "刷新令牌", required = true, example = "nice")
+    private String refreshToken;
+
+    @ApiModelProperty(value = "过期时间", required = true)
+    private Date expiresTime;
 
 
 }
 }

+ 3 - 12
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java

@@ -1,17 +1,14 @@
 package cn.iocoder.yudao.module.member.convert.auth;
 package cn.iocoder.yudao.module.member.convert.auth;
 
 
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
 import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
 import org.mapstruct.factory.Mappers;
 import org.mapstruct.factory.Mappers;
 
 
 @Mapper
 @Mapper
@@ -19,14 +16,6 @@ public interface AuthConvert {
 
 
     AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
     AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
 
 
-    @Mapping(source = "mobile", target = "username")
-    LoginUser convert0(MemberUserDO bean);
-
-    default LoginUser convert(MemberUserDO bean) {
-        // 目的,为了设置 UserTypeEnum.MEMBER.getValue()
-        return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue());
-    }
-
     SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO);
     SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO);
     SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO);
     SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO);
     SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
     SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
@@ -35,4 +24,6 @@ public interface AuthConvert {
     SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
     SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
     SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
     SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
 
 
+    AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean);
+
 }
 }

+ 6 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * 属于 system 模块的 framework 封装
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.member.framework;

+ 28 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/config/SecurityConfiguration.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.member.framework.security.config;
+
+import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
+
+/**
+ * Member 模块的 Security 配置
+ */
+@Configuration("memberSecurityConfiguration")
+public class SecurityConfiguration {
+
+    @Bean("memberAuthorizeRequestsCustomizer")
+    public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
+        return new AuthorizeRequestsCustomizer() {
+
+            @Override
+            public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
+                // 登录的接口
+                registry.antMatchers(buildAdminApi("/member/auth/logout")).permitAll();
+            }
+
+        };
+    }
+
+}

+ 4 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/core/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.module.member.framework.security.core;

+ 23 - 18
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.member.service.auth;
 package cn.iocoder.yudao.module.member.service.auth;
 
 
-import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 
 
 import javax.validation.Valid;
 import javax.validation.Valid;
@@ -12,47 +11,46 @@ import javax.validation.Valid;
  *
  *
  * @author 芋道源码
  * @author 芋道源码
  */
  */
-public interface MemberAuthService extends SecurityAuthFrameworkService {
+public interface MemberAuthService {
 
 
     /**
     /**
      * 手机 + 密码登录
      * 手机 + 密码登录
      *
      *
      * @param reqVO 登录信息
      * @param reqVO 登录信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
      */
      */
-    String login(@Valid AppAuthLoginReqVO reqVO, String userIp, String userAgent);
+    AppAuthLoginRespVO login(@Valid AppAuthLoginReqVO reqVO);
+
+    /**
+     * 基于 token 退出登录
+     *
+     * @param token token
+     */
+    void logout(String token);
 
 
     /**
     /**
      * 手机 + 验证码登陆
      * 手机 + 验证码登陆
      *
      *
      * @param reqVO 登陆信息
      * @param reqVO 登陆信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
      */
      */
-    String smsLogin(@Valid AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent);
+    AppAuthLoginRespVO smsLogin(@Valid AppAuthSmsLoginReqVO reqVO);
 
 
     /**
     /**
      * 社交登录,使用 code 授权码
      * 社交登录,使用 code 授权码
      *
      *
      * @param reqVO 登录信息
      * @param reqVO 登录信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
      */
      */
-    String socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
+    AppAuthLoginRespVO socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO);
 
 
     /**
     /**
      * 社交登录,使用 手机号 + 手机验证码
      * 社交登录,使用 手机号 + 手机验证码
      *
      *
      * @param reqVO 登录信息
      * @param reqVO 登录信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
      */
      */
-    String socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
+    AppAuthLoginRespVO socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO);
 
 
     /**
     /**
      * 获得社交认证 URL
      * 获得社交认证 URL
@@ -84,4 +82,11 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
      */
      */
     void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO);
     void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO);
 
 
+    /**
+     * 刷新访问令牌
+     *
+     * @param refreshToken 刷新令牌
+     * @return 登录结果
+     */
+    AppAuthLoginRespVO refreshToken(String refreshToken);
 }
 }

+ 73 - 138
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java

@@ -1,35 +1,29 @@
 package cn.iocoder.yudao.module.member.service.auth;
 package cn.iocoder.yudao.module.member.service.auth;
 
 
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
 import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
 import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
-import cn.iocoder.yudao.module.system.api.auth.UserSessionApi;
+import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
+import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientIdEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.DisabledException;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
@@ -51,19 +45,15 @@ import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
 public class MemberAuthServiceImpl implements MemberAuthService {
 public class MemberAuthServiceImpl implements MemberAuthService {
 
 
     @Resource
     @Resource
-    @Lazy // 延迟加载,因为存在相互依赖的问题
-    private AuthenticationManager authenticationManager;
-
-    @Resource
     private MemberUserService userService;
     private MemberUserService userService;
     @Resource
     @Resource
     private SmsCodeApi smsCodeApi;
     private SmsCodeApi smsCodeApi;
     @Resource
     @Resource
     private LoginLogApi loginLogApi;
     private LoginLogApi loginLogApi;
     @Resource
     @Resource
-    private UserSessionApi userSessionApi;
-    @Resource
     private SocialUserApi socialUserApi;
     private SocialUserApi socialUserApi;
+    @Resource
+    private OAuth2TokenApi oauth2TokenApi;
 
 
     @Resource
     @Resource
     private PasswordEncoder passwordEncoder;
     private PasswordEncoder passwordEncoder;
@@ -71,44 +61,31 @@ public class MemberAuthServiceImpl implements MemberAuthService {
     private MemberUserMapper userMapper;
     private MemberUserMapper userMapper;
 
 
     @Override
     @Override
-    public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
-        // 获取 username 对应的 SysUserDO
-        MemberUserDO user = userService.getUserByMobile(mobile);
-        if (user == null) {
-            throw new UsernameNotFoundException(mobile);
-        }
-        // 创建 LoginUser 对象
-        return AuthConvert.INSTANCE.convert(user);
-    }
-
-    @Override
-    public String login(AppAuthLoginReqVO reqVO, String userIp, String userAgent) {
+    public AppAuthLoginRespVO login(AppAuthLoginReqVO reqVO) {
         // 使用手机 + 密码,进行登录。
         // 使用手机 + 密码,进行登录。
-        LoginUser loginUser = this.login0(reqVO.getMobile(), reqVO.getPassword());
+        MemberUserDO user = login0(reqVO.getMobile(), reqVO.getPassword());
 
 
-        // 缓存登录用户到 Redis 中,返回 Token 令牌
-        return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
     }
     }
 
 
     @Override
     @Override
     @Transactional
     @Transactional
-    public String smsLogin(AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
+    public AppAuthLoginRespVO smsLogin(AppAuthSmsLoginReqVO reqVO) {
         // 校验验证码
         // 校验验证码
+        String userIp = getClientIP();
         smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));
         smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));
 
 
         // 获得获得注册用户
         // 获得获得注册用户
         MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp);
         MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp);
         Assert.notNull(user, "获取用户失败,结果为空");
         Assert.notNull(user, "获取用户失败,结果为空");
 
 
-        // 执行登陆
-        LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
-
-        // 缓存登录用户到 Redis 中,返回 Token 令牌
-        return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SMS, userIp, userAgent);
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS);
     }
     }
 
 
     @Override
     @Override
-    public String socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) {
+    public AppAuthLoginRespVO socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO) {
         // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
         // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
         Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
         Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
                 reqVO.getCode(), reqVO.getState());
                 reqVO.getCode(), reqVO.getState());
@@ -122,31 +99,30 @@ public class MemberAuthServiceImpl implements MemberAuthService {
             throw exception(USER_NOT_EXISTS);
             throw exception(USER_NOT_EXISTS);
         }
         }
 
 
-        // 创建 LoginUser 对象
-        LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
-
-        // 缓存登录用户到 Redis 中,返回 Token 令牌
-        return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user, null, LoginLogTypeEnum.LOGIN_SOCIAL);
     }
     }
 
 
     @Override
     @Override
-    public String socialBindLogin(AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) {
+    public AppAuthLoginRespVO socialBindLogin(AppAuthSocialBindLoginReqVO reqVO) {
         // 使用手机号、手机验证码登录
         // 使用手机号、手机验证码登录
         AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
         AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
                 .mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build();
                 .mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build();
-        String token = this.smsLogin(loginReqVO, userIp, userAgent);
-        LoginUser loginUser = userSessionApi.getLoginUser(token);
+        AppAuthLoginRespVO token = smsLogin(loginReqVO);
 
 
         // 绑定社交用户
         // 绑定社交用户
-        socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
+        socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(token.getUserId(), getUserType().getValue(), reqVO));
         return token;
         return token;
     }
     }
 
 
-    private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) {
+    private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) {
         // 插入登陆日志
         // 插入登陆日志
-        createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS);
-        // 缓存登录用户到 Redis 中,返回 Token 令牌
-        return userSessionApi.createUserSession(loginUser, userIp, userAgent);
+        createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
+        // 创建 Token 令牌
+        OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
+                .setUserId(user.getId()).setUserType(getUserType().getValue()).setClientId(OAuth2ClientIdEnum.DEFAULT.getId()));
+        // 构建返回结果
+        return AuthConvert.INSTANCE.convert(accessTokenRespDTO);
     }
     }
 
 
     @Override
     @Override
@@ -154,40 +130,32 @@ public class MemberAuthServiceImpl implements MemberAuthService {
         return socialUserApi.getAuthorizeUrl(type, redirectUri);
         return socialUserApi.getAuthorizeUrl(type, redirectUri);
     }
     }
 
 
-    private LoginUser login0(String username, String password) {
-        final LoginLogTypeEnum logType = LoginLogTypeEnum.LOGIN_USERNAME;
-        // 用户验证
-        Authentication authentication;
-        try {
-            // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
-            // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
-            authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
-                    username, password, getUserType()));
-        } catch (BadCredentialsException badCredentialsException) {
-            this.createLoginLog(username, logType, LoginResultEnum.BAD_CREDENTIALS);
+    private MemberUserDO login0(String mobile, String password) {
+        final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_MOBILE;
+        // 校验账号是否存在
+        MemberUserDO user = userService.getUserByMobile(mobile);
+        if (user == null) {
+            createLoginLog(null, mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
             throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
             throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
-        } catch (DisabledException disabledException) {
-            this.createLoginLog(username, logType, LoginResultEnum.USER_DISABLED);
+        }
+        if (!userService.isPasswordMatch(password, user.getPassword())) {
+            createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
+            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
+        }
+        // 校验是否禁用
+        if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
+            createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.USER_DISABLED);
             throw exception(AUTH_LOGIN_USER_DISABLED);
             throw exception(AUTH_LOGIN_USER_DISABLED);
-        } catch (AuthenticationException authenticationException) {
-            log.error("[login0][username({}) 发生未知异常]", username, authenticationException);
-            this.createLoginLog(username, logType, LoginResultEnum.UNKNOWN_ERROR);
-            throw exception(AUTH_LOGIN_FAIL_UNKNOWN);
         }
         }
-        Assert.notNull(authentication.getPrincipal(), "Principal 不会为空");
-        return (LoginUser) authentication.getPrincipal();
+        return user;
     }
     }
 
 
-    private void createLoginLog(String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
-        // 获得用户
-        MemberUserDO user = userService.getUserByMobile(mobile);
+    private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
         // 插入登录日志
         // 插入登录日志
         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
         reqDTO.setLogType(logType.getType());
         reqDTO.setLogType(logType.getType());
         reqDTO.setTraceId(TracerUtils.getTraceId());
         reqDTO.setTraceId(TracerUtils.getTraceId());
-        if (user != null) {
-            reqDTO.setUserId(user.getId());
-        }
+        reqDTO.setUserId(userId);
         reqDTO.setUserType(getUserType().getValue());
         reqDTO.setUserType(getUserType().getValue());
         reqDTO.setUsername(mobile);
         reqDTO.setUsername(mobile);
         reqDTO.setUserAgent(ServletUtils.getUserAgent());
         reqDTO.setUserAgent(ServletUtils.getUserAgent());
@@ -195,72 +163,20 @@ public class MemberAuthServiceImpl implements MemberAuthService {
         reqDTO.setResult(loginResult.getResult());
         reqDTO.setResult(loginResult.getResult());
         loginLogApi.createLoginLog(reqDTO);
         loginLogApi.createLoginLog(reqDTO);
         // 更新最后登录时间
         // 更新最后登录时间
-        if (user != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
-            userService.updateUserLogin(user.getId(), getClientIP());
+        if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
+            userService.updateUserLogin(userId, getClientIP());
         }
         }
     }
     }
 
 
     @Override
     @Override
-    public LoginUser verifyTokenAndRefresh(String token) {
-        // 获得 LoginUser
-        LoginUser loginUser = userSessionApi.getLoginUser(token);
-        if (loginUser == null) {
-            return null;
-        }
-        // 刷新 LoginUser 缓存
-        this.refreshLoginUserCache(token, loginUser);
-        return loginUser;
-    }
-
-    private void refreshLoginUserCache(String token, LoginUser loginUser) {
-        // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
-        if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
-                userSessionApi.getSessionTimeoutMillis() / 3) {
-            return;
-        }
-
-        // 重新加载 UserDO 信息
-        MemberUserDO user = userService.getUser(loginUser.getId());
-        if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
-            // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面
-            throw exception(AUTH_TOKEN_EXPIRED);
-        }
-
-        // 刷新 LoginUser 缓存
-        userSessionApi.refreshUserSession(token, loginUser);
-    }
-
-    @Override
-    public LoginUser mockLogin(Long userId) {
-        // 获取用户编号对应的 UserDO
-        MemberUserDO user = userService.getUser(userId);
-        if (user == null) {
-            throw new UsernameNotFoundException(String.valueOf(userId));
-        }
-
-        // 执行登陆
-        this.createLoginLog(user.getMobile(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS);
-
-        // 创建 LoginUser 对象
-        return AuthConvert.INSTANCE.convert(user);
-    }
-
-    @Override
     public void logout(String token) {
     public void logout(String token) {
-        // 查询用户信息
-        LoginUser loginUser = userSessionApi.getLoginUser(token);
-        if (loginUser == null) {
+        // 删除访问令牌
+        OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.removeAccessToken(token);
+        if (accessTokenRespDTO == null) {
             return;
             return;
         }
         }
-        // 删除 session
-        userSessionApi.deleteUserSession(token);
-        // 记录登出日志
-        this.createLogoutLog(loginUser.getId(), loginUser.getUsername());
-    }
-
-    @Override
-    public UserTypeEnum getUserType() {
-        return UserTypeEnum.MEMBER;
+        // 删除成功,则记录登出日志
+        createLogoutLog(accessTokenRespDTO.getUserId());
     }
     }
 
 
     @Override
     @Override
@@ -269,6 +185,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
         MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
         MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
 
 
         // 更新用户密码
         // 更新用户密码
+        // TODO 芋艿:需要重构到用户模块
         userMapper.updateById(MemberUserDO.builder().id(userDO.getId())
         userMapper.updateById(MemberUserDO.builder().id(userDO.getId())
                 .password(passwordEncoder.encode(reqVO.getPassword())).build());
                 .password(passwordEncoder.encode(reqVO.getPassword())).build());
     }
     }
@@ -293,6 +210,12 @@ public class MemberAuthServiceImpl implements MemberAuthService {
         smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
         smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
     }
     }
 
 
+    @Override
+    public AppAuthLoginRespVO refreshToken(String refreshToken) {
+        OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, OAuth2ClientIdEnum.DEFAULT.getId());
+        return AuthConvert.INSTANCE.convert(accessTokenDO);
+    }
+
     /**
     /**
      * 校验旧密码
      * 校验旧密码
      *
      *
@@ -321,17 +244,29 @@ public class MemberAuthServiceImpl implements MemberAuthService {
         return user;
         return user;
     }
     }
 
 
-    private void createLogoutLog(Long userId, String username) {
+    private void createLogoutLog(Long userId) {
         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
         reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
         reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
         reqDTO.setTraceId(TracerUtils.getTraceId());
         reqDTO.setTraceId(TracerUtils.getTraceId());
         reqDTO.setUserId(userId);
         reqDTO.setUserId(userId);
         reqDTO.setUserType(getUserType().getValue());
         reqDTO.setUserType(getUserType().getValue());
-        reqDTO.setUsername(username);
+        reqDTO.setUsername(getMobile(userId));
         reqDTO.setUserAgent(ServletUtils.getUserAgent());
         reqDTO.setUserAgent(ServletUtils.getUserAgent());
         reqDTO.setUserIp(getClientIP());
         reqDTO.setUserIp(getClientIP());
         reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
         reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
         loginLogApi.createLoginLog(reqDTO);
         loginLogApi.createLoginLog(reqDTO);
     }
     }
 
 
+    private String getMobile(Long userId) {
+        if (userId == null) {
+            return null;
+        }
+        MemberUserDO user = userService.getUser(userId);
+        return user != null ? user.getMobile() : null;
+    }
+
+    private UserTypeEnum getUserType() {
+        return UserTypeEnum.MEMBER;
+    }
+
 }
 }

+ 9 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java

@@ -69,4 +69,13 @@ public interface MemberUserService {
      */
      */
     void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO);
     void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO);
 
 
+    /**
+     * 判断密码是否匹配
+     *
+     * @param rawPassword 未加密的密码
+     * @param encodedPassword 加密后的密码
+     * @return 是否匹配
+     */
+    boolean isPasswordMatch(String rawPassword, String encodedPassword);
+
 }
 }

+ 16 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java

@@ -69,7 +69,7 @@ public class MemberUserServiceImpl implements MemberUserService {
         MemberUserDO user = new MemberUserDO();
         MemberUserDO user = new MemberUserDO();
         user.setMobile(mobile);
         user.setMobile(mobile);
         user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
         user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
-        user.setPassword(passwordEncoder.encode(password)); // 加密密码
+        user.setPassword(encodePassword(password)); // 加密密码
         user.setRegisterIp(registerIp);
         user.setRegisterIp(registerIp);
         memberUserMapper.insert(user);
         memberUserMapper.insert(user);
         return user;
         return user;
@@ -127,6 +127,21 @@ public class MemberUserServiceImpl implements MemberUserService {
         memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build());
         memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build());
     }
     }
 
 
+    @Override
+    public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+
+    /**
+     * 对密码进行加密
+     *
+     * @param password 密码
+     * @return 加密后的密码
+     */
+    private String encodePassword(String password) {
+        return passwordEncoder.encode(password);
+    }
+
     @VisibleForTesting
     @VisibleForTesting
     public MemberUserDO checkUserExists(Long id) {
     public MemberUserDO checkUserExists(Long id) {
         if (id == null) {
         if (id == null) {

+ 4 - 5
yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java

@@ -9,14 +9,13 @@ import cn.iocoder.yudao.module.member.controller.app.auth.vo.AppAuthUpdatePasswo
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
-import cn.iocoder.yudao.module.system.api.auth.UserSessionApi;
+import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.Import;
-import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
@@ -38,8 +37,8 @@ import static org.mockito.Mockito.when;
 @Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class})
 @Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class})
 public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
 public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
 
 
-    @MockBean
-    private AuthenticationManager authenticationManager;
+    // TODO @芋艿:登录相关的单测,待补全
+
     @MockBean
     @MockBean
     private MemberUserService userService;
     private MemberUserService userService;
     @MockBean
     @MockBean
@@ -47,7 +46,7 @@ public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
     @MockBean
     @MockBean
     private LoginLogApi loginLogApi;
     private LoginLogApi loginLogApi;
     @MockBean
     @MockBean
-    private UserSessionApi userSessionApi;
+    private OAuth2TokenApi oauth2TokenApi;
     @MockBean
     @MockBean
     private SocialUserApi socialUserApi;
     private SocialUserApi socialUserApi;
     @MockBean
     @MockBean

+ 0 - 7
yudao-module-system/yudao-module-system-api/pom.xml

@@ -29,13 +29,6 @@
             <optional>true</optional>
             <optional>true</optional>
         </dependency>
         </dependency>
 
 
-        <!-- 用户信息 -->
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-security</artifactId>
-            <optional>true</optional>
-        </dependency>
-
     </dependencies>
     </dependencies>
 
 
 </project>
 </project>

+ 49 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java

@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.system.api.auth;
+
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
+
+import javax.validation.Valid;
+
+/**
+ * OAuth2.0 Token API 接口
+ *
+ * @author 芋道源码
+ */
+public interface OAuth2TokenApi {
+
+    /**
+     * 创建访问令牌
+     *
+     * @param reqDTO 访问令牌的创建信息
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenRespDTO createAccessToken(@Valid OAuth2AccessTokenCreateReqDTO reqDTO);
+
+    /**
+     * 校验访问令牌
+     *
+     * @param accessToken 访问令牌
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken);
+
+    /**
+     * 移除访问令牌
+     *
+     * @param accessToken 访问令牌
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenRespDTO removeAccessToken(String accessToken);
+
+    /**
+     * 刷新访问令牌
+     *
+     * @param refreshToken 刷新令牌
+     * @param clientId 客户端编号
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, Long clientId);
+
+}

+ 0 - 56
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java

@@ -1,56 +0,0 @@
-package cn.iocoder.yudao.module.system.api.auth;
-
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-
-/**
- * 在线用户 Session API 接口
- *
- * @author 芋道源码
- */
-public interface UserSessionApi {
-
-    /**
-     * 创建在线用户 Session
-     *
-     * @param loginUser 登录用户
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return Token 令牌
-     */
-    String createUserSession(@NotNull(message = "登录用户不能为空") LoginUser loginUser, String userIp, String userAgent);
-
-    /**
-     * 刷新在线用户 Session 的更新时间
-     *
-     * @param token Token 令牌
-     * @param loginUser 登录用户
-     */
-    void refreshUserSession(@NotEmpty(message = "Token 令牌不能为空") String token,
-                            @NotNull(message = "登录用户不能为空") LoginUser loginUser);
-
-    /**
-     * 删除在线用户 Session
-     *
-     * @param token Token 令牌
-     */
-    void deleteUserSession(String token);
-
-    /**
-     * 获得 Token 令牌对应的在线用户
-     *
-     * @param token Token 令牌
-     * @return 在线用户
-     */
-    LoginUser getLoginUser(String token);
-
-    /**
-     * 获得 Session 超时时间,单位:毫秒
-     *
-     * @return 超时时间
-     */
-    Long getSessionTimeoutMillis();
-
-}

+ 28 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.system.api.auth.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * OAuth2.0 访问令牌的校验 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class OAuth2AccessTokenCheckRespDTO implements Serializable {
+
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    private Integer userType;
+    /**
+     * 租户编号
+     */
+    private Long tenantId;
+
+}

+ 35 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.system.api.auth.dto;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * OAuth2.0 访问令牌创建 Request DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class OAuth2AccessTokenCreateReqDTO implements Serializable {
+
+    /**
+     * 用户编号
+     */
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    @NotNull(message = "用户类型不能为空")
+    @InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}")
+    private Integer userType;
+    /**
+     * 客户端编号
+     */
+    @NotNull(message = "客户端编号不能为空")
+    private Long clientId;
+
+}

+ 39 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.system.api.auth.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * OAuth2.0 访问令牌的信息 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+@Accessors(chain = true)
+public class OAuth2AccessTokenRespDTO implements Serializable {
+
+    /**
+     * 访问令牌
+     */
+    private String accessToken;
+    /**
+     * 刷新令牌
+     */
+    private String refreshToken;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    private Integer userType;
+    /**
+     * 过期时间
+     */
+    private Date expiresTime;
+
+}

+ 4 - 2
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java

@@ -12,7 +12,6 @@ public interface ErrorCodeConstants {
     // ========== AUTH 模块 1002000000 ==========
     // ========== AUTH 模块 1002000000 ==========
     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确");
     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确");
     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用");
     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用");
-    ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登录失败的兜底,未知原因
     ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
     ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
     ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
     ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
     ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
     ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
@@ -120,8 +119,11 @@ public interface ErrorCodeConstants {
     ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定");
     ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定");
     ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户");
     ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户");
 
 
-    // ========== 系统感词 1002019000 =========
+    // ========== 系统感词 1002019000 =========
     ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在");
     ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在");
     ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在");
     ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在");
 
 
+    // ========== 系统敏感词 1002020000 =========
+    ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1002020000, "OAuth2 客户端不存在");
+
 }
 }

+ 17 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientIdEnum.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.system.enums.auth;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * OAuth2.0 客户端的编号枚举
+ */
+@AllArgsConstructor
+@Getter
+public enum OAuth2ClientIdEnum {
+
+    DEFAULT(1L); // 系统默认
+
+    private final Long id;
+
+}

+ 0 - 2
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java

@@ -12,12 +12,10 @@ public enum LoginLogTypeEnum {
 
 
     LOGIN_USERNAME(100), // 使用账号登录
     LOGIN_USERNAME(100), // 使用账号登录
     LOGIN_SOCIAL(101), // 使用社交登录
     LOGIN_SOCIAL(101), // 使用社交登录
-    LOGIN_MOCK(102), // 使用 Mock 登录
     LOGIN_MOBILE(103), // 使用手机登陆
     LOGIN_MOBILE(103), // 使用手机登陆
     LOGIN_SMS(104), // 使用短信登陆
     LOGIN_SMS(104), // 使用短信登陆
 
 
     LOGOUT_SELF(200),  // 自己主动登出
     LOGOUT_SELF(200),  // 自己主动登出
-    LOGOUT_TIMEOUT(201), // 超时登出
     LOGOUT_DELETE(202), // 强制退出
     LOGOUT_DELETE(202), // 强制退出
     ;
     ;
 
 

+ 48 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.system.api.auth;
+
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
+import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
+import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * OAuth2.0 Token API 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+public class OAuth2TokenApiImpl implements OAuth2TokenApi {
+
+    @Resource
+    private OAuth2TokenService oauth2TokenService;
+
+    @Override
+    public OAuth2AccessTokenRespDTO createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) {
+        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(
+                reqDTO.getUserId(), reqDTO.getUserType(), reqDTO.getClientId());
+        return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
+    }
+
+    @Override
+    public OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken) {
+        return OAuth2TokenConvert.INSTANCE.convert(oauth2TokenService.checkAccessToken(accessToken));
+    }
+
+    @Override
+    public OAuth2AccessTokenRespDTO removeAccessToken(String accessToken) {
+        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(accessToken);
+        return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
+    }
+
+    @Override
+    public OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, Long clientId) {
+        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId);
+        return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
+    }
+
+}

+ 0 - 47
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java

@@ -1,47 +0,0 @@
-package cn.iocoder.yudao.module.system.api.auth;
-
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.module.system.service.auth.UserSessionService;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-
-/**
- * 在线用户 Session API 实现类
- *
- * @author 芋道源码
- */
-@Service
-@Validated
-public class UserSessionApiImpl implements UserSessionApi {
-
-    @Resource
-    private UserSessionService userSessionService;
-
-    @Override
-    public String createUserSession(LoginUser loginUser, String userIp, String userAgent) {
-        return userSessionService.createUserSession(loginUser, userIp, userAgent);
-    }
-
-    @Override
-    public void refreshUserSession(String token, LoginUser loginUser) {
-        userSessionService.refreshUserSession(token, loginUser);
-    }
-
-    @Override
-    public void deleteUserSession(String token) {
-        userSessionService.deleteUserSession(token);
-    }
-
-    @Override
-    public LoginUser getLoginUser(String token) {
-        return userSessionService.getLoginUser(token);
-    }
-
-    @Override
-    public Long getSessionTimeoutMillis() {
-        return userSessionService.getSessionTimeoutMillis();
-    }
-
-}

+ 3 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http

@@ -1,5 +1,5 @@
 ### 请求 /login 接口 => 成功
 ### 请求 /login 接口 => 成功
-POST {{baseUrl}}/system/login
+POST {{baseUrl}}/system/auth/login
 Content-Type: application/json
 Content-Type: application/json
 tenant-id: {{adminTenentId}}
 tenant-id: {{adminTenentId}}
 
 
@@ -11,7 +11,7 @@ tenant-id: {{adminTenentId}}
 }
 }
 
 
 ### 请求 /login 接口 => 成功(无验证码)
 ### 请求 /login 接口 => 成功(无验证码)
-POST {{baseUrl}}/system/login
+POST {{baseUrl}}/system/auth/login
 Content-Type: application/json
 Content-Type: application/json
 tenant-id: {{adminTenentId}}
 tenant-id: {{adminTenentId}}
 
 
@@ -21,7 +21,7 @@ tenant-id: {{adminTenentId}}
 }
 }
 
 
 ### 请求 /get-permission-info 接口 => 成功
 ### 请求 /get-permission-info 接口 => 成功
-GET {{baseUrl}}/system/get-permission-info
+GET {{baseUrl}}/system/auth/get-permission-info
 Authorization: Bearer {{token}}
 Authorization: Bearer {{token}}
 tenant-id: {{adminTenentId}}
 tenant-id: {{adminTenentId}}
 
 

+ 42 - 23
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java

@@ -1,14 +1,17 @@
 package cn.iocoder.yudao.module.system.controller.admin.auth;
 package cn.iocoder.yudao.module.system.controller.admin.auth;
 
 
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.framework.security.config.SecurityProperties;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
 import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
 import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
@@ -24,18 +27,19 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
 import javax.validation.Valid;
 import java.util.List;
 import java.util.List;
+import java.util.Set;
 
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
-import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
-import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserRoleIds;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
+import static java.util.Collections.singleton;
 
 
 @Api(tags = "管理后台 - 认证")
 @Api(tags = "管理后台 - 认证")
 @RestController
 @RestController
-@RequestMapping("/system/auth") // 暂时不跟 /auth 结尾
+@RequestMapping("/system/auth")
 @Validated
 @Validated
 @Slf4j
 @Slf4j
 public class AuthController {
 public class AuthController {
@@ -51,13 +55,33 @@ public class AuthController {
     @Resource
     @Resource
     private SocialUserService socialUserService;
     private SocialUserService socialUserService;
 
 
+    @Resource
+    private SecurityProperties securityProperties;
+
     @PostMapping("/login")
     @PostMapping("/login")
     @ApiOperation("使用账号密码登录")
     @ApiOperation("使用账号密码登录")
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
     public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
-        String token = authService.login(reqVO, getClientIP(), getUserAgent());
-        // 返回结果
-        return success(AuthLoginRespVO.builder().token(token).build());
+        return success(authService.login(reqVO));
+    }
+
+    @PostMapping("/logout")
+    @ApiOperation("登出系统")
+    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+    public CommonResult<Boolean> logout(HttpServletRequest request) {
+        String token = obtainAuthorization(request, securityProperties.getTokenHeader());
+        if (StrUtil.isNotBlank(token)) {
+            authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
+        }
+        return success(true);
+    }
+
+    @PostMapping("/refresh-token")
+    @ApiOperation("刷新令牌")
+    @ApiImplicitParam(name = "refreshToken", value = "刷新令牌", required = true, dataTypeClass = String.class)
+    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+    public CommonResult<AuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
+        return success(authService.refreshToken(refreshToken));
     }
     }
 
 
     @GetMapping("/get-permission-info")
     @GetMapping("/get-permission-info")
@@ -69,12 +93,12 @@ public class AuthController {
             return null;
             return null;
         }
         }
         // 获得角色列表
         // 获得角色列表
-        List<RoleDO> roleList = roleService.getRolesFromCache(getLoginUserRoleIds());
+        Set<Long> roleIds = permissionService.getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
+        List<RoleDO> roleList = roleService.getRolesFromCache(roleIds);
         // 获得菜单列表
         // 获得菜单列表
-        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(
-                getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它
+        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
-                SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus()));
+                singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
         // 拼接结果返回
         // 拼接结果返回
         return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
         return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
     }
     }
@@ -82,11 +106,12 @@ public class AuthController {
     @GetMapping("/list-menus")
     @GetMapping("/list-menus")
     @ApiOperation("获得登录用户的菜单列表")
     @ApiOperation("获得登录用户的菜单列表")
     public CommonResult<List<AuthMenuRespVO>> getMenus() {
     public CommonResult<List<AuthMenuRespVO>> getMenus() {
+        // 获得角色列表
+        Set<Long> roleIds = permissionService.getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
         // 获得用户拥有的菜单列表
         // 获得用户拥有的菜单列表
-        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(
-                getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它
+        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
-                SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
+                singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
         // 转换成 Tree 结构返回
         // 转换成 Tree 结构返回
         return success(AuthConvert.INSTANCE.buildMenuTree(menuList));
         return success(AuthConvert.INSTANCE.buildMenuTree(menuList));
     }
     }
@@ -97,9 +122,7 @@ public class AuthController {
     @ApiOperation("使用短信验证码登录")
     @ApiOperation("使用短信验证码登录")
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
     public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
-        String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
-        // 返回结果
-        return success(AuthLoginRespVO.builder().token(token).build());
+        return success(authService.smsLogin(reqVO));
     }
     }
 
 
     @PostMapping("/send-sms-code")
     @PostMapping("/send-sms-code")
@@ -127,18 +150,14 @@ public class AuthController {
     @ApiOperation("社交快捷登录,使用 code 授权码")
     @ApiOperation("社交快捷登录,使用 code 授权码")
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     public CommonResult<AuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) {
     public CommonResult<AuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) {
-        String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent());
-        // 返回结果
-        return success(AuthLoginRespVO.builder().token(token).build());
+        return success(authService.socialQuickLogin(reqVO));
     }
     }
 
 
     @PostMapping("/social-bind-login")
     @PostMapping("/social-bind-login")
     @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码")
     @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码")
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
     public CommonResult<AuthLoginRespVO> socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) {
     public CommonResult<AuthLoginRespVO> socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) {
-        String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent());
-        // 返回结果
-        return success(AuthLoginRespVO.builder().token(token).build());
+        return success(authService.socialBindLogin(reqVO));
     }
     }
 
 
 }
 }

+ 74 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2ClientController.java

@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.system.controller.admin.auth;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
+import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
+import cn.iocoder.yudao.module.system.service.auth.OAuth2ClientService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - OAuth2 客户端")
+@RestController
+@RequestMapping("/system/oauth2-client")
+@Validated
+public class OAuth2ClientController {
+
+    @Resource
+    private OAuth2ClientService oAuth2ClientService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建 OAuth2 客户端")
+    @PreAuthorize("@ss.hasPermission('system:oauth2-client:create')")
+    public CommonResult<Long> createOAuth2Client(@Valid @RequestBody OAuth2ClientCreateReqVO createReqVO) {
+        return success(oAuth2ClientService.createOAuth2Client(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新 OAuth2 客户端")
+    @PreAuthorize("@ss.hasPermission('system:oauth2-client:update')")
+    public CommonResult<Boolean> updateOAuth2Client(@Valid @RequestBody OAuth2ClientUpdateReqVO updateReqVO) {
+        oAuth2ClientService.updateOAuth2Client(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除 OAuth2 客户端")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('system:oauth2-client:delete')")
+    public CommonResult<Boolean> deleteOAuth2Client(@RequestParam("id") Long id) {
+        oAuth2ClientService.deleteOAuth2Client(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得 OAuth2 客户端")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('system:oauth2-client:query')")
+    public CommonResult<OAuth2ClientRespVO> getOAuth2Client(@RequestParam("id") Long id) {
+        OAuth2ClientDO oAuth2Client = oAuth2ClientService.getOAuth2Client(id);
+        return success(OAuth2ClientConvert.INSTANCE.convert(oAuth2Client));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得OAuth2 客户端分页")
+    @PreAuthorize("@ss.hasPermission('system:oauth2-client:query')")
+    public CommonResult<PageResult<OAuth2ClientRespVO>> getOAuth2ClientPage(@Valid OAuth2ClientPageReqVO pageVO) {
+        PageResult<OAuth2ClientDO> pageResult = oAuth2ClientService.getOAuth2ClientPage(pageVO);
+        return success(OAuth2ClientConvert.INSTANCE.convertPage(pageResult));
+    }
+
+}

+ 24 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2Controller.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.system.controller.admin.auth;
+
+import io.swagger.annotations.Api;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Api(tags = "管理后台 - OAuth2.0 授权")
+@RestController
+@RequestMapping("/system/oauth2")
+@Validated
+@Slf4j
+public class OAuth2Controller {
+
+//    POST oauth/token TokenEndpoint:Password、Implicit、Code、Refresh Token
+
+//    POST oauth/check_token CheckTokenEndpoint
+
+//    DELETE oauth/token ConsumerTokenServices#revokeToken
+
+//    GET  oauth/authorize AuthorizationEndpoint
+
+}

+ 50 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2TokenController.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.system.controller.admin.auth;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenRespVO;
+import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
+import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
+import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
+import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - OAuth2.0 令牌")
+@RestController
+@RequestMapping("/system/oauth2-token")
+public class OAuth2TokenController {
+
+    @Resource
+    private OAuth2TokenService oauth2TokenService;
+    @Resource
+    private AdminAuthService authService;
+
+    @GetMapping("/page")
+    @ApiOperation(value = "获得访问令牌分页", notes = "只返回有效期内的")
+    @PreAuthorize("@ss.hasPermission('system:oauth2-token:page')")
+    public CommonResult<PageResult<OAuth2AccessTokenRespVO>> getAccessTokenPage(@Valid OAuth2AccessTokenPageReqVO reqVO) {
+        PageResult<OAuth2AccessTokenDO> pageResult = oauth2TokenService.getAccessTokenPage(reqVO);
+        return success(OAuth2TokenConvert.INSTANCE.convert(pageResult));
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除访问令牌")
+    @ApiImplicitParam(name = "accessToken", value = "访问令牌", required = true, dataTypeClass = String.class, example = "tudou")
+    @PreAuthorize("@ss.hasPermission('system:oauth2-token:delete')")
+    public CommonResult<Boolean> deleteAccessToken(@RequestParam("accessToken") String accessToken) {
+        authService.logout(accessToken, LoginLogTypeEnum.LOGOUT_DELETE.getType());
+        return success(true);
+    }
+
+}

+ 0 - 79
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java

@@ -1,79 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth;
-
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageItemRespVO;
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
-import cn.iocoder.yudao.module.system.convert.auth.UserSessionConvert;
-import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
-import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
-import cn.iocoder.yudao.module.system.service.auth.UserSessionService;
-import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.module.system.service.dept.DeptService;
-import cn.iocoder.yudao.module.system.service.user.AdminUserService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiImplicitParam;
-import io.swagger.annotations.ApiOperation;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
-
-@Api(tags = "管理后台 - 用户 Session")
-@RestController
-@RequestMapping("/system/user-session")
-public class UserSessionController {
-
-    @Resource
-    private UserSessionService userSessionService;
-    @Resource
-    private AdminUserService userService;
-
-    @Resource
-    private DeptService deptService;
-
-    @GetMapping("/page")
-    @ApiOperation("获得 Session 分页列表")
-    @PreAuthorize("@ss.hasPermission('system:user-session:page')")
-    public CommonResult<PageResult<UserSessionPageItemRespVO>> getUserSessionPage(@Validated UserSessionPageReqVO reqVO) {
-        // 获得 Session 分页
-        PageResult<UserSessionDO> pageResult = userSessionService.getUserSessionPage(reqVO);
-
-        // 获得拼接需要的数据
-        Map<Long, AdminUserDO> userMap = userService.getUserMap(
-                convertList(pageResult.getList(), UserSessionDO::getUserId));
-        Map<Long, DeptDO> deptMap = deptService.getDeptMap(
-                convertList(userMap.values(), AdminUserDO::getDeptId));
-        // 拼接结果返回
-        List<UserSessionPageItemRespVO> sessionList = new ArrayList<>(pageResult.getList().size());
-        pageResult.getList().forEach(session -> {
-            UserSessionPageItemRespVO respVO = UserSessionConvert.INSTANCE.convert(session);
-            sessionList.add(respVO);
-            // 设置用户账号
-            MapUtils.findAndThen(userMap, session.getUserId(), user -> {
-                respVO.setUsername(user.getUsername());
-                // 设置用户部门
-                MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> respVO.setDeptName(dept.getName()));
-            });
-        });
-        return success(new PageResult<>(sessionList, pageResult.getTotal()));
-    }
-
-    @DeleteMapping("/delete")
-    @ApiOperation("删除 Session")
-    @ApiImplicitParam(name = "id", value = "Session 编号", required = true, dataTypeClass = Long.class, example = "1024")
-    @PreAuthorize("@ss.hasPermission('system:user-session:delete')")
-    public CommonResult<Boolean> deleteUserSession(@RequestParam("id") Long id) {
-        userSessionService.deleteUserSession(id);
-        return success(true);
-    }
-
-}

+ 14 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginRespVO.java

@@ -7,14 +7,25 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.NoArgsConstructor;
 
 
-@ApiModel("管理后台 - 账号密码登录 Response VO")
+import java.util.Date;
+
+@ApiModel("管理后台 - 登录 Response VO")
 @Data
 @Data
 @NoArgsConstructor
 @NoArgsConstructor
 @AllArgsConstructor
 @AllArgsConstructor
 @Builder
 @Builder
 public class AuthLoginRespVO {
 public class AuthLoginRespVO {
 
 
-    @ApiModelProperty(value = "token", required = true, example = "yudaoyuanma")
-    private String token;
+    @ApiModelProperty(value = "用户编号", required = true, example = "1024")
+    private Long userId;
+
+    @ApiModelProperty(value = "访问令牌", required = true, example = "happy")
+    private String accessToken;
+
+    @ApiModelProperty(value = "刷新令牌", required = true, example = "nice")
+    private String refreshToken;
+
+    @ApiModelProperty(value = "过期时间", required = true)
+    private Date expiresTime;
 
 
 }
 }

+ 51 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientBaseVO.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+* OAuth2 客户端 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class OAuth2ClientBaseVO {
+
+    @ApiModelProperty(value = "客户端编号", required = true)
+    @NotNull(message = "客户端编号不能为空")
+    private Long id;
+
+    @ApiModelProperty(value = "客户端密钥", required = true)
+    @NotNull(message = "客户端密钥不能为空")
+    private String secret;
+
+    @ApiModelProperty(value = "应用名", required = true)
+    @NotNull(message = "应用名不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "应用图标", required = true)
+    @NotNull(message = "应用图标不能为空")
+    private String logo;
+
+    @ApiModelProperty(value = "应用描述")
+    private String description;
+
+    @ApiModelProperty(value = "状态", required = true)
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+    @ApiModelProperty(value = "访问令牌的有效期", required = true)
+    @NotNull(message = "访问令牌的有效期不能为空")
+    private Integer accessTokenValiditySeconds;
+
+    @ApiModelProperty(value = "刷新令牌的有效期", required = true)
+    @NotNull(message = "刷新令牌的有效期不能为空")
+    private Integer refreshTokenValiditySeconds;
+
+    @ApiModelProperty(value = "可重定向的 URI 地址", required = true)
+    @NotNull(message = "可重定向的 URI 地址不能为空")
+    private List<String> redirectUris;
+
+}

+ 12 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientCreateReqVO.java

@@ -0,0 +1,12 @@
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
+
+import lombok.*;
+import io.swagger.annotations.*;
+
+@ApiModel("管理后台 - OAuth2 客户端创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class OAuth2ClientCreateReqVO extends OAuth2ClientBaseVO {
+
+}

+ 19 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientPageReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+@ApiModel("管理后台 - OAuth2 客户端分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class OAuth2ClientPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "应用名")
+    private String name;
+
+    @ApiModelProperty(value = "状态")
+    private Integer status;
+
+}

+ 20 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientRespVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - OAuth2 客户端 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class OAuth2ClientRespVO extends OAuth2ClientBaseVO {
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 14 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientUpdateReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("管理后台 - OAuth2 客户端更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class OAuth2ClientUpdateReqVO extends OAuth2ClientBaseVO {
+
+}

+ 0 - 38
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageItemRespVO.java

@@ -1,38 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.session;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-
-import java.util.Date;
-
-@ApiModel(value = "管理后台 - 用户在线 Session Response VO", description = "相比用户基本信息来说,会多部门、用户账号等信息")
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@EqualsAndHashCode(callSuper = true)
-public class UserSessionPageItemRespVO extends PageParam {
-
-    @ApiModelProperty(value = "Session 编号", required = true, example = "fe50b9f6-d177-44b1-8da9-72ea34f63db7")
-    private String id;
-
-    @ApiModelProperty(value = "用户 IP", required = true, example = "127.0.0.1")
-    private String userIp;
-
-    @ApiModelProperty(value = "浏览器 UserAgent", required = true, example = "Mozilla/5.0")
-    private String userAgent;
-
-    @ApiModelProperty(value = "登录时间", required = true)
-    private Date createTime;
-
-    @ApiModelProperty(value = "用户账号", required = true, example = "yudao")
-    private String username;
-
-    @ApiModelProperty(value = "部门名称", example = "研发部")
-    private String deptName;
-
-}

+ 0 - 20
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageReqVO.java

@@ -1,20 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.admin.auth.vo.session;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-@ApiModel("管理后台 - 在线用户 Session 分页 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class UserSessionPageReqVO extends PageParam {
-
-    @ApiModelProperty(value = "用户 IP", example = "127.0.0.1", notes = "模糊匹配")
-    private String userIp;
-
-    @ApiModelProperty(value = "用户账号", example = "yudao", notes = "模糊匹配")
-    private String username;
-
-}

+ 23 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenPageReqVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo.token;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@ApiModel("管理后台 - 访问令牌分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class OAuth2AccessTokenPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "用户编号", required = true, example = "666")
+    private Long userId;
+
+    @ApiModelProperty(value = "用户类型", required = true, example = "2", notes = "参见 UserTypeEnum 枚举")
+    private Integer userType;
+
+    @ApiModelProperty(value = "客户端编号", required = true, example = "2")
+    private Long clientId;
+
+}

+ 41 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenRespVO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.system.controller.admin.auth.vo.token;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - 访问令牌 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class OAuth2AccessTokenRespVO {
+
+    @ApiModelProperty(value = "编号", required = true, example = "1024")
+    private Long id;
+
+    @ApiModelProperty(value = "访问令牌", required = true, example = "tudou")
+    private String accessToken;
+
+    @ApiModelProperty(value = "刷新令牌", required = true, example = "nice")
+    private String refreshToken;
+
+    @ApiModelProperty(value = "用户编号", required = true, example = "666")
+    private Long userId;
+
+    @ApiModelProperty(value = "用户类型", required = true, example = "2", notes = "参见 UserTypeEnum 枚举")
+    private Integer userType;
+
+    @ApiModelProperty(value = "客户端编号", required = true, example = "2")
+    private Long clientId;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+    @ApiModelProperty(value = "过期时间", required = true)
+    private Date expiresTime;
+
+}

+ 2 - 12
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java

@@ -1,20 +1,16 @@
 package cn.iocoder.yudao.module.system.convert.auth;
 package cn.iocoder.yudao.module.system.convert.auth;
 
 
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
-import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.enums.permission.MenuIdEnum;
 import cn.iocoder.yudao.module.system.enums.permission.MenuIdEnum;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
 import org.mapstruct.factory.Mappers;
 import org.mapstruct.factory.Mappers;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 
 
@@ -25,13 +21,7 @@ public interface AuthConvert {
 
 
     AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
     AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
 
 
-    @Mapping(source = "updateTime", target = "updateTime", ignore = true) // 字段相同,但是含义不同,忽略
-    LoginUser convert0(AdminUserDO bean);
-
-    default LoginUser convert(AdminUserDO bean) {
-        // 目的,为了设置 UserTypeEnum.ADMIN.getValue()
-        return convert0(bean).setUserType(UserTypeEnum.ADMIN.getValue());
-    }
+    AuthLoginRespVO convert(OAuth2AccessTokenDO bean);
 
 
     default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
     default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
         return AuthPermissionInfoRespVO.builder()
         return AuthPermissionInfoRespVO.builder()

+ 33 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/OAuth2ClientConvert.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.system.convert.auth;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * OAuth2 客户端 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface OAuth2ClientConvert {
+
+    OAuth2ClientConvert INSTANCE = Mappers.getMapper(OAuth2ClientConvert.class);
+
+    OAuth2ClientDO convert(OAuth2ClientCreateReqVO bean);
+
+    OAuth2ClientDO convert(OAuth2ClientUpdateReqVO bean);
+
+    OAuth2ClientRespVO convert(OAuth2ClientDO bean);
+
+    List<OAuth2ClientRespVO> convertList(List<OAuth2ClientDO> list);
+
+    PageResult<OAuth2ClientRespVO> convertPage(PageResult<OAuth2ClientDO> page);
+
+}

+ 22 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/OAuth2TokenConvert.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.system.convert.auth;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
+import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenRespVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface OAuth2TokenConvert {
+
+    OAuth2TokenConvert INSTANCE = Mappers.getMapper(OAuth2TokenConvert.class);
+
+    OAuth2AccessTokenCheckRespDTO convert(OAuth2AccessTokenDO bean);
+
+    PageResult<OAuth2AccessTokenRespVO> convert(PageResult<OAuth2AccessTokenDO> page);
+
+    OAuth2AccessTokenRespDTO convert2(OAuth2AccessTokenDO bean);
+
+}

+ 0 - 15
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java

@@ -1,15 +0,0 @@
-package cn.iocoder.yudao.module.system.convert.auth;
-
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageItemRespVO;
-import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-@Mapper
-public interface UserSessionConvert {
-
-    UserSessionConvert INSTANCE = Mappers.getMapper(UserSessionConvert.class);
-
-    UserSessionPageItemRespVO convert(UserSessionDO session);
-
-}

+ 61 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java

@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.system.dal.dataobject.auth;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+/**
+ * OAuth2 访问令牌 DO
+ *
+ * 如下字段,暂时未使用,暂时不支持:
+ * user_name、authentication(用户信息)
+ *
+ * @author 芋道源码
+ */
+@TableName("system_oauth2_access_token")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public class OAuth2AccessTokenDO extends TenantBaseDO {
+
+    /**
+     * 编号,数据库递增
+     */
+    @TableId
+    private Long id;
+    /**
+     * 访问令牌
+     */
+    private String accessToken;
+    /**
+     * 刷新令牌
+     */
+    private String refreshToken;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 用户类型
+     *
+     * 枚举 {@link UserTypeEnum}
+     */
+    private Integer userType;
+    /**
+     * 客户端编号
+     *
+     * 关联 {@link OAuth2ClientDO#getId()}
+     */
+    private Long clientId;
+    /**
+     * 过期时间
+     */
+    private Date expiresTime;
+
+}

+ 73 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.system.dal.dataobject.auth;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+/**
+ * OAuth2 客户端 DO
+ *
+ * 如下字段,考虑到使用相对不是很高频,主要是一些开关,暂时不支持:
+ * authorized_grant_types、authorities、additional_information、autoapprove、resource_ids、scope
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "system_oauth2_client", autoResultMap = true)
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public class OAuth2ClientDO extends BaseDO {
+
+    /**
+     * 客户端编号
+     *
+     * 由于 SQL Server 在存储 String 主键有点问题,所以暂时使用 Long 类型
+     */
+    @TableId(type = IdType.INPUT)
+    private Long id;
+    /**
+     * 客户端密钥
+     */
+    private String secret;
+    /**
+     * 应用名
+     */
+    private String name;
+    /**
+     * 应用图标
+     */
+    private String logo;
+    /**
+     * 应用描述
+     */
+    private String description;
+    /**
+     * 状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 访问令牌的有效期
+     */
+    private Integer accessTokenValiditySeconds;
+    /**
+     * 刷新令牌的有效期
+     */
+    private Integer refreshTokenValiditySeconds;
+    /**
+     * 可重定向的 URI 地址
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private List<String> redirectUris;
+
+}

+ 62 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java

@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.system.dal.dataobject.auth;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+/**
+ * OAuth2 授权码 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("system_oauth2_code")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public class OAuth2CodeDO extends BaseDO {
+
+    /**
+     * 编号,数据库递增
+     */
+    private Long id;
+    /**
+     * 授权码
+     */
+    private String code;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 用户类型
+     *
+     * 枚举 {@link UserTypeEnum}
+     */
+    private Integer userType;
+    /**
+     * 客户端编号
+     *
+     * 关联 {@link OAuth2ClientDO#getId()}
+     */
+    private Long clientId;
+    /**
+     * 刷新令牌
+     *
+     * 关联 {@link OAuth2RefreshTokenDO#getRefreshToken()}
+     */
+    private String refreshToken;
+    /**
+     * 过期时间
+     */
+    private Date expiresTime;
+    /**
+     * 创建 IP
+     */
+    private String createIp;
+
+}

+ 52 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.system.dal.dataobject.auth;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+/**
+ * OAuth2 刷新令牌
+ *
+ * @author 芋道源码
+ */
+@TableName("system_oauth2_refresh_token")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public class OAuth2RefreshTokenDO extends BaseDO {
+
+    /**
+     * 编号,数据库字典
+     */
+    private Long id;
+    /**
+     * 刷新令牌
+     */
+    private String refreshToken;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 用户类型
+     *
+     * 枚举 {@link UserTypeEnum}
+     */
+    private Integer userType;
+    /**
+     * 客户端编号
+     *
+     * 关联 {@link OAuth2ClientDO#getId()}
+     */
+    private Long clientId;
+    /**
+     * 过期时间
+     */
+    private Date expiresTime;
+
+}

+ 0 - 71
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java

@@ -1,71 +0,0 @@
-package cn.iocoder.yudao.module.system.dal.dataobject.auth;
-
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import com.baomidou.mybatisplus.annotation.*;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-import java.util.Date;
-
-/**
- * 在线用户表
- *
- * 我们已经将 {@link LoginUser} 缓存在 Redis 当中。
- * 这里额外存储在线用户到 MySQL 中,目的是为了方便管理界面可以灵活查询。
- * 同时,通过定时轮询 UserSessionDO 表,可以主动删除 Redis 的缓存,因为 Redis 的过期删除是延迟的。
- *
- * @author 芋道源码
- */
-@TableName(value = "system_user_session")
-@KeySequence(value = "system_user_session_seq")
-@Data
-@Builder
-@EqualsAndHashCode(callSuper = true)
-public class UserSessionDO extends BaseDO {
-
-    /**
-     * 会话编号
-     */
-    private Long id;
-    /**
-     * 令牌
-     */
-    private String token;
-
-    /**
-     * 用户编号
-     *
-     * 关联 AdminUserDO.id 或者 MemberUserDO.id
-     */
-    private Long userId;
-    /**
-     * 用户类型
-     *
-     * 枚举 {@link UserTypeEnum}
-     */
-    private Integer userType;
-
-    /**
-     * 用户账号
-     *
-     * 冗余,因为账号可以变更
-     */
-    private String username;
-
-    /**
-     * 用户 IP
-     */
-    private String userIp;
-    /**
-     * 浏览器 UA
-     */
-    private String userAgent;
-    /**
-     * 会话超时时间
-     */
-    private Date sessionTimeout;
-
-}

+ 33 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.system.dal.mysql.auth;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Date;
+import java.util.List;
+
+@Mapper
+public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO> {
+
+    default OAuth2AccessTokenDO selectByAccessToken(String accessToken) {
+        return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken);
+    }
+
+    default List<OAuth2AccessTokenDO> selectListByRefreshToken(String refreshToken) {
+        return selectList(OAuth2AccessTokenDO::getRefreshToken, refreshToken);
+    }
+
+    default PageResult<OAuth2AccessTokenDO> selectPage(OAuth2AccessTokenPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<OAuth2AccessTokenDO>()
+                .eqIfPresent(OAuth2AccessTokenDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(OAuth2AccessTokenDO::getUserType, reqVO.getUserType())
+                .eqIfPresent(OAuth2AccessTokenDO::getClientId, reqVO.getClientId())
+                .gt(OAuth2AccessTokenDO::getExpiresTime, new Date())
+                .orderByDesc(OAuth2AccessTokenDO::getId));
+    }
+
+}

+ 25 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2ClientMapper.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.system.dal.mysql.auth;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * OAuth2 客户端 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface OAuth2ClientMapper extends BaseMapperX<OAuth2ClientDO> {
+
+    default PageResult<OAuth2ClientDO> selectPage(OAuth2ClientPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<OAuth2ClientDO>()
+                .likeIfPresent(OAuth2ClientDO::getName, reqVO.getName())
+                .eqIfPresent(OAuth2ClientDO::getStatus, reqVO.getStatus())
+                .orderByDesc(OAuth2ClientDO::getId));
+    }
+
+}

+ 20 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.system.dal.mysql.auth;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshTokenDO> {
+
+    default int deleteByRefreshToken(String refreshToken) {
+        return delete(new LambdaQueryWrapperX<OAuth2RefreshTokenDO>()
+                .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken));
+    }
+
+    default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) {
+        return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken);
+    }
+
+}

+ 0 - 37
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java

@@ -1,37 +0,0 @@
-package cn.iocoder.yudao.module.system.dal.mysql.auth;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
-import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-
-@Mapper
-public interface UserSessionMapper extends BaseMapperX<UserSessionDO> {
-
-    default PageResult<UserSessionDO> selectPage(UserSessionPageReqVO reqVO, Collection<Long> userIds) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<UserSessionDO>()
-                .inIfPresent(UserSessionDO::getUserId, userIds)
-                .likeIfPresent(UserSessionDO::getUserIp, reqVO.getUserIp()));
-    }
-
-    default List<UserSessionDO> selectListBySessionTimoutLt() {
-        return selectList(new LambdaQueryWrapperX<UserSessionDO>()
-                .lt(UserSessionDO::getSessionTimeout, new Date()));
-    }
-
-    default void updateByToken(String token, UserSessionDO updateObj) {
-        update(updateObj, new LambdaQueryWrapperX<UserSessionDO>()
-                .eq(UserSessionDO::getToken, token));
-    }
-
-    default void deleteByToken(String token) {
-        delete(new LambdaQueryWrapperX<UserSessionDO>().eq(UserSessionDO::getToken, token));
-    }
-
-}

+ 4 - 4
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.system.dal.redis;
 package cn.iocoder.yudao.module.system.dal.redis;
 
 
 import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
 import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
 
 
 import java.time.Duration;
 import java.time.Duration;
 
 
@@ -18,9 +18,9 @@ public interface RedisKeyConstants {
             "captcha_code:%s", // 参数为 uuid
             "captcha_code:%s", // 参数为 uuid
             STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
             STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
 
 
-    RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登录用户的缓存",
-            "login_user:%s", // 参数为 token 令牌
-            STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
+    RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存",
+            "oauth2_access_token:%s", // 参数为访问令牌 token
+            STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
 
 
     RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
     RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
             "social_auth_state:%s", // 参数为 state
             "social_auth_state:%s", // 参数为 state

+ 0 - 52
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/LoginUserRedisDAO.java

@@ -1,52 +0,0 @@
-package cn.iocoder.yudao.module.system.dal.redis.auth;
-
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
-import cn.iocoder.yudao.framework.security.config.SecurityProperties;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import org.springframework.data.redis.core.StringRedisTemplate;
-import org.springframework.stereotype.Repository;
-
-import javax.annotation.Resource;
-
-import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.LOGIN_USER;
-
-/**
- * {@link LoginUser} 的 RedisDAO
- *
- * @author 芋道源码
- */
-@Repository
-public class LoginUserRedisDAO {
-
-    @Resource
-    private StringRedisTemplate stringRedisTemplate;
-
-    @Resource
-    private SecurityProperties securityProperties;
-
-    public LoginUser get(String token) {
-        String redisKey = formatKey(token);
-        return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), LoginUser.class);
-    }
-
-    public Boolean exists(String token) {
-        String redisKey = formatKey(token);
-        return stringRedisTemplate.hasKey(redisKey);
-    }
-
-    public void set(String token, LoginUser loginUser) {
-        String redisKey = formatKey(token);
-        stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser),
-                securityProperties.getSessionTimeout());
-    }
-
-    public void delete(String token) {
-        String redisKey = formatKey(token);
-        stringRedisTemplate.delete(redisKey);
-    }
-
-    private static String formatKey(String token) {
-        return LOGIN_USER.formatKey(token);
-    }
-
-}

+ 54 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.system.dal.redis.auth;
+
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
+
+/**
+ * {@link OAuth2AccessTokenDO} 的 RedisDAO
+ *
+ * @author 芋道源码
+ */
+@Repository
+public class OAuth2AccessTokenRedisDAO {
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    public OAuth2AccessTokenDO get(String accessToken) {
+        String redisKey = formatKey(accessToken);
+        return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class);
+    }
+
+    public void set(OAuth2AccessTokenDO accessTokenDO) {
+        String redisKey = formatKey(accessTokenDO.getAccessToken());
+        // 清理多余字段,避免缓存
+        accessTokenDO.setUpdater(null).setUpdateTime(null).setCreateTime(null).setCreator(null).setDeleted(null);
+        stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(accessTokenDO),
+                accessTokenDO.getExpiresTime().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
+    }
+
+    public void delete(String accessToken) {
+        String redisKey = formatKey(accessToken);
+        stringRedisTemplate.delete(redisKey);
+    }
+
+    public void deleteList(Collection<String> accessTokens) {
+        List<String> redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey);
+        stringRedisTemplate.delete(redisKeys);
+    }
+
+    private static String formatKey(String accessToken) {
+        return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken);
+    }
+
+}

+ 2 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java

@@ -20,6 +20,8 @@ public class SecurityConfiguration {
             public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
             public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
                 // 登录的接口
                 // 登录的接口
                 registry.antMatchers(buildAdminApi("/system/auth/login")).permitAll();
                 registry.antMatchers(buildAdminApi("/system/auth/login")).permitAll();
+                registry.antMatchers(buildAdminApi("/system/auth/logout")).permitAll();
+                registry.antMatchers(buildAdminApi("/system/auth/refresh-token")).permitAll();
                 // 社交登陆的接口
                 // 社交登陆的接口
                 registry.antMatchers(buildAdminApi("/system/auth/social-auth-redirect")).permitAll();
                 registry.antMatchers(buildAdminApi("/system/auth/social-auth-redirect")).permitAll();
                 registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll();
                 registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll();

+ 0 - 32
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java

@@ -1,32 +0,0 @@
-package cn.iocoder.yudao.module.system.job.auth;
-
-import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
-import cn.iocoder.yudao.module.system.service.auth.UserSessionService;
-import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 用户 Session 超时 Job
- *
- * @author 願
- */
-@Component
-@TenantJob
-@Slf4j
-public class UserSessionTimeoutJob implements JobHandler {
-
-    @Resource
-    private UserSessionService userSessionService;
-
-    @Override
-    public String execute(String param) throws Exception {
-        // 执行过期
-        Long timeoutCount = userSessionService.deleteTimeoutSession();
-        // 返回结果,记录每次的超时数量
-        return String.format("移除在线会话数量为 %s 个", timeoutCount);
-    }
-
-}

+ 26 - 19
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java

@@ -1,28 +1,33 @@
 package cn.iocoder.yudao.module.system.service.auth;
 package cn.iocoder.yudao.module.system.service.auth;
 
 
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
-import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
 
 
 import javax.validation.Valid;
 import javax.validation.Valid;
 
 
 /**
 /**
  * 管理后台的认证 Service 接口
  * 管理后台的认证 Service 接口
  *
  *
- * 提供用户的账号密码登录、token 的校验等认证相关的功能
+ * 提供用户的登录、登出的能力
  *
  *
  * @author 芋道源码
  * @author 芋道源码
  */
  */
-public interface AdminAuthService extends SecurityAuthFrameworkService {
+public interface AdminAuthService {
 
 
     /**
     /**
      * 账号登录
      * 账号登录
      *
      *
      * @param reqVO 登录信息
      * @param reqVO 登录信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
      */
      */
-    String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent);
+    AuthLoginRespVO login(@Valid AuthLoginReqVO reqVO);
+
+    /**
+     * 基于 token 退出登录
+     *
+     * @param token token
+     * @param logType 登出类型
+     */
+    void logout(String token, Integer logType);
 
 
     /**
     /**
      * 短信验证码发送
      * 短信验证码发送
@@ -35,30 +40,32 @@ public interface AdminAuthService extends SecurityAuthFrameworkService {
      * 短信登录
      * 短信登录
      *
      *
      * @param reqVO 登录信息
      * @param reqVO 登录信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
      */
      */
-    String smsLogin(AuthSmsLoginReqVO reqVO, String userIp, String userAgent) ;
+    AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) ;
 
 
     /**
     /**
      * 社交快捷登录,使用 code 授权码
      * 社交快捷登录,使用 code 授权码
      *
      *
      * @param reqVO 登录信息
      * @param reqVO 登录信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
      */
      */
-    String socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
+    AuthLoginRespVO socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO);
 
 
     /**
     /**
      * 社交绑定登录,使用 code 授权码 + 账号密码
      * 社交绑定登录,使用 code 授权码 + 账号密码
      *
      *
      * @param reqVO 登录信息
      * @param reqVO 登录信息
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return 身份令牌,使用 JWT 方式
+     * @return 登录结果
+     */
+    AuthLoginRespVO socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO);
+
+    /**
+     * 刷新访问令牌
+     *
+     * @param refreshToken 刷新令牌
+     * @return 登录结果
      */
      */
-    String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
+    AuthLoginRespVO refreshToken(String refreshToken);
 
 
 }
 }

+ 81 - 161
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java

@@ -1,47 +1,37 @@
 package cn.iocoder.yudao.module.system.service.auth;
 package cn.iocoder.yudao.module.system.service.auth;
 
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientIdEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
 import cn.iocoder.yudao.module.system.service.common.CaptchaService;
 import cn.iocoder.yudao.module.system.service.common.CaptchaService;
 import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
 import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
-import cn.iocoder.yudao.module.system.service.permission.PermissionService;
+import cn.iocoder.yudao.module.system.service.member.MemberService;
 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.DisabledException;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
-import org.springframework.util.Assert;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
 import javax.validation.Validator;
 import javax.validation.Validator;
 import java.util.Objects;
 import java.util.Objects;
-import java.util.Set;
 
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
-import static java.util.Collections.singleton;
 
 
 /**
 /**
  * Auth Service 实现类
  * Auth Service 实现类
@@ -53,22 +43,17 @@ import static java.util.Collections.singleton;
 public class AdminAuthServiceImpl implements AdminAuthService {
 public class AdminAuthServiceImpl implements AdminAuthService {
 
 
     @Resource
     @Resource
-    @Lazy // 延迟加载,因为存在相互依赖的问题
-    private AuthenticationManager authenticationManager;
-
-    @Autowired
-    @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") // UserService 存在重名
     private AdminUserService userService;
     private AdminUserService userService;
     @Resource
     @Resource
-    private PermissionService permissionService;
-    @Resource
     private CaptchaService captchaService;
     private CaptchaService captchaService;
     @Resource
     @Resource
     private LoginLogService loginLogService;
     private LoginLogService loginLogService;
     @Resource
     @Resource
-    private UserSessionService userSessionService;
+    private OAuth2TokenService oauth2TokenService;
     @Resource
     @Resource
     private SocialUserService socialUserService;
     private SocialUserService socialUserService;
+    @Resource
+    private MemberService memberService;
 
 
     @Resource
     @Resource
     private Validator validator;
     private Validator validator;
@@ -77,39 +62,15 @@ public class AdminAuthServiceImpl implements AdminAuthService {
     private SmsCodeApi smsCodeApi;
     private SmsCodeApi smsCodeApi;
 
 
     @Override
     @Override
-    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
-        // 获取 username 对应的 AdminUserDO
-        AdminUserDO user = userService.getUserByUsername(username);
-        if (user == null) {
-            throw new UsernameNotFoundException(username);
-        }
-        // 创建 LoginUser 对象
-        return buildLoginUser(user);
-    }
-
-    @Override
-    public LoginUser mockLogin(Long userId) {
-        // 获取用户编号对应的 AdminUserDO
-        AdminUserDO user = userService.getUser(userId);
-        if (user == null) {
-            throw new UsernameNotFoundException(String.valueOf(userId));
-        }
-        createLoginLog(user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS);
-
-        // 创建 LoginUser 对象
-        return buildLoginUser(user);
-    }
-
-    @Override
-    public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) {
+    public AuthLoginRespVO login(AuthLoginReqVO reqVO) {
         // 判断验证码是否正确
         // 判断验证码是否正确
         verifyCaptcha(reqVO);
         verifyCaptcha(reqVO);
 
 
         // 使用账号密码,进行登录
         // 使用账号密码,进行登录
-        LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword());
+        AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword());
 
 
-        // 缓存登陆用户到 Redis 中,返回 Token 令牌
-        return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
     }
     }
 
 
     @Override
     @Override
@@ -123,9 +84,9 @@ public class AdminAuthServiceImpl implements AdminAuthService {
     }
     }
 
 
     @Override
     @Override
-    public String smsLogin(AuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
+    public AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) {
         // 校验验证码
         // 校验验证码
-        smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), userIp));
+        smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), getClientIP()));
 
 
         // 获得用户信息
         // 获得用户信息
         AdminUserDO user = userService.getUserByMobile(reqVO.getMobile());
         AdminUserDO user = userService.getUserByMobile(reqVO.getMobile());
@@ -133,14 +94,12 @@ public class AdminAuthServiceImpl implements AdminAuthService {
             throw exception(USER_NOT_EXISTS);
             throw exception(USER_NOT_EXISTS);
         }
         }
 
 
-        // 创建 LoginUser 对象
-        LoginUser loginUser = buildLoginUser(user);
-
         // 缓存登陆用户到 Redis 中,返回 sessionId 编号
         // 缓存登陆用户到 Redis 中,返回 sessionId 编号
-        return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_MOBILE, userIp, userAgent);
+        return createTokenAfterLoginSuccess(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
     }
     }
 
 
-    private void verifyCaptcha(AuthLoginReqVO reqVO) {
+    @VisibleForTesting
+    void verifyCaptcha(AuthLoginReqVO reqVO) {
         // 如果验证码关闭,则不进行校验
         // 如果验证码关闭,则不进行校验
         if (!captchaService.isCaptchaEnable()) {
         if (!captchaService.isCaptchaEnable()) {
             return;
             return;
@@ -152,53 +111,47 @@ public class AdminAuthServiceImpl implements AdminAuthService {
         String code = captchaService.getCaptchaCode(reqVO.getUuid());
         String code = captchaService.getCaptchaCode(reqVO.getUuid());
         if (code == null) {
         if (code == null) {
             // 创建登录失败日志(验证码不存在)
             // 创建登录失败日志(验证码不存在)
-            this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND);
+            createLoginLog(null, reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND);
             throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND);
             throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND);
         }
         }
         // 验证码不正确
         // 验证码不正确
         if (!code.equals(reqVO.getCode())) {
         if (!code.equals(reqVO.getCode())) {
             // 创建登录失败日志(验证码不正确)
             // 创建登录失败日志(验证码不正确)
-            this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR);
+            createLoginLog(null, reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR);
             throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR);
             throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR);
         }
         }
         // 正确,所以要删除下验证码
         // 正确,所以要删除下验证码
         captchaService.deleteCaptchaCode(reqVO.getUuid());
         captchaService.deleteCaptchaCode(reqVO.getUuid());
     }
     }
 
 
-    private LoginUser login0(String username, String password) {
+    @VisibleForTesting
+    AdminUserDO login0(String username, String password) {
         final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
         final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
-        // 用户验证
-        Authentication authentication;
-        try {
-            // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
-            // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
-            authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
-                    username, password, getUserType()));
-        } catch (BadCredentialsException badCredentialsException) {
-            this.createLoginLog(username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
+        // 校验账号是否存在
+        AdminUserDO user = userService.getUserByUsername(username);
+        if (user == null) {
+            createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
             throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
             throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
-        } catch (DisabledException disabledException) {
-            this.createLoginLog(username, logTypeEnum, LoginResultEnum.USER_DISABLED);
+        }
+        if (!userService.isPasswordMatch(password, user.getPassword())) {
+            createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
+            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
+        }
+        // 校验是否禁用
+        if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
+            createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED);
             throw exception(AUTH_LOGIN_USER_DISABLED);
             throw exception(AUTH_LOGIN_USER_DISABLED);
-        } catch (AuthenticationException authenticationException) {
-            log.error("[login0][username({}) 发生未知异常]", username, authenticationException);
-            this.createLoginLog(username, logTypeEnum, LoginResultEnum.UNKNOWN_ERROR);
-            throw exception(AUTH_LOGIN_FAIL_UNKNOWN);
         }
         }
-        Assert.notNull(authentication.getPrincipal(), "Principal 不会为空");
-        return (LoginUser) authentication.getPrincipal();
+        return user;
     }
     }
 
 
-    private void createLoginLog(String username, LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {
-        // 获得用户
-        AdminUserDO user = userService.getUserByUsername(username);
+    private void createLoginLog(Long userId, String username,
+                                LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {
         // 插入登录日志
         // 插入登录日志
         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
         reqDTO.setLogType(logTypeEnum.getType());
         reqDTO.setLogType(logTypeEnum.getType());
         reqDTO.setTraceId(TracerUtils.getTraceId());
         reqDTO.setTraceId(TracerUtils.getTraceId());
-        if (user != null) {
-            reqDTO.setUserId(user.getId());
-        }
+        reqDTO.setUserId(userId);
         reqDTO.setUserType(getUserType().getValue());
         reqDTO.setUserType(getUserType().getValue());
         reqDTO.setUsername(username);
         reqDTO.setUsername(username);
         reqDTO.setUserAgent(ServletUtils.getUserAgent());
         reqDTO.setUserAgent(ServletUtils.getUserAgent());
@@ -206,23 +159,13 @@ public class AdminAuthServiceImpl implements AdminAuthService {
         reqDTO.setResult(loginResult.getResult());
         reqDTO.setResult(loginResult.getResult());
         loginLogService.createLoginLog(reqDTO);
         loginLogService.createLoginLog(reqDTO);
         // 更新最后登录时间
         // 更新最后登录时间
-        if (user != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
-            userService.updateUserLogin(user.getId(), ServletUtils.getClientIP());
+        if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
+            userService.updateUserLogin(userId, ServletUtils.getClientIP());
         }
         }
     }
     }
 
 
-    /**
-     * 获得 User 拥有的角色编号数组
-     *
-     * @param userId 用户编号
-     * @return 角色编号数组
-     */
-    private Set<Long> getUserRoleIds(Long userId) {
-        return permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
-    }
-
     @Override
     @Override
-    public String socialQuickLogin(AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) {
+    public AuthLoginRespVO socialQuickLogin(AuthSocialQuickLoginReqVO reqVO) {
         // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
         // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
         Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
         Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
                 reqVO.getCode(), reqVO.getState());
                 reqVO.getCode(), reqVO.getState());
@@ -230,105 +173,82 @@ public class AdminAuthServiceImpl implements AdminAuthService {
             throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
             throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
         }
         }
 
 
-        // 自动登录
+        // 获得用户
         AdminUserDO user = userService.getUser(userId);
         AdminUserDO user = userService.getUser(userId);
         if (user == null) {
         if (user == null) {
             throw exception(USER_NOT_EXISTS);
             throw exception(USER_NOT_EXISTS);
         }
         }
 
 
-        // 创建 LoginUser 对象
-        LoginUser loginUser = buildLoginUser(user);
-
-        // 缓存登录用户到 Redis 中,返回 Token 令牌
-        return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user.getId(), null, LoginLogTypeEnum.LOGIN_SOCIAL);
     }
     }
 
 
     @Override
     @Override
-    public String socialBindLogin(AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) {
+    public AuthLoginRespVO socialBindLogin(AuthSocialBindLoginReqVO reqVO) {
         // 使用账号密码,进行登录。
         // 使用账号密码,进行登录。
-        LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword());
+        AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword());
 
 
         // 绑定社交用户
         // 绑定社交用户
-        socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
+        socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(user.getId(), getUserType().getValue(), reqVO));
 
 
-        // 缓存登录用户到 Redis 中,返回 Token 令牌
-        return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL);
     }
     }
 
 
-    private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) {
+    @Override
+    public AuthLoginRespVO refreshToken(String refreshToken) {
+        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientIdEnum.DEFAULT.getId());
+        return AuthConvert.INSTANCE.convert(accessTokenDO);
+    }
+
+    private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) {
         // 插入登陆日志
         // 插入登陆日志
-        createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS);
-        // 缓存登录用户到 Redis 中,返回 Token 令牌
-        return userSessionService.createUserSession(loginUser, userIp, userAgent);
+        createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
+        // 创建访问令牌
+        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
+                OAuth2ClientIdEnum.DEFAULT.getId());
+        // 构建返回结果
+        return AuthConvert.INSTANCE.convert(accessTokenDO);
     }
     }
 
 
     @Override
     @Override
-    public void logout(String token) {
-        // 查询用户信息
-        LoginUser loginUser = userSessionService.getLoginUser(token);
-        if (loginUser == null) {
+    public void logout(String token, Integer logType) {
+        // 删除访问令牌
+        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token);
+        if (accessTokenDO == null) {
             return;
             return;
         }
         }
-        // 删除 session
-        userSessionService.deleteUserSession(token);
-        // 记录登出日志
-        createLogoutLog(loginUser.getId(), loginUser.getUsername());
-    }
-
-    @Override
-    public UserTypeEnum getUserType() {
-        return UserTypeEnum.ADMIN;
+        // 删除成功,则记录登出日志
+        createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType);
     }
     }
 
 
-    private void createLogoutLog(Long userId, String username) {
+    private void createLogoutLog(Long userId, Integer userType, Integer logType) {
         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
-        reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
+        reqDTO.setLogType(logType);
         reqDTO.setTraceId(TracerUtils.getTraceId());
         reqDTO.setTraceId(TracerUtils.getTraceId());
         reqDTO.setUserId(userId);
         reqDTO.setUserId(userId);
-        reqDTO.setUserType(getUserType().getValue());
-        reqDTO.setUsername(username);
+        reqDTO.setUserType(userType);
+        if (ObjectUtil.notEqual(getUserType(), userType)) {
+            reqDTO.setUsername(getUsername(userId));
+        } else {
+            reqDTO.setUsername(memberService.getMemberUserMobile(userId));
+        }
         reqDTO.setUserAgent(ServletUtils.getUserAgent());
         reqDTO.setUserAgent(ServletUtils.getUserAgent());
         reqDTO.setUserIp(ServletUtils.getClientIP());
         reqDTO.setUserIp(ServletUtils.getClientIP());
         reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
         reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
         loginLogService.createLoginLog(reqDTO);
         loginLogService.createLoginLog(reqDTO);
     }
     }
 
 
-    @Override
-    public LoginUser verifyTokenAndRefresh(String token) {
-        // 获得 LoginUser
-        LoginUser loginUser = userSessionService.getLoginUser(token);
-        if (loginUser == null) {
+    private String getUsername(Long userId) {
+        if (userId == null) {
             return null;
             return null;
         }
         }
-        // 刷新 LoginUser 缓存
-        return this.refreshLoginUserCache(token, loginUser);
-    }
-
-    private LoginUser refreshLoginUserCache(String token, LoginUser loginUser) {
-        // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
-        if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
-                userSessionService.getSessionTimeoutMillis() / 3) {
-            return loginUser;
-        }
-
-        // 重新加载 AdminUserDO 信息
-        AdminUserDO user = userService.getUser(loginUser.getId());
-        if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
-            throw exception(AUTH_TOKEN_EXPIRED); // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面
-        }
-
-        // 刷新 LoginUser 缓存
-        LoginUser newLoginUser= this.buildLoginUser(user);
-        userSessionService.refreshUserSession(token, newLoginUser);
-        return newLoginUser;
+        AdminUserDO user = userService.getUser(userId);
+        return user != null ? user.getUsername() : null;
     }
     }
 
 
-    private LoginUser buildLoginUser(AdminUserDO user) {
-        LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
-        // 补全字段
-        loginUser.setDeptId(user.getDeptId());
-        loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId()));
-        return loginUser;
+    private UserTypeEnum getUserType() {
+        return UserTypeEnum.ADMIN;
     }
     }
 
 
 }
 }

+ 14 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminOAuth2Service.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.system.service.auth;
+
+/**
+ * 管理后台的 OAuth2 Service 接口
+ *
+ * 将自身的 AdminUser 用户,授权给第三方应用,采用 OAuth2.0 的协议。
+ *
+ * 问题:为什么自身也作为一个第三方应用,也走这套流程呢?
+ * 回复:当然可以这么做,采用 Implicit 模式。考虑到大多数开发者使用不到这个特性,OAuth2.0 毕竟有一定学习成本,所以暂时没有采取这种方式。
+ *
+ * @author 芋道源码
+ */
+public interface AdminOAuth2Service {
+}

+ 66 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java

@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.system.service.auth;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
+
+import javax.validation.Valid;
+
+/**
+ * OAuth2.0 Client Service 接口
+ *
+ * 从功能上,和 JdbcClientDetailsService 的功能,提供客户端的操作
+ *
+ * @author 芋道源码
+ */
+public interface OAuth2ClientService {
+
+    /**
+     * 创建OAuth2 客户端
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createOAuth2Client(@Valid OAuth2ClientCreateReqVO createReqVO);
+
+    /**
+     * 更新OAuth2 客户端
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateOAuth2Client(@Valid OAuth2ClientUpdateReqVO updateReqVO);
+
+    /**
+     * 删除OAuth2 客户端
+     *
+     * @param id 编号
+     */
+    void deleteOAuth2Client(Long id);
+
+    /**
+     * 获得OAuth2 客户端
+     *
+     * @param id 编号
+     * @return OAuth2 客户端
+     */
+    OAuth2ClientDO getOAuth2Client(Long id);
+
+    /**
+     * 获得OAuth2 客户端分页
+     *
+     * @param pageReqVO 分页查询
+     * @return OAuth2 客户端分页
+     */
+    PageResult<OAuth2ClientDO> getOAuth2ClientPage(OAuth2ClientPageReqVO pageReqVO);
+
+    /**
+     * 从缓存中,校验客户端是否合法
+     *
+     * @param id 客户端编号
+     * @return 客户端
+     */
+    OAuth2ClientDO validOAuthClientFromCache(Long id);
+
+}

+ 77 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java

@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.system.service.auth;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
+import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
+import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS;
+
+/**
+ * OAuth2.0 Client Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+public class OAuth2ClientServiceImpl implements OAuth2ClientService {
+
+    @Resource
+    private OAuth2ClientMapper oauth2ClientMapper;
+
+    @Override
+    public Long createOAuth2Client(OAuth2ClientCreateReqVO createReqVO) {
+        // 插入
+        OAuth2ClientDO oAuth2Client = OAuth2ClientConvert.INSTANCE.convert(createReqVO);
+        oauth2ClientMapper.insert(oAuth2Client);
+        // 返回
+        return oAuth2Client.getId();
+    }
+
+    @Override
+    public void updateOAuth2Client(OAuth2ClientUpdateReqVO updateReqVO) {
+        // 校验存在
+        this.validateOAuth2ClientExists(updateReqVO.getId());
+        // 更新
+        OAuth2ClientDO updateObj = OAuth2ClientConvert.INSTANCE.convert(updateReqVO);
+        oauth2ClientMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteOAuth2Client(Long id) {
+        // 校验存在
+        this.validateOAuth2ClientExists(id);
+        // 删除
+        oauth2ClientMapper.deleteById(id);
+    }
+
+    private void validateOAuth2ClientExists(Long id) {
+        if (oauth2ClientMapper.selectById(id) == null) {
+            throw exception(OAUTH2_CLIENT_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public OAuth2ClientDO getOAuth2Client(Long id) {
+        return oauth2ClientMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<OAuth2ClientDO> getOAuth2ClientPage(OAuth2ClientPageReqVO pageReqVO) {
+        return oauth2ClientMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public OAuth2ClientDO validOAuthClientFromCache(Long id) {
+        return new OAuth2ClientDO().setId(id)
+                .setAccessTokenValiditySeconds(60 * 30)
+                .setRefreshTokenValiditySeconds(60 * 60 * 24 * 30);
+    }
+
+}

+ 11 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2CodeService.java

@@ -0,0 +1,11 @@
+package cn.iocoder.yudao.module.system.service.auth;
+
+/**
+ * OAuth2.0 授权码 Service 接口
+ *
+ * 从功能上,和 Spring Security OAuth 的 JdbcAuthorizationCodeServices 的功能,提供授权码的操作
+ *
+ * @author 芋道源码
+ */
+public class OAuth2CodeService {
+}

+ 77 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java

@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.system.service.auth;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
+
+/**
+ * OAuth2.0 Token Service 接口
+ *
+ * 从功能上,和 Spring Security OAuth 的 DefaultTokenServices + JdbcTokenStore 的功能,提供访问令牌、刷新令牌的操作
+ *
+ * @author 芋道源码
+ */
+public interface OAuth2TokenService {
+
+    /**
+     * 创建访问令牌
+     * 注意:该流程中,会包含创建刷新令牌的创建
+     *
+     * 参考 DefaultTokenServices 的 createAccessToken 方法
+     *
+     * @param userId 用户编号
+     * @param userType 用户类型
+     * @param clientId 客户端编号
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId);
+
+    /**
+     * 刷新访问令牌
+     *
+     * 参考 DefaultTokenServices 的 refreshAccessToken 方法
+     *
+     * @param refreshToken 刷新令牌
+     * @param clientId 客户端编号
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId);
+
+    /**
+     * 获得访问令牌
+     *
+     * 参考 DefaultTokenServices 的 getAccessToken 方法
+     *
+     * @param accessToken 访问令牌
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenDO getAccessToken(String accessToken);
+
+    /**
+     * 校验访问令牌
+     *
+     * @param accessToken 访问令牌
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenDO checkAccessToken(String accessToken);
+
+    /**
+     * 移除访问令牌
+     * 注意:该流程中,会移除相关的刷新令牌
+     *
+     * 参考 DefaultTokenServices 的 revokeToken 方法
+     *
+     * @param accessToken 刷新令牌
+     * @return 访问令牌的信息
+     */
+    OAuth2AccessTokenDO removeAccessToken(String accessToken);
+
+    /**
+     * 获得访问令牌分页
+     *
+     * @param reqVO 请求
+     * @return 访问令牌分页
+     */
+    PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO);
+
+}

+ 163 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java

@@ -0,0 +1,163 @@
+package cn.iocoder.yudao.module.system.service.auth;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO;
+import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2AccessTokenMapper;
+import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2RefreshTokenMapper;
+import cn.iocoder.yudao.module.system.dal.redis.auth.OAuth2AccessTokenRedisDAO;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.Calendar;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+/**
+ * OAuth2.0 Token Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+public class OAuth2TokenServiceImpl implements OAuth2TokenService {
+
+    @Resource
+    private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
+    @Resource
+    private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;
+
+    @Resource
+    private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO;
+
+    @Resource
+    private OAuth2ClientService oauth2ClientService;
+
+    @Override
+    @Transactional
+    public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId) {
+        OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
+        // 创建刷新令牌
+        OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO);
+        // 创建访问令牌
+        return createOAuth2AccessToken(refreshTokenDO, clientDO);
+    }
+
+    @Override
+    public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId) {
+        // 查询访问令牌
+        OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken);
+        if (refreshTokenDO == null) {
+            throw exception(GlobalErrorCodeConstants.BAD_REQUEST, "无效的刷新令牌");
+        }
+
+        // 校验 Client 匹配
+        OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
+        if (ObjectUtil.notEqual(clientId, refreshTokenDO.getClientId())) {
+            throw exception(GlobalErrorCodeConstants.BAD_REQUEST, "刷新令牌的客户端编号不正确");
+        }
+
+        // 移除相关的访问令牌
+        List<OAuth2AccessTokenDO> accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshToken);
+        if (CollUtil.isNotEmpty(accessTokenDOs)) {
+            oauth2AccessTokenMapper.deleteBatchIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId));
+            oauth2AccessTokenRedisDAO.deleteList(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getAccessToken));
+        }
+
+        // 已过期的情况下,删除刷新令牌
+        if (DateUtils.isExpired(refreshTokenDO.getExpiresTime())) {
+            oauth2AccessTokenMapper.deleteById(refreshTokenDO.getId());
+            throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "刷新令牌已过期");
+        }
+
+        // 创建访问令牌
+        return createOAuth2AccessToken(refreshTokenDO, clientDO);
+    }
+
+    @Override
+    public OAuth2AccessTokenDO getAccessToken(String accessToken) {
+        // 优先从 Redis 中获取
+        OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken);
+        if (accessTokenDO != null) {
+            return accessTokenDO;
+        }
+
+        // 获取不到,从 MySQL 中获取
+        accessTokenDO = oauth2AccessTokenMapper.selectById(accessToken);
+        // 如果在 MySQL 存在,则往 Redis 中写入
+        if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
+            oauth2AccessTokenRedisDAO.set(accessTokenDO);
+        }
+        return accessTokenDO;
+    }
+
+    @Override
+    public OAuth2AccessTokenDO checkAccessToken(String accessToken) {
+        OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken);
+        if (accessTokenDO == null) {
+            throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌不存在");
+        }
+        if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
+            throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌已过期");
+        }
+        return accessTokenDO;
+    }
+
+    @Override
+    public OAuth2AccessTokenDO removeAccessToken(String accessToken) {
+        // 删除访问令牌
+        OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken);
+        if (accessTokenDO == null) {
+            return null;
+        }
+        oauth2AccessTokenMapper.deleteById(accessTokenDO.getId());
+        oauth2AccessTokenRedisDAO.delete(accessToken);
+        // 删除刷新令牌
+        oauth2RefreshTokenMapper.deleteByRefreshToken(accessTokenDO.getRefreshToken());
+        return accessTokenDO;
+    }
+
+    @Override
+    public PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO) {
+        return oauth2AccessTokenMapper.selectPage(reqVO);
+    }
+
+    private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
+        OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())
+                .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getId())
+                .setRefreshToken(refreshTokenDO.getRefreshToken())
+                .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds()));
+        accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
+        oauth2AccessTokenMapper.insert(accessTokenDO);
+        // 记录到 Redis 中
+        oauth2AccessTokenRedisDAO.set(accessTokenDO);
+        return accessTokenDO;
+    }
+
+    private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) {
+        OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken())
+                .setUserId(userId).setUserType(userType).setClientId(clientDO.getId())
+                .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getRefreshTokenValiditySeconds()));
+        oauth2RefreshTokenMapper.insert(refreshToken);
+        return refreshToken;
+    }
+
+    private static String generateAccessToken() {
+        return IdUtil.fastSimpleUUID();
+    }
+
+    private static String generateRefreshToken() {
+        return IdUtil.fastSimpleUUID();
+    }
+
+}

+ 0 - 77
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java

@@ -1,77 +0,0 @@
-package cn.iocoder.yudao.module.system.service.auth;
-
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
-import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-
-/**
- * 在线用户 Session Service 接口
- *
- * @author 芋道源码
- */
-public interface UserSessionService {
-
-    /**
-     * 获得在线用户分页列表
-     *
-     * @param reqVO 分页条件
-     * @return 份额与列表
-     */
-    PageResult<UserSessionDO> getUserSessionPage(UserSessionPageReqVO reqVO);
-
-    /**
-     * 移除超时的在线用户
-     *
-     * @return {@link Long } 移出的超时用户数量
-     **/
-    long deleteTimeoutSession();
-
-    /**
-     * 创建在线用户 Session
-     *
-     * @param loginUser 登录用户
-     * @param userIp 用户 IP
-     * @param userAgent 用户 UA
-     * @return Token 令牌
-     */
-    String createUserSession(LoginUser loginUser, String userIp, String userAgent);
-
-    /**
-     * 刷新在线用户 Session 的更新时间
-     *
-     * @param token 令牌
-     * @param loginUser 登录用户
-     */
-    void refreshUserSession(String token, LoginUser loginUser);
-
-    /**
-     * 删除在线用户 Session
-     *
-     * @param token token 令牌
-     */
-    void deleteUserSession(String token);
-
-    /**
-     * 删除在线用户 Session
-     *
-     * @param id 编号
-     */
-    void deleteUserSession(Long id);
-
-    /**
-     * 获得 Token 对应的在线用户
-     *
-     * @param token 令牌
-     * @return 在线用户
-     */
-    LoginUser getLoginUser(String token);
-
-    /**
-     * 获得 Session 超时时间,单位:毫秒
-     *
-     * @return 超时时间
-     */
-    Long getSessionTimeoutMillis();
-
-}

+ 0 - 177
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java

@@ -1,177 +0,0 @@
-package cn.iocoder.yudao.module.system.service.auth;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.IdUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
-import cn.iocoder.yudao.framework.security.config.SecurityProperties;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
-import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
-import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
-import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper;
-import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO;
-import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
-import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
-import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
-import cn.iocoder.yudao.module.system.service.user.AdminUserService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.time.Duration;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
-
-/**
- * 在线用户 Session Service 实现类
- *
- * @author 芋道源码
- */
-@Slf4j
-@Service
-public class UserSessionServiceImpl implements UserSessionService {
-
-    @Resource
-    private UserSessionMapper userSessionMapper;
-
-    @Resource
-    private AdminUserService userService;
-    @Resource
-    private LoginLogService loginLogService;
-
-    @Resource
-    private LoginUserRedisDAO loginUserRedisDAO;
-
-    @Resource
-    private SecurityProperties securityProperties;
-
-    @Override
-    public PageResult<UserSessionDO> getUserSessionPage(UserSessionPageReqVO reqVO) {
-        // 处理基于用户昵称的查询
-        Collection<Long> userIds = null;
-        if (StrUtil.isNotEmpty(reqVO.getUsername())) {
-            userIds = convertSet(userService.getUsersByUsername(reqVO.getUsername()), AdminUserDO::getId);
-            if (CollUtil.isEmpty(userIds)) {
-                return PageResult.empty();
-            }
-        }
-        return userSessionMapper.selectPage(reqVO, userIds);
-    }
-
-    @Override
-    public long deleteTimeoutSession() {
-        // 获取 db 里已经超时的用户列表
-        List<UserSessionDO> timeoutSessions = userSessionMapper.selectListBySessionTimoutLt();
-        if (CollUtil.isEmpty(timeoutSessions)) {
-            return 0L;
-        }
-
-        // 由于过期的用户一般不多,所以顺序遍历,进行清理
-        int count = 0;
-        for (UserSessionDO session : timeoutSessions) {
-            // 基于 Redis 二次判断,同时也保证 Redis Key 的立即过期,避免延迟导致浪费内存空间
-            if (loginUserRedisDAO.exists(session.getToken())) {
-                continue;
-            }
-            userSessionMapper.deleteById(session.getId());
-            // 记录退出日志
-            createLogoutLog(session, LoginLogTypeEnum.LOGOUT_TIMEOUT);
-            count++;
-        }
-        return count;
-    }
-
-    private void createLogoutLog(UserSessionDO session, LoginLogTypeEnum type) {
-        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
-        reqDTO.setLogType(type.getType());
-        reqDTO.setTraceId(TracerUtils.getTraceId());
-        reqDTO.setUserId(session.getUserId());
-        reqDTO.setUserType(session.getUserType());
-        reqDTO.setUsername(session.getUsername());
-        reqDTO.setUserAgent(session.getUserAgent());
-        reqDTO.setUserIp(session.getUserIp());
-        reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
-        loginLogService.createLoginLog(reqDTO);
-    }
-
-    @Override
-    public String createUserSession(LoginUser loginUser, String userIp, String userAgent) {
-        // 生成 Session 编号
-        String token = generateToken();
-        // 写入 Redis 缓存
-        loginUser.setUpdateTime(new Date());
-        loginUserRedisDAO.set(token, loginUser);
-        // 写入 DB 中
-        UserSessionDO userSession = UserSessionDO.builder().token(token)
-                .userId(loginUser.getId()).userType(loginUser.getUserType())
-                .userIp(userIp).userAgent(userAgent).username(loginUser.getUsername())
-                .sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())))
-                .build();
-        userSessionMapper.insert(userSession);
-        // 返回 Token 令牌
-        return token;
-    }
-
-    @Override
-    public void refreshUserSession(String token, LoginUser loginUser) {
-        // 写入 Redis 缓存
-        loginUser.setUpdateTime(new Date());
-        loginUserRedisDAO.set(token, loginUser);
-        // 更新 DB 中
-        UserSessionDO updateObj = UserSessionDO.builder().build();
-        updateObj.setUsername(loginUser.getUsername());
-        updateObj.setUpdateTime(new Date());
-        updateObj.setSessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())));
-        userSessionMapper.updateByToken(token, updateObj);
-    }
-
-    @Override
-    public void deleteUserSession(String token) {
-        // 删除 Redis 缓存
-        loginUserRedisDAO.delete(token);
-        // 删除 DB 记录
-        userSessionMapper.deleteByToken(token);
-        // 无需记录日志,因为退出那已经记录
-    }
-
-    @Override
-    public void deleteUserSession(Long id) {
-        UserSessionDO session = userSessionMapper.selectById(id);
-        if (session == null) {
-            return;
-        }
-        // 删除 Redis 缓存
-        loginUserRedisDAO.delete(session.getToken());
-        // 删除 DB 记录
-        userSessionMapper.deleteById(id);
-        // 记录退出日志
-        createLogoutLog(session, LoginLogTypeEnum.LOGOUT_DELETE);
-    }
-
-    @Override
-    public LoginUser getLoginUser(String token) {
-        return loginUserRedisDAO.get(token);
-    }
-
-    @Override
-    public Long getSessionTimeoutMillis() {
-        return securityProperties.getSessionTimeout().toMillis();
-    }
-
-    /**
-     * 生成 Token 令牌,目前采用 UUID 算法
-     *
-     * @return Session 编号
-     */
-    private static String generateToken() {
-        return IdUtil.fastSimpleUUID();
-    }
-
-}

+ 28 - 26
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java

@@ -3,12 +3,12 @@ package cn.iocoder.yudao.module.system.service.permission;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
@@ -22,6 +22,8 @@ import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
 import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
 import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
 import cn.iocoder.yudao.module.system.service.dept.DeptService;
 import cn.iocoder.yudao.module.system.service.dept.DeptService;
+import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Sets;
@@ -36,6 +38,10 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
 import javax.annotation.PostConstruct;
 import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import javax.annotation.Resource;
 import java.util.*;
 import java.util.*;
+import java.util.function.Supplier;
+
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static java.util.Collections.singleton;
 
 
 /**
 /**
  * 权限 Service 实现类
  * 权限 Service 实现类
@@ -47,11 +53,6 @@ import java.util.*;
 public class PermissionServiceImpl implements PermissionService {
 public class PermissionServiceImpl implements PermissionService {
 
 
     /**
     /**
-     * LoginUser 的 Context 缓存 Key
-     */
-    public static final String CONTEXT_KEY = PermissionServiceImpl.class.getSimpleName();
-
-    /**
      * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
      * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
      * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
      * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
      */
      */
@@ -93,6 +94,8 @@ public class PermissionServiceImpl implements PermissionService {
     private MenuService menuService;
     private MenuService menuService;
     @Resource
     @Resource
     private DeptService deptService;
     private DeptService deptService;
+    @Resource
+    private AdminUserService userService;
 
 
     @Resource
     @Resource
     private PermissionProducer permissionProducer;
     private PermissionProducer permissionProducer;
@@ -319,7 +322,7 @@ public class PermissionServiceImpl implements PermissionService {
         }
         }
 
 
         // 获得当前登录的角色。如果为空,说明没有权限
         // 获得当前登录的角色。如果为空,说明没有权限
-        Set<Long> roleIds = SecurityFrameworkUtils.getLoginUserRoleIds();
+        Set<Long> roleIds = getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
         if (CollUtil.isEmpty(roleIds)) {
         if (CollUtil.isEmpty(roleIds)) {
             return false;
             return false;
         }
         }
@@ -354,7 +357,7 @@ public class PermissionServiceImpl implements PermissionService {
         }
         }
 
 
         // 获得当前登录的角色。如果为空,说明没有权限
         // 获得当前登录的角色。如果为空,说明没有权限
-        Set<Long> roleIds = SecurityFrameworkUtils.getLoginUserRoleIds();
+        Set<Long> roleIds = getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
         if (CollUtil.isEmpty(roleIds)) {
         if (CollUtil.isEmpty(roleIds)) {
             return false;
             return false;
         }
         }
@@ -368,16 +371,18 @@ public class PermissionServiceImpl implements PermissionService {
     }
     }
 
 
     @Override
     @Override
-    public DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser) {
-        // 判断是否 context 已经缓存
-        DeptDataPermissionRespDTO result = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
-        if (result != null) {
+    @DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题
+    public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
+        DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
+        // 获得用户的角色
+        Set<Long> roleIds = getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
+        if (CollUtil.isEmpty(roleIds)) {
             return result;
             return result;
         }
         }
-
-        // 创建 DeptDataPermissionRespDTO 对象
-        result = new DeptDataPermissionRespDTO();
-        List<RoleDO> roles = roleService.getRolesFromCache(loginUser.getRoleIds());
+        List<RoleDO> roles = roleService.getRolesFromCache(roleIds);
+        // 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询
+        Supplier<Long> userDeptIdCache = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
+        // 遍历每个角色,计算
         for (RoleDO role : roles) {
         for (RoleDO role : roles) {
             // 为空时,跳过
             // 为空时,跳过
             if (role.getDataScope() == null) {
             if (role.getDataScope() == null) {
@@ -393,20 +398,20 @@ public class PermissionServiceImpl implements PermissionService {
                 CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
                 CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
                 // 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
                 // 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
                 // 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
                 // 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
-                CollUtil.addAll(result.getDeptIds(), loginUser.getDeptId());
+                CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
                 continue;
                 continue;
             }
             }
             // 情况三,DEPT_ONLY
             // 情况三,DEPT_ONLY
             if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
             if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
-                CollectionUtils.addIfNotNull(result.getDeptIds(), loginUser.getDeptId());
+                CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptIdCache.get());
                 continue;
                 continue;
             }
             }
             // 情况四,DEPT_DEPT_AND_CHILD
             // 情况四,DEPT_DEPT_AND_CHILD
             if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
             if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
-                List<DeptDO> depts = deptService.getDeptsByParentIdFromCache(loginUser.getDeptId(), true);
+                List<DeptDO> depts = deptService.getDeptsByParentIdFromCache(userDeptIdCache.get(), true);
                 CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, DeptDO::getId));
                 CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, DeptDO::getId));
-                //添加本身部门id
-                CollUtil.addAll(result.getDeptIds(), loginUser.getDeptId());
+                // 添加本身部门编号
+                CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
                 continue;
                 continue;
             }
             }
             // 情况五,SELF
             // 情况五,SELF
@@ -415,11 +420,8 @@ public class PermissionServiceImpl implements PermissionService {
                 continue;
                 continue;
             }
             }
             // 未知情况,error log 即可
             // 未知情况,error log 即可
-            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", loginUser.getId(), JsonUtils.toJsonString(result));
+            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, JsonUtils.toJsonString(result));
         }
         }
-
-        // 添加到缓存,并返回
-        loginUser.setContext(CONTEXT_KEY, result);
         return result;
         return result;
     }
     }
 
 

+ 1 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java

@@ -81,6 +81,7 @@ public class TenantServiceImpl implements TenantService {
     @Getter
     @Getter
     private volatile Date maxUpdateTime;
     private volatile Date maxUpdateTime;
 
 
+    @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
     @Autowired(required = false) // 由于 yudao.tenant.enable 配置项,可以关闭多租户的功能,所以这里只能不强制注入
     @Autowired(required = false) // 由于 yudao.tenant.enable 配置项,可以关闭多租户的功能,所以这里只能不强制注入
     private TenantProperties tenantProperties;
     private TenantProperties tenantProperties;
 
 

+ 9 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java

@@ -105,7 +105,6 @@ public interface AdminUserService {
      */
      */
     AdminUserDO getUserByMobile(String mobile);
     AdminUserDO getUserByMobile(String mobile);
 
 
-
     /**
     /**
      * 获得用户分页列表
      * 获得用户分页列表
      *
      *
@@ -209,4 +208,13 @@ public interface AdminUserService {
      */
      */
     List<AdminUserDO> getUsersByStatus(Integer status);
     List<AdminUserDO> getUsersByStatus(Integer status);
 
 
+    /**
+     * 判断密码是否匹配
+     *
+     * @param rawPassword 未加密的密码
+     * @param encodedPassword 加密后的密码
+     * @return 是否匹配
+     */
+    boolean isPasswordMatch(String rawPassword, String encodedPassword);
+
 }
 }

+ 21 - 9
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java

@@ -25,6 +25,7 @@ import cn.iocoder.yudao.module.system.service.tenant.TenantService;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
@@ -61,6 +62,7 @@ public class AdminUserServiceImpl implements AdminUserService {
     @Resource
     @Resource
     private PasswordEncoder passwordEncoder;
     private PasswordEncoder passwordEncoder;
     @Resource
     @Resource
+    @Lazy // 延迟,避免循环依赖报错
     private TenantService tenantService;
     private TenantService tenantService;
 
 
     @Resource
     @Resource
@@ -146,7 +148,7 @@ public class AdminUserServiceImpl implements AdminUserService {
         checkOldPassword(id, reqVO.getOldPassword());
         checkOldPassword(id, reqVO.getOldPassword());
         // 执行更新
         // 执行更新
         AdminUserDO updateObj = new AdminUserDO().setId(id);
         AdminUserDO updateObj = new AdminUserDO().setId(id);
-        updateObj.setPassword(passwordEncoder.encode(reqVO.getNewPassword())); // 加密密码
+        updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码
         userMapper.updateById(updateObj);
         userMapper.updateById(updateObj);
     }
     }
 
 
@@ -170,7 +172,7 @@ public class AdminUserServiceImpl implements AdminUserService {
         // 更新密码
         // 更新密码
         AdminUserDO updateObj = new AdminUserDO();
         AdminUserDO updateObj = new AdminUserDO();
         updateObj.setId(id);
         updateObj.setId(id);
-        updateObj.setPassword(passwordEncoder.encode(password)); // 加密密码
+        updateObj.setPassword(encodePassword(password)); // 加密密码
         userMapper.updateById(updateObj);
         userMapper.updateById(updateObj);
     }
     }
 
 
@@ -203,11 +205,6 @@ public class AdminUserServiceImpl implements AdminUserService {
         return userMapper.selectByUsername(username);
         return userMapper.selectByUsername(username);
     }
     }
 
 
-    /**
-     * 通过手机号获取用户
-     * @param mobile
-     * @return
-     */
     @Override
     @Override
     public AdminUserDO getUserByMobile(String mobile) {
     public AdminUserDO getUserByMobile(String mobile) {
         return userMapper.selectByMobile(mobile);
         return userMapper.selectByMobile(mobile);
@@ -393,7 +390,7 @@ public class AdminUserServiceImpl implements AdminUserService {
         if (user == null) {
         if (user == null) {
             throw exception(USER_NOT_EXISTS);
             throw exception(USER_NOT_EXISTS);
         }
         }
-        if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
+        if (!isPasswordMatch(oldPassword, user.getPassword())) {
             throw exception(USER_PASSWORD_FAILED);
             throw exception(USER_PASSWORD_FAILED);
         }
         }
     }
     }
@@ -419,7 +416,7 @@ public class AdminUserServiceImpl implements AdminUserService {
             AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
             AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
             if (existUser == null) {
             if (existUser == null) {
                 userMapper.insert(UserConvert.INSTANCE.convert(importUser)
                 userMapper.insert(UserConvert.INSTANCE.convert(importUser)
-                        .setPassword(passwordEncoder.encode(userInitPassword))); // 设置默认密码
+                        .setPassword(encodePassword(userInitPassword))); // 设置默认密码
                 respVO.getCreateUsernames().add(importUser.getUsername());
                 respVO.getCreateUsernames().add(importUser.getUsername());
                 return;
                 return;
             }
             }
@@ -441,4 +438,19 @@ public class AdminUserServiceImpl implements AdminUserService {
         return userMapper.selectListByStatus(status);
         return userMapper.selectListByStatus(status);
     }
     }
 
 
+    @Override
+    public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+
+    /**
+     * 对密码进行加密
+     *
+     * @param password 密码
+     * @return 加密后的密码
+     */
+    private String encodePassword(String password) {
+        return passwordEncoder.encode(password);
+    }
+
 }
 }

+ 104 - 153
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java

@@ -1,42 +1,33 @@
 package cn.iocoder.yudao.module.system.service.auth;
 package cn.iocoder.yudao.module.system.service.auth;
 
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
 import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginRespVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
 import cn.iocoder.yudao.module.system.service.common.CaptchaService;
 import cn.iocoder.yudao.module.system.service.common.CaptchaService;
-import cn.iocoder.yudao.module.system.service.dept.PostService;
 import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
 import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
-import cn.iocoder.yudao.module.system.service.permission.PermissionService;
 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.Import;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.DisabledException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
 import javax.validation.Validator;
 import javax.validation.Validator;
-import java.util.Set;
 
 
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
-import static java.util.Collections.singleton;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.*;
 import static org.mockito.Mockito.*;
 
 
@@ -49,23 +40,15 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
     @MockBean
     @MockBean
     private AdminUserService userService;
     private AdminUserService userService;
     @MockBean
     @MockBean
-    private PermissionService permissionService;
-    @MockBean
-    private AuthenticationManager authenticationManager;
-    @MockBean
-    private Authentication authentication;
-    @MockBean
     private CaptchaService captchaService;
     private CaptchaService captchaService;
     @MockBean
     @MockBean
     private LoginLogService loginLogService;
     private LoginLogService loginLogService;
     @MockBean
     @MockBean
-    private UserSessionService userSessionService;
-    @MockBean
     private SocialUserService socialService;
     private SocialUserService socialService;
     @MockBean
     @MockBean
-    private PostService postService;
-    @MockBean
     private SmsCodeApi smsCodeApi;
     private SmsCodeApi smsCodeApi;
+    @MockBean
+    private OAuth2TokenService oauth2TokenService;
 
 
     @MockBean
     @MockBean
     private Validator validator;
     private Validator validator;
@@ -76,216 +59,184 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
     }
     }
 
 
     @Test
     @Test
-    public void testLoadUserByUsername_success() {
+    public void testLogin0_success() {
         // 准备参数
         // 准备参数
         String username = randomString();
         String username = randomString();
-        // mock 方法
-        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username));
+        String password = randomString();
+        // mock user 数据
+        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)
+                .setPassword(password).setStatus(CommonStatusEnum.ENABLE.getStatus()));
         when(userService.getUserByUsername(eq(username))).thenReturn(user);
         when(userService.getUserByUsername(eq(username))).thenReturn(user);
+        // mock password 匹配
+        when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true);
 
 
         // 调用
         // 调用
-        LoginUser loginUser = (LoginUser) authService.loadUserByUsername(username);
+        AdminUserDO loginUser = authService.login0(username, password);
         // 校验
         // 校验
-        AssertUtils.assertPojoEquals(user, loginUser, "updateTime");
+        assertPojoEquals(user, loginUser);
     }
     }
 
 
     @Test
     @Test
-    public void testLoadUserByUsername_userNotFound() {
+    public void testLogin0_userNotFound() {
         // 准备参数
         // 准备参数
         String username = randomString();
         String username = randomString();
-        // mock 方法
+        String password = randomString();
 
 
         // 调用, 并断言异常
         // 调用, 并断言异常
-        assertThrows(UsernameNotFoundException.class, // 抛出 UsernameNotFoundException 异常
-                () -> authService.loadUserByUsername(username),
-                username); // 异常提示为 username
-    }
-
-    @Test
-    public void testMockLogin_success() {
-        // 准备参数
-        Long userId = randomLongId();
-        // mock 方法 01
-        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId));
-        when(userService.getUser(eq(userId))).thenReturn(user);
-        // mock 方法 02
-        Set<Long> roleIds = randomSet(Long.class);
-        when(permissionService.getUserRoleIds(eq(userId), eq(singleton(CommonStatusEnum.ENABLE.getStatus()))))
-                .thenReturn(roleIds);
-
-        // 调用
-        LoginUser loginUser = authService.mockLogin(userId);
-        // 断言
-        AssertUtils.assertPojoEquals(user, loginUser, "updateTime");
-        assertEquals(roleIds, loginUser.getRoleIds());
+        AssertUtils.assertServiceException(() -> authService.login0(username, password),
+                AUTH_LOGIN_BAD_CREDENTIALS);
+        verify(loginLogService).createLoginLog(
+                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
+                        && o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult())
+                        && o.getUserId() == null)
+        );
     }
     }
 
 
     @Test
     @Test
-    public void testMockLogin_userNotFound() {
+    public void testLogin0_badCredentials() {
         // 准备参数
         // 准备参数
-        Long userId = randomLongId();
-        // mock 方法
+        String username = randomString();
+        String password = randomString();
+        // mock user 数据
+        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)
+                .setPassword(password).setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        when(userService.getUserByUsername(eq(username))).thenReturn(user);
 
 
         // 调用, 并断言异常
         // 调用, 并断言异常
-        assertThrows(UsernameNotFoundException.class, // 抛出 UsernameNotFoundException 异常
-                () -> authService.mockLogin(userId),
-                String.valueOf(userId)); // 异常提示为 userId
+        AssertUtils.assertServiceException(() -> authService.login0(username, password),
+                AUTH_LOGIN_BAD_CREDENTIALS);
+        verify(loginLogService).createLoginLog(
+                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
+                        && o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult())
+                        && o.getUserId().equals(user.getId()))
+        );
     }
     }
 
 
     @Test
     @Test
-    public void testLogin_captchaNotFound() {
+    public void testLogin0_userDisabled() {
         // 准备参数
         // 准备参数
-        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
-        String userIp = randomString();
-        String userAgent = randomString();
+        String username = randomString();
+        String password = randomString();
+        // mock user 数据
+        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)
+                .setPassword(password).setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        when(userService.getUserByUsername(eq(username))).thenReturn(user);
+        // mock password 匹配
+        when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true);
 
 
         // 调用, 并断言异常
         // 调用, 并断言异常
-        assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_NOT_FOUND);
-        // 校验调用参数
-        verify(loginLogService, times(1)).createLoginLog(
-            argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
-                    && o.getResult().equals(LoginResultEnum.CAPTCHA_NOT_FOUND.getResult()))
+        AssertUtils.assertServiceException(() -> authService.login0(username, password),
+                AUTH_LOGIN_USER_DISABLED);
+        verify(loginLogService).createLoginLog(
+                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
+                        && o.getResult().equals(LoginResultEnum.USER_DISABLED.getResult())
+                        && o.getUserId().equals(user.getId()))
         );
         );
     }
     }
 
 
     @Test
     @Test
-    public void testLogin_captchaCodeError() {
+    public void testCaptcha_success() {
         // 准备参数
         // 准备参数
-        String userIp = randomString();
-        String userAgent = randomString();
         AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
         AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
 
 
-        // mock 验证码不正确
-        String code = randomString();
-        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code);
+        // mock 验证码正确
+        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
 
 
-        // 调用, 并断言异常
-        assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_CODE_ERROR);
-        // 校验调用参数
-        verify(loginLogService, times(1)).createLoginLog(
-            argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
-                    && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult()))
-        );
+        // 调用
+        authService.verifyCaptcha(reqVO);
+        // 断言
+        verify(captchaService).deleteCaptchaCode(reqVO.getUuid());
     }
     }
 
 
     @Test
     @Test
-    public void testLogin_badCredentials() {
+    public void testCaptcha_notFound() {
         // 准备参数
         // 准备参数
-        String userIp = randomString();
-        String userAgent = randomString();
         AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
         AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
-        // mock 验证码正确
-        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
-        // mock 抛出异常
-        when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
-                .thenThrow(new BadCredentialsException("测试账号或密码不正确"));
 
 
         // 调用, 并断言异常
         // 调用, 并断言异常
-        assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_BAD_CREDENTIALS);
+        assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_NOT_FOUND);
         // 校验调用参数
         // 校验调用参数
-        verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid());
         verify(loginLogService, times(1)).createLoginLog(
         verify(loginLogService, times(1)).createLoginLog(
             argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
             argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
-                    && o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult()))
+                    && o.getResult().equals(LoginResultEnum.CAPTCHA_NOT_FOUND.getResult()))
         );
         );
     }
     }
 
 
     @Test
     @Test
-    public void testLogin_userDisabled() {
+    public void testCaptcha_codeError() {
         // 准备参数
         // 准备参数
-        String userIp = randomString();
-        String userAgent = randomString();
         AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
         AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
 
 
-        // mock 验证码正确
-        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
-        // mock 抛出异常
-        when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
-                .thenThrow(new DisabledException("测试用户被禁用"));
+        // mock 验证码不正确
+        String code = randomString();
+        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code);
 
 
         // 调用, 并断言异常
         // 调用, 并断言异常
-        assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_USER_DISABLED);
+        assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR);
         // 校验调用参数
         // 校验调用参数
-        verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid());
-        verify(loginLogService, times(1)).createLoginLog(
+        verify(loginLogService).createLoginLog(
             argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
             argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
-                    && o.getResult().equals(LoginResultEnum.USER_DISABLED.getResult()))
+                    && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult()))
         );
         );
     }
     }
 
 
     @Test
     @Test
-    public void testLogin_unknownError() {
+    public void testLogin_success() {
         // 准备参数
         // 准备参数
-        String userIp = randomString();
-        String userAgent = randomString();
-        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
+        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o ->
+                o.setUsername("test_username").setPassword("test_password"));
+
         // mock 验证码正确
         // mock 验证码正确
         when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
         when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
-        // mock 抛出异常
-        when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
-                .thenThrow(new AuthenticationException("测试未知异常") {});
+        // mock user 数据
+        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L).setUsername("test_username")
+                .setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        when(userService.getUserByUsername(eq("test_username"))).thenReturn(user);
+        // mock password 匹配
+        when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true);
+        // mock 缓存登录用户到 Redis
+        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
+                .setUserType(UserTypeEnum.ADMIN.getValue()));
+        when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq(1L)))
+                .thenReturn(accessTokenDO);
 
 
         // 调用, 并断言异常
         // 调用, 并断言异常
-        assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_FAIL_UNKNOWN);
+        AuthLoginRespVO loginRespVO = authService.login(reqVO);
+        assertPojoEquals(accessTokenDO, loginRespVO);
         // 校验调用参数
         // 校验调用参数
-        verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid());
-        verify(loginLogService, times(1)).createLoginLog(
+        verify(loginLogService).createLoginLog(
             argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
             argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
-                    && o.getResult().equals(LoginResultEnum.UNKNOWN_ERROR.getResult()))
+                    && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())
+                    && o.getUserId().equals(user.getId()))
         );
         );
     }
     }
 
 
     @Test
     @Test
-    public void testLogin_success() {
+    public void testLogout_success() {
         // 准备参数
         // 准备参数
-        String userIp = randomString();
-        String userAgent = randomString();
-        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
-
-        // mock 验证码正确
-        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
-        // mock authentication
-        Long userId = randomLongId();
-        Set<Long> userRoleIds = randomSet(Long.class);
-        LoginUser loginUser = randomPojo(LoginUser.class, o -> {
-            o.setId(userId);
-            o.setRoleIds(userRoleIds);
-        });
-        when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
-                .thenReturn(authentication);
-        when(authentication.getPrincipal()).thenReturn(loginUser);
-        // mock 获得 User 拥有的角色编号数组
-        when(permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()))).thenReturn(userRoleIds);
-        // mock 缓存登录用户到 Redis
         String token = randomString();
         String token = randomString();
-        when(userSessionService.createUserSession(loginUser, userIp, userAgent)).thenReturn(token);
+        // mock
+        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
+                .setUserType(UserTypeEnum.ADMIN.getValue()));
+        when(oauth2TokenService.removeAccessToken(eq(token))).thenReturn(accessTokenDO);
 
 
-        // 调用, 并断言异常
-        String login = authService.login(reqVO, userIp, userAgent);
-        assertEquals(token, login);
+        // 调用
+        authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
         // 校验调用参数
         // 校验调用参数
-        verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid());
-        verify(loginLogService, times(1)).createLoginLog(
-            argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
+        verify(loginLogService).createLoginLog(argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType())
                     && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))
                     && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))
         );
         );
     }
     }
 
 
     @Test
     @Test
-    public void testLogout_success() {
+    public void testLogout_fail() {
         // 准备参数
         // 准备参数
         String token = randomString();
         String token = randomString();
-        LoginUser loginUser = randomPojo(LoginUser.class);
-        // mock
-        when(userSessionService.getLoginUser(token)).thenReturn(loginUser);
+
         // 调用
         // 调用
-        authService.logout(token);
+        authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
         // 校验调用参数
         // 校验调用参数
-        verify(userSessionService, times(1)).deleteUserSession(token);
-        verify(loginLogService, times(1)).createLoginLog(
-            argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType())
-                    && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))
-        );
+        verify(loginLogService, never()).createLoginLog(any());
     }
     }
 
 
 }
 }

+ 128 - 0
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImplTest.java

@@ -0,0 +1,128 @@
+package cn.iocoder.yudao.module.system.service.auth;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
+import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+* {@link OAuth2ClientServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+@Import(OAuth2ClientServiceImpl.class)
+public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private OAuth2ClientServiceImpl oAuth2ClientService;
+
+    @Resource
+    private OAuth2ClientMapper oAuth2ClientMapper;
+
+    @Test
+    public void testCreateOAuth2Client_success() {
+        // 准备参数
+        OAuth2ClientCreateReqVO reqVO = randomPojo(OAuth2ClientCreateReqVO.class);
+
+        // 调用
+        Long oauth2ClientId = oAuth2ClientService.createOAuth2Client(reqVO);
+        // 断言
+        assertNotNull(oauth2ClientId);
+        // 校验记录的属性是否正确
+        OAuth2ClientDO oAuth2Client = oAuth2ClientMapper.selectById(oauth2ClientId);
+        assertPojoEquals(reqVO, oAuth2Client);
+    }
+
+    @Test
+    public void testUpdateOAuth2Client_success() {
+        // mock 数据
+        OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class);
+        oAuth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        OAuth2ClientUpdateReqVO reqVO = randomPojo(OAuth2ClientUpdateReqVO.class, o -> {
+            o.setId(dbOAuth2Client.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        oAuth2ClientService.updateOAuth2Client(reqVO);
+        // 校验是否更新正确
+        OAuth2ClientDO oAuth2Client = oAuth2ClientMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, oAuth2Client);
+    }
+
+    @Test
+    public void testUpdateOAuth2Client_notExists() {
+        // 准备参数
+        OAuth2ClientUpdateReqVO reqVO = randomPojo(OAuth2ClientUpdateReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> oAuth2ClientService.updateOAuth2Client(reqVO), OAUTH2_CLIENT_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteOAuth2Client_success() {
+        // mock 数据
+        OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class);
+        oAuth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbOAuth2Client.getId();
+
+        // 调用
+        oAuth2ClientService.deleteOAuth2Client(id);
+       // 校验数据不存在了
+       assertNull(oAuth2ClientMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteOAuth2Client_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> oAuth2ClientService.deleteOAuth2Client(id), OAUTH2_CLIENT_NOT_EXISTS);
+    }
+
+    @Test
+    @Disabled
+    public void testGetOAuth2ClientPage() {
+       // mock 数据
+       OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class, o -> { // 等会查询到
+           o.setName("潜龙");
+           o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+       });
+       oAuth2ClientMapper.insert(dbOAuth2Client);
+       // 测试 name 不匹配
+       oAuth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰")));
+       // 测试 status 不匹配
+       oAuth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())));
+       // 准备参数
+       OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO();
+       reqVO.setName("long");
+       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+
+       // 调用
+       PageResult<OAuth2ClientDO> pageResult = oAuth2ClientService.getOAuth2ClientPage(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(dbOAuth2Client, pageResult.getList().get(0));
+    }
+
+}

+ 0 - 250
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java

@@ -1,250 +0,0 @@
-package cn.iocoder.yudao.module.system.service.auth;
-
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.date.DateUtils;
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-import cn.iocoder.yudao.framework.security.config.SecurityProperties;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
-import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
-import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
-import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper;
-import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO;
-import cn.iocoder.yudao.module.system.enums.common.SexEnum;
-import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
-import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
-import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
-import cn.iocoder.yudao.module.system.service.user.AdminUserService;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.context.annotation.Import;
-
-import javax.annotation.Resource;
-import java.time.Duration;
-import java.util.Calendar;
-
-import static cn.hutool.core.util.RandomUtil.randomEle;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static java.util.Collections.singletonList;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-/**
- * {@link UserSessionServiceImpl} 的单元测试
- *
- * @author Lyon
- */
-@Import({UserSessionServiceImpl.class, LoginUserRedisDAO.class})
-public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
-
-    @Resource
-    private UserSessionServiceImpl userSessionService;
-
-    @Resource
-    private UserSessionMapper userSessionMapper;
-
-    @MockBean
-    private AdminUserService userService;
-    @MockBean
-    private LoginLogService loginLogService;
-    @Resource
-    private LoginUserRedisDAO loginUserRedisDAO;
-
-    @MockBean
-    private SecurityProperties securityProperties;
-
-    @BeforeEach
-    public void setUp() {
-        when(securityProperties.getSessionTimeout()).thenReturn(Duration.ofDays(1L));
-    }
-
-    @Test
-    public void testGetUserSessionPage_success() {
-        // mock 数据
-        AdminUserDO dbUser = randomPojo(AdminUserDO.class, o -> {
-            o.setSex(randomEle(SexEnum.values()).getSex());
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-        });
-        when(userService.getUsersByUsername(eq(dbUser.getUsername()))).thenReturn(singletonList(dbUser));
-        // 插入可被查询到的数据
-        String userIp = randomString();
-        UserSessionDO dbSession = randomPojo(UserSessionDO.class, o -> {
-            o.setUserId(dbUser.getId());
-            o.setUserType(randomEle(UserTypeEnum.values()).getValue());
-            o.setUserIp(userIp);
-        });
-        userSessionMapper.insert(dbSession);
-        // 测试 username 不匹配
-        userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> o.setUserId(123456L)));
-        // 测试 userIp 不匹配
-        userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> o.setUserIp("testUserIp")));
-        // 准备参数
-        UserSessionPageReqVO reqVO = new UserSessionPageReqVO();
-        reqVO.setUsername(dbUser.getUsername());
-        reqVO.setUserIp(userIp);
-
-        // 调用
-        PageResult<UserSessionDO> pageResult = userSessionService.getUserSessionPage(reqVO);
-        // 断言
-        assertEquals(1, pageResult.getTotal());
-        assertEquals(1, pageResult.getList().size());
-        assertPojoEquals(dbSession, pageResult.getList().get(0));
-    }
-
-    @Test
-    public void testClearSessionTimeout_none() {
-        // mock db 数据
-        UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
-            o.setUserType(randomEle(UserTypeEnum.values()).getValue());
-            o.setSessionTimeout(addTime(Duration.ofDays(1)));
-        });
-        userSessionMapper.insert(userSession);
-
-        // 调用
-        long count = userSessionService.deleteTimeoutSession();
-        // 断言
-        assertEquals(0, count);
-        assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除
-    }
-
-    @Test // Redis 还存在的情况
-    public void testClearSessionTimeout_exists() {
-        // mock db 数据
-        UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
-            o.setUserType(randomEle(UserTypeEnum.values()).getValue());
-            o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1));
-        });
-        userSessionMapper.insert(userSession);
-        // mock redis 数据
-        loginUserRedisDAO.set(userSession.getToken(), new LoginUser());
-
-        // 调用
-        long count = userSessionService.deleteTimeoutSession();
-        // 断言
-        assertEquals(0, count);
-        assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除
-    }
-
-    @Test
-    public void testClearSessionTimeout_success() {
-        // mock db 数据
-        UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
-            o.setUserType(randomEle(UserTypeEnum.values()).getValue());
-            o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1));
-        });
-        userSessionMapper.insert(userSession);
-
-        // 清空超时数据
-        long count = userSessionService.deleteTimeoutSession();
-        // 校验
-        assertEquals(1, count);
-        assertNull(userSessionMapper.selectById(userSession.getId())); // 已删除
-        verify(loginLogService).createLoginLog(argThat(loginLog -> {
-            assertPojoEquals(userSession, loginLog);
-            assertEquals(LoginLogTypeEnum.LOGOUT_TIMEOUT.getType(), loginLog.getLogType());
-            assertEquals(LoginResultEnum.SUCCESS.getResult(), loginLog.getResult());
-            return true;
-        }));
-    }
-
-    @Test
-    public void testCreateUserSession_success() {
-        // 准备参数
-        String userIp = randomString();
-        String userAgent = randomString();
-        LoginUser loginUser = randomPojo(LoginUser.class, o -> {
-            o.setUserType(randomEle(UserTypeEnum.values()).getValue());
-            o.setTenantId(0L); // 租户设置为 0,因为暂未启用多租户组件
-        });
-
-        // 调用
-        String token = userSessionService.createUserSession(loginUser, userIp, userAgent);
-        // 校验 UserSessionDO 记录
-        UserSessionDO userSessionDO = userSessionMapper.selectOne(UserSessionDO::getToken, token);
-        assertPojoEquals(loginUser, userSessionDO, "id", "updateTime");
-        assertEquals(token, userSessionDO.getToken());
-        assertEquals(userIp, userSessionDO.getUserIp());
-        assertEquals(userAgent, userSessionDO.getUserAgent());
-        // 校验 LoginUser 缓存
-        LoginUser redisLoginUser = loginUserRedisDAO.get(token);
-        assertPojoEquals(loginUser, redisLoginUser, "username", "password");
-    }
-
-    @Test
-    public void testCreateRefreshUserSession() {
-        // 准备参数
-        String token = randomString();
-
-        // mock redis 数据
-        LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setUserType(randomEle(UserTypeEnum.values()).getValue()));
-        loginUserRedisDAO.set(token, loginUser);
-        // mock db 数据
-        UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
-            o.setUserType(randomEle(UserTypeEnum.values()).getValue());
-            o.setToken(token);
-        });
-        userSessionMapper.insert(userSession);
-
-        // 调用
-        userSessionService.refreshUserSession(token, loginUser);
-        // 校验 LoginUser 缓存
-        LoginUser redisLoginUser = loginUserRedisDAO.get(token);
-        assertPojoEquals(redisLoginUser, loginUser, "username", "password");
-        // 校验 UserSessionDO 记录
-        UserSessionDO updateDO = userSessionMapper.selectOne(UserSessionDO::getToken, token);
-        assertEquals(updateDO.getUsername(), loginUser.getUsername());
-        assertNotNull(userSession.getUpdateTime());
-        assertNotNull(userSession.getSessionTimeout());
-    }
-
-    @Test
-    public void testDeleteUserSession_Token() {
-        // 准备参数
-        String token = randomString();
-
-        // mock redis 数据
-        loginUserRedisDAO.set(token, new LoginUser());
-        // mock db 数据
-        UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
-            o.setUserType(randomEle(UserTypeEnum.values()).getValue());
-            o.setToken(token);
-        });
-        userSessionMapper.insert(userSession);
-
-        // 调用
-        userSessionService.deleteUserSession(token);
-        // 校验数据不存在了
-        assertNull(loginUserRedisDAO.get(token));
-        assertNull(userSessionMapper.selectOne(UserSessionDO::getToken, token));
-    }
-
-    @Test
-    public void testDeleteUserSession_Id() {
-        // mock db 数据
-        UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
-            o.setUserType(randomEle(UserTypeEnum.values()).getValue());
-        });
-        userSessionMapper.insert(userSession);
-        // mock redis 数据
-        loginUserRedisDAO.set(userSession.getToken(), new LoginUser());
-
-        // 准备参数
-        Long id = userSession.getId();
-
-        // 调用
-        userSessionService.deleteUserSession(id);
-        // 校验数据不存在了
-        assertNull(loginUserRedisDAO.get(userSession.getToken()));
-        assertNull(userSessionMapper.selectById(id));
-    }
-
-}

+ 65 - 53
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java

@@ -1,20 +1,22 @@
 package cn.iocoder.yudao.module.system.service.permission;
 package cn.iocoder.yudao.module.system.service.permission;
 
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuBatchInsertMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuBatchInsertMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleBatchInsertMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleBatchInsertMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
+import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
 import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
 import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
 import cn.iocoder.yudao.module.system.service.dept.DeptService;
 import cn.iocoder.yudao.module.system.service.dept.DeptService;
-import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.Import;
@@ -25,10 +27,10 @@ import java.util.List;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static java.util.Collections.singleton;
 import static java.util.Collections.singletonList;
 import static java.util.Collections.singletonList;
 import static org.junit.jupiter.api.Assertions.*;
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.when;
 
 
@@ -55,6 +57,9 @@ public class PermissionServiceTest extends BaseDbUnitTest {
     @MockBean
     @MockBean
     private DeptService deptService;
     private DeptService deptService;
     @MockBean
     @MockBean
+    private AdminUserService userService;
+
+    @MockBean
     private PermissionProducer permissionProducer;
     private PermissionProducer permissionProducer;
 
 
     @Test
     @Test
@@ -124,112 +129,119 @@ public class PermissionServiceTest extends BaseDbUnitTest {
         assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
         assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
     }
     }
 
 
-    @Test // 测试从 context 获取的场景
-    public void testGetDeptDataPermission_fromContext() {
-        // 准备参数
-        LoginUser loginUser = randomPojo(LoginUser.class);
-        // mock 方法
-        DeptDataPermissionRespDTO respDTO = new DeptDataPermissionRespDTO();
-        loginUser.setContext(PermissionServiceImpl.CONTEXT_KEY, respDTO);
-
-        // 调用
-        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
-        // 断言
-        assertSame(respDTO, result);
-    }
-
     @Test
     @Test
     public void testGetDeptDataPermission_All() {
     public void testGetDeptDataPermission_All() {
         // 准备参数
         // 准备参数
-        LoginUser loginUser = randomPojo(LoginUser.class);
-        // mock 方法
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope()));
-        when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO));
+        Long userId = 1L;
+        // mock 用户的角色编号
+        userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L));
+        // mock 获得用户的角色
+        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())
+                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
+        when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
 
 
         // 调用
         // 调用
-        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
+        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
         // 断言
         // 断言
         assertTrue(result.getAll());
         assertTrue(result.getAll());
         assertFalse(result.getSelf());
         assertFalse(result.getSelf());
         assertTrue(CollUtil.isEmpty(result.getDeptIds()));
         assertTrue(CollUtil.isEmpty(result.getDeptIds()));
-        assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
     }
     }
 
 
     @Test
     @Test
     public void testGetDeptDataPermission_DeptCustom() {
     public void testGetDeptDataPermission_DeptCustom() {
         // 准备参数
         // 准备参数
-        LoginUser loginUser = randomPojo(LoginUser.class);
-        // mock 方法
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope()));
-        when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO));
+        Long userId = 1L;
+        // mock 用户的角色编号
+        userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L));
+        // mock 获得用户的角色
+        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
+                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
+        when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
+        // mock 部门的返回
+        when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
 
 
         // 调用
         // 调用
-        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
+        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(1L);
         // 断言
         // 断言
         assertFalse(result.getAll());
         assertFalse(result.getAll());
         assertFalse(result.getSelf());
         assertFalse(result.getSelf());
         assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());
         assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());
         assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));
         assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));
-        assertTrue(CollUtil.contains(result.getDeptIds(), loginUser.getDeptId()));
-        assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
+        assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
     }
     }
 
 
     @Test
     @Test
     public void testGetDeptDataPermission_DeptOnly() {
     public void testGetDeptDataPermission_DeptOnly() {
         // 准备参数
         // 准备参数
-        LoginUser loginUser = randomPojo(LoginUser.class);
-        // mock 方法
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope()));
-        when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO));
+        Long userId = 1L;
+        // mock 用户的角色编号
+        userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L));
+        // mock 获得用户的角色
+        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())
+                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
+        when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
+        // mock 部门的返回
+        when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
 
 
         // 调用
         // 调用
-        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
+        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(1L);
         // 断言
         // 断言
         assertFalse(result.getAll());
         assertFalse(result.getAll());
         assertFalse(result.getSelf());
         assertFalse(result.getSelf());
         assertEquals(1, result.getDeptIds().size());
         assertEquals(1, result.getDeptIds().size());
-        assertTrue(CollUtil.contains(result.getDeptIds(), loginUser.getDeptId()));
-        assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
+        assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
     }
     }
 
 
     @Test
     @Test
     public void testGetDeptDataPermission_DeptAndChild() {
     public void testGetDeptDataPermission_DeptAndChild() {
         // 准备参数
         // 准备参数
-        LoginUser loginUser = randomPojo(LoginUser.class);
-        // mock 方法(角色)
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope()));
-        when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO));
+        Long userId = 1L;
+        // mock 用户的角色编号
+        userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L));
+        // mock 获得用户的角色
+        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())
+                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
+        when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
+        // mock 部门的返回
+        when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
         // mock 方法(部门)
         // mock 方法(部门)
         DeptDO deptDO = randomPojo(DeptDO.class);
         DeptDO deptDO = randomPojo(DeptDO.class);
-        when(deptService.getDeptsByParentIdFromCache(eq(loginUser.getDeptId()), eq(true)))
+        when(deptService.getDeptsByParentIdFromCache(eq(3L), eq(true)))
                 .thenReturn(singletonList(deptDO));
                 .thenReturn(singletonList(deptDO));
 
 
         // 调用
         // 调用
-        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
+        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
         // 断言
         // 断言
         assertFalse(result.getAll());
         assertFalse(result.getAll());
         assertFalse(result.getSelf());
         assertFalse(result.getSelf());
         assertEquals(2, result.getDeptIds().size());
         assertEquals(2, result.getDeptIds().size());
         assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId()));
         assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId()));
-        assertTrue(CollUtil.contains(result.getDeptIds(), loginUser.getDeptId()));
-        assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
+        assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
     }
     }
 
 
     @Test
     @Test
     public void testGetDeptDataPermission_Self() {
     public void testGetDeptDataPermission_Self() {
         // 准备参数
         // 准备参数
-        LoginUser loginUser = randomPojo(LoginUser.class);
-        // mock 方法
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope()));
-        when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO));
+        Long userId = 1L;
+        // mock 用户的角色编号
+        userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L));
+        // mock 获得用户的角色
+        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())
+                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
+        when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
 
 
         // 调用
         // 调用
-        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
+        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
         // 断言
         // 断言
         assertFalse(result.getAll());
         assertFalse(result.getAll());
         assertTrue(result.getSelf());
         assertTrue(result.getSelf());
         assertTrue(CollUtil.isEmpty(result.getDeptIds()));
         assertTrue(CollUtil.isEmpty(result.getDeptIds()));
-        assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
     }
     }
 
 
 }
 }

+ 1 - 0
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql

@@ -20,3 +20,4 @@ DELETE FROM "system_social_user_bind";
 DELETE FROM "system_tenant";
 DELETE FROM "system_tenant";
 DELETE FROM "system_tenant_package";
 DELETE FROM "system_tenant_package";
 DELETE FROM "system_sensitive_word";
 DELETE FROM "system_sensitive_word";
+DELETE FROM "system_oauth2_client";

+ 0 - 0
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio