Commit 190150d1f4539082a6517a3083e9f724ac659298

Authored by 芋道源码
Committed by Gitee
2 parents 32944421 1f36af8e

!166 用户 Token 采用 OAuth2 的 Access Token + Refresh Token,提升安全性

Merge pull request !166 from 芋道源码/feature/1.6.2
Showing 117 changed files with 2771 additions and 2276 deletions

Too many changes.

To preserve performance only 100 of 117 files are displayed.

yudao-dependencies/pom.xml
... ... @@ -22,7 +22,6 @@
22 22 <swagger-annotations.version>1.5.22</swagger-annotations.version>
23 23 <servlet.versoin>2.5</servlet.versoin>
24 24 <!-- DB 相关 -->
25   - <mysql.version>5.1.46</mysql.version>
26 25 <druid.version>1.2.8</druid.version>
27 26 <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
28 27 <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
... ... @@ -76,12 +75,6 @@
76 75 <version>${spring.boot.version}</version>
77 76 <type>pom</type>
78 77 <scope>import</scope>
79   - <exclusions>
80   - <exclusion>
81   - <groupId>mysql</groupId>
82   - <artifactId>mysql-connector-java</artifactId>
83   - </exclusion>
84   - </exclusions>
85 78 </dependency>
86 79  
87 80 <!-- 业务组件 -->
... ... @@ -180,11 +173,6 @@
180 173 </dependency>
181 174  
182 175 <dependency>
183   - <groupId>mysql</groupId>
184   - <artifactId>mysql-connector-java</artifactId>
185   - <version>${mysql.version}</version>
186   - </dependency>
187   - <dependency>
188 176 <groupId>com.alibaba</groupId>
189 177 <artifactId>druid-spring-boot-starter</artifactId>
190 178 <version>${druid.version}</version>
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
... ... @@ -54,6 +54,13 @@ public class CollectionUtils {
54 54 return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
55 55 }
56 56  
  57 + public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
  58 + if (CollUtil.isEmpty(from)) {
  59 + return new ArrayList<>();
  60 + }
  61 + return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
  62 + }
  63 +
57 64 public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
58 65 if (CollUtil.isEmpty(from)) {
59 66 return new HashSet<>();
... ...
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRule.java
1 1 package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
4 5 import cn.hutool.core.util.StrUtil;
5   -import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
6   -import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
  6 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
7 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  9 +import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
  10 +import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
9 11 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
10 12 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
11 13 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
... ... @@ -13,7 +15,6 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
13 15 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
14 16 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
15 17 import lombok.AllArgsConstructor;
16   -import lombok.Getter;
17 18 import lombok.extern.slf4j.Slf4j;
18 19 import net.sf.jsqlparser.expression.Alias;
19 20 import net.sf.jsqlparser.expression.Expression;
... ... @@ -24,10 +25,7 @@ import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
24 25 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
25 26 import net.sf.jsqlparser.expression.operators.relational.InExpression;
26 27  
27   -import java.util.HashMap;
28   -import java.util.HashSet;
29   -import java.util.Map;
30   -import java.util.Set;
  28 +import java.util.*;
31 29  
32 30 /**
33 31 * 基于部门的 {@link DataPermissionRule} 数据权限规则实现
... ... @@ -50,6 +48,11 @@ import java.util.Set;
50 48 @Slf4j
51 49 public class DeptDataPermissionRule implements DataPermissionRule {
52 50  
  51 + /**
  52 + * LoginUser 的 Context 缓存 Key
  53 + */
  54 + protected static final String CONTEXT_KEY = DeptDataPermissionRule.class.getSimpleName();
  55 +
53 56 private static final String DEPT_COLUMN_NAME = "dept_id";
54 57 private static final String USER_COLUMN_NAME = "user_id";
55 58  
... ... @@ -90,13 +93,23 @@ public class DeptDataPermissionRule implements DataPermissionRule {
90 93 if (loginUser == null) {
91 94 return null;
92 95 }
  96 + // 只有管理员类型的用户,才进行数据权限的处理
  97 + if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {
  98 + return null;
  99 + }
93 100  
94 101 // 获得数据权限
95   - DeptDataPermissionRespDTO deptDataPermission = deptDataPermissionService.getDeptDataPermission(loginUser);
  102 + DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
  103 + // 从上下文中拿不到,则调用逻辑进行获取
96 104 if (deptDataPermission == null) {
97   - log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
98   - throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
99   - loginUser.getId(), tableName, tableAlias.getName()));
  105 + deptDataPermission = deptDataPermissionService.getDeptDataPermission(loginUser.getId());
  106 + if (deptDataPermission == null) {
  107 + log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
  108 + throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
  109 + loginUser.getId(), tableName, tableAlias.getName()));
  110 + }
  111 + // 添加到上下文中,避免重复计算
  112 + loginUser.setContext(CONTEXT_KEY, deptDataPermission);
100 113 }
101 114  
102 115 // 情况一,如果是 ALL 可查看全部,则无需拼接条件
... ... @@ -111,8 +124,8 @@ public class DeptDataPermissionRule implements DataPermissionRule {
111 124 }
112 125  
113 126 // 情况三,拼接 Dept 和 User 的条件,最后组合
114   - Expression deptExpression = this.buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
115   - Expression userExpression = this.buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
  127 + Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
  128 + Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
116 129 if (deptExpression == null && userExpression == null) {
117 130 // TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据
118 131 log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",
... ...
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/service/DeptDataPermissionFrameworkService.java
1 1 package cn.iocoder.yudao.framework.datapermission.core.dept.service;
2 2  
3 3 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
4   -import cn.iocoder.yudao.framework.security.core.LoginUser;
5 4  
6 5 /**
7 6 * 基于部门的数据权限 Framework Service 接口
... ... @@ -14,9 +13,9 @@ public interface DeptDataPermissionFrameworkService {
14 13 /**
15 14 * 获得登陆用户的部门数据权限
16 15 *
17   - * @param loginUser 登陆用户
  16 + * @param userId 用户编号
18 17 * @return 部门数据权限
19 18 */
20   - DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser);
  19 + DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
21 20  
22 21 }
... ...
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;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ReflectUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 6 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
6 7 import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
7 8 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
... ... @@ -69,7 +70,8 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
69 70 String tableName = "t_user";
70 71 Alias tableAlias = new Alias("u");
71 72 // mock 方法
72   - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
  73 + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
  74 + .setUserType(UserTypeEnum.ADMIN.getValue()));
73 75 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
74 76  
75 77 // 调用
... ... @@ -88,16 +90,18 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
88 90 String tableName = "t_user";
89 91 Alias tableAlias = new Alias("u");
90 92 // mock 方法(LoginUser)
91   - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
  93 + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
  94 + .setUserType(UserTypeEnum.ADMIN.getValue()));
92 95 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
93 96 // mock 方法(DeptDataPermissionRespDTO)
94 97 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO().setAll(true);
95   - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
  98 + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
96 99  
97 100 // 调用
98 101 Expression expression = rule.getExpression(tableName, tableAlias);
99 102 // 断言
100 103 assertNull(expression);
  104 + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
101 105 }
102 106 }
103 107  
... ... @@ -109,16 +113,18 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
109 113 String tableName = "t_user";
110 114 Alias tableAlias = new Alias("u");
111 115 // mock 方法(LoginUser)
112   - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
  116 + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
  117 + .setUserType(UserTypeEnum.ADMIN.getValue()));
113 118 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
114 119 // mock 方法(DeptDataPermissionRespDTO)
115 120 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO();
116   - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
  121 + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
117 122  
118 123 // 调用
119 124 Expression expression = rule.getExpression(tableName, tableAlias);
120 125 // 断言
121 126 assertEquals("null = null", expression.toString());
  127 + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
122 128 }
123 129 }
124 130  
... ... @@ -130,17 +136,19 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
130 136 String tableName = "t_user";
131 137 Alias tableAlias = new Alias("u");
132 138 // mock 方法(LoginUser)
133   - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
  139 + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
  140 + .setUserType(UserTypeEnum.ADMIN.getValue()));
134 141 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
135 142 // mock 方法(DeptDataPermissionRespDTO)
136 143 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
137 144 .setDeptIds(SetUtils.asSet(10L, 20L)).setSelf(true);
138   - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
  145 + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
139 146  
140 147 // 调用
141 148 Expression expression = rule.getExpression(tableName, tableAlias);
142 149 // 断言
143 150 assertSame(EXPRESSION_NULL, expression);
  151 + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
144 152 }
145 153 }
146 154  
... ... @@ -152,12 +160,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
152 160 String tableName = "t_user";
153 161 Alias tableAlias = new Alias("u");
154 162 // mock 方法(LoginUser)
155   - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
  163 + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
  164 + .setUserType(UserTypeEnum.ADMIN.getValue()));
156 165 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
157 166 // mock 方法(DeptDataPermissionRespDTO)
158 167 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
159 168 .setSelf(true);
160   - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
  169 + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
161 170 // 添加 user 字段配置
162 171 rule.addUserColumn("t_user", "id");
163 172  
... ... @@ -165,6 +174,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
165 174 Expression expression = rule.getExpression(tableName, tableAlias);
166 175 // 断言
167 176 assertEquals("u.id = 1", expression.toString());
  177 + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
168 178 }
169 179 }
170 180  
... ... @@ -176,12 +186,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
176 186 String tableName = "t_user";
177 187 Alias tableAlias = new Alias("u");
178 188 // mock 方法(LoginUser)
179   - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
  189 + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
  190 + .setUserType(UserTypeEnum.ADMIN.getValue()));
180 191 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
181 192 // mock 方法(DeptDataPermissionRespDTO)
182 193 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
183 194 .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L));
184   - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
  195 + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
185 196 // 添加 dept 字段配置
186 197 rule.addDeptColumn("t_user", "dept_id");
187 198  
... ... @@ -189,6 +200,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
189 200 Expression expression = rule.getExpression(tableName, tableAlias);
190 201 // 断言
191 202 assertEquals("u.dept_id IN (10, 20)", expression.toString());
  203 + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
192 204 }
193 205 }
194 206  
... ... @@ -200,12 +212,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
200 212 String tableName = "t_user";
201 213 Alias tableAlias = new Alias("u");
202 214 // mock 方法(LoginUser)
203   - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L));
  215 + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
  216 + .setUserType(UserTypeEnum.ADMIN.getValue()));
204 217 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
205 218 // mock 方法(DeptDataPermissionRespDTO)
206 219 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
207 220 .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L)).setSelf(true);
208   - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission);
  221 + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
209 222 // 添加 user 字段配置
210 223 rule.addUserColumn("t_user", "id");
211 224 // 添加 dept 字段配置
... ... @@ -215,6 +228,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
215 228 Expression expression = rule.getExpression(tableName, tableAlias);
216 229 // 断言
217 230 assertEquals("u.dept_id IN (10, 20) OR u.id = 1", expression.toString());
  231 + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
218 232 }
219 233 }
220 234  
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantContextWebFilter.java
1 1 package cn.iocoder.yudao.framework.tenant.core.web;
2 2  
3   -import cn.hutool.core.util.StrUtil;
4 3 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  4 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
5 5 import org.springframework.web.filter.OncePerRequestFilter;
6 6  
7 7 import javax.servlet.FilterChain;
... ... @@ -24,9 +24,9 @@ public class TenantContextWebFilter extends OncePerRequestFilter {
24 24 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
25 25 throws ServletException, IOException {
26 26 // 设置
27   - String tenantId = request.getHeader(HEADER_TENANT_ID);
28   - if (StrUtil.isNotEmpty(tenantId)) {
29   - TenantContextHolder.setTenantId(Long.valueOf(tenantId));
  27 + Long tenantId = WebFrameworkUtils.getTenantId(request);
  28 + if (tenantId != null) {
  29 + TenantContextHolder.setTenantId(tenantId);
30 30 }
31 31 try {
32 32 chain.doFilter(request, response);
... ...
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&lt;FtpFileClientConfig&gt; {
27 27  
28 28 @Override
29 29 protected void doInit() {
30   - // 补全风格。例如说 Linux 是 /,Windows 是 \
31   - if (!config.getBasePath().endsWith(File.separator)) {
32   - config.setBasePath(config.getBasePath() + File.separator);
  30 + // 把配置的 \ 替换成 /, 如果路径配置 \a\test, 替换成 /a/test, 替换方法已经处理 null 情况
  31 + config.setBasePath(StrUtil.replace(config.getBasePath(), StrUtil.BACKSLASH, StrUtil.SLASH));
  32 + // ftp的路径是 / 结尾
  33 + if (!config.getBasePath().endsWith(StrUtil.SLASH)) {
  34 + config.setBasePath(config.getBasePath() + StrUtil.SLASH);
33 35 }
34 36 // 初始化 Ftp 对象
35 37 this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
... ... @@ -42,6 +44,7 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
42 44 String filePath = getFilePath(path);
43 45 String fileName = FileUtil.getName(filePath);
44 46 String dir = StrUtil.removeSuffix(filePath, fileName);
  47 + ftp.reconnectIfTimeout();
45 48 boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));
46 49 if (!success) {
47 50 throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
... ... @@ -53,6 +56,7 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
53 56 @Override
54 57 public void delete(String path) {
55 58 String filePath = getFilePath(path);
  59 + ftp.reconnectIfTimeout();
56 60 ftp.delFile(filePath);
57 61 }
58 62  
... ... @@ -60,8 +64,9 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
60 64 public byte[] getContent(String path) {
61 65 String filePath = getFilePath(path);
62 66 String fileName = FileUtil.getName(filePath);
63   - String dir = StrUtil.removeSuffix(path, fileName);
  67 + String dir = StrUtil.removeSuffix(filePath, fileName);
64 68 ByteArrayOutputStream out = new ByteArrayOutputStream();
  69 + ftp.reconnectIfTimeout();
65 70 ftp.download(dir, fileName, out);
66 71 return out.toByteArray();
67 72 }
... ... @@ -70,4 +75,4 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
70 75 return config.getBasePath() + path;
71 76 }
72 77  
73 78 -}
  79 +}
74 80 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-security/pom.xml
... ... @@ -44,18 +44,11 @@
44 44 <artifactId>spring-boot-starter-security</artifactId>
45 45 </dependency>
46 46  
47   - <!-- TODO 芋艿: -->
  47 + <!-- 业务组件 -->
48 48 <dependency>
49   - <groupId>org.activiti</groupId>
50   - <artifactId>activiti-engine</artifactId>
51   - <version>7.1.0.M6</version>
52   - <exclusions>
53   - <exclusion>
54   - <groupId>*</groupId>
55   - <artifactId>*</artifactId>
56   - </exclusion>
57   - </exclusions>
58   - <optional>true</optional>
  49 + <groupId>cn.iocoder.boot</groupId>
  50 + <artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行 Token 的校验 -->
  51 + <version>${revision}</version>
59 52 </dependency>
60 53 </dependencies>
61 54  
... ...
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;
6 6  
7 7 import javax.validation.constraints.NotEmpty;
8 8 import javax.validation.constraints.NotNull;
9   -import java.time.Duration;
10 9  
11 10 @ConfigurationProperties(prefix = "yudao.security")
12 11 @Validated
... ... @@ -18,18 +17,6 @@ public class SecurityProperties {
18 17 */
19 18 @NotEmpty(message = "Token Header 不能为空")
20 19 private String tokenHeader;
21   - /**
22   - * Token 过期时间
23   - */
24   - @NotNull(message = "Token 过期时间不能为空")
25   - private Duration tokenTimeout;
26   - /**
27   - * Session 过期时间
28   - *
29   - * 当 User 用户超过当前时间未操作,则 Session 会过期
30   - */
31   - @NotNull(message = "Session 过期时间不能为空")
32   - private Duration sessionTimeout;
33 20  
34 21 /**
35 22 * mock 模式的开关
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.security.config;
2 2  
3 3 import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect;
4   -import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
5 4 import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
6 5 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
7 6 import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
8 7 import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
9   -import cn.iocoder.yudao.framework.security.core.handler.LogoutSuccessHandlerImpl;
10   -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
11   -import cn.iocoder.yudao.framework.web.config.WebProperties;
12 8 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
  9 +import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
13 10 import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
14 11 import org.springframework.boot.context.properties.EnableConfigurationProperties;
15 12 import org.springframework.context.annotation.Bean;
... ... @@ -19,10 +16,8 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
19 16 import org.springframework.security.crypto.password.PasswordEncoder;
20 17 import org.springframework.security.web.AuthenticationEntryPoint;
21 18 import org.springframework.security.web.access.AccessDeniedHandler;
22   -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
23 19  
24 20 import javax.annotation.Resource;
25   -import java.util.List;
26 21  
27 22 /**
28 23 * Spring Security 自动配置类,主要用于相关组件的配置
... ... @@ -64,14 +59,6 @@ public class YudaoSecurityAutoConfiguration {
64 59 }
65 60  
66 61 /**
67   - * 退出处理类 Bean
68   - */
69   - @Bean
70   - public LogoutSuccessHandler logoutSuccessHandler(MultiUserDetailsAuthenticationProvider authenticationProvider) {
71   - return new LogoutSuccessHandlerImpl(securityProperties, authenticationProvider);
72   - }
73   -
74   - /**
75 62 * Spring Security 加密器
76 63 * 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器
77 64 *
... ... @@ -86,19 +73,9 @@ public class YudaoSecurityAutoConfiguration {
86 73 * Token 认证过滤器 Bean
87 74 */
88 75 @Bean
89   - public TokenAuthenticationFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,
90   - GlobalExceptionHandler globalExceptionHandler) {
91   - return new TokenAuthenticationFilter(securityProperties, authenticationProvider, globalExceptionHandler);
92   - }
93   -
94   - /**
95   - * 身份验证的 Provider Bean,通过它实现账号 + 密码的认证
96   - */
97   - @Bean
98   - public MultiUserDetailsAuthenticationProvider authenticationProvider(
99   - List<SecurityAuthFrameworkService> securityFrameworkServices,
100   - WebProperties webProperties, PasswordEncoder passwordEncoder) {
101   - return new MultiUserDetailsAuthenticationProvider(securityFrameworkServices, webProperties, passwordEncoder);
  76 + public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler,
  77 + OAuth2TokenApi oauth2TokenApi) {
  78 + return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi);
102 79 }
103 80  
104 81 /**
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
1 1 package cn.iocoder.yudao.framework.security.config;
2 2  
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
5 3 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
6 4 import cn.iocoder.yudao.framework.web.config.WebProperties;
7 5 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
... ... @@ -9,7 +7,6 @@ import org.springframework.context.annotation.Bean;
9 7 import org.springframework.context.annotation.Configuration;
10 8 import org.springframework.http.HttpMethod;
11 9 import org.springframework.security.authentication.AuthenticationManager;
12   -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
13 10 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
14 11 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
15 12 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
... ... @@ -17,7 +14,6 @@ import org.springframework.security.config.http.SessionCreationPolicy;
17 14 import org.springframework.security.web.AuthenticationEntryPoint;
18 15 import org.springframework.security.web.access.AccessDeniedHandler;
19 16 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
20   -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
21 17  
22 18 import javax.annotation.Resource;
23 19 import java.util.List;
... ... @@ -34,8 +30,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
34 30 @Resource
35 31 private WebProperties webProperties;
36 32  
37   - @Resource
38   - private MultiUserDetailsAuthenticationProvider authenticationProvider;
39 33 /**
40 34 * 认证失败处理类 Bean
41 35 */
... ... @@ -47,11 +41,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
47 41 @Resource
48 42 private AccessDeniedHandler accessDeniedHandler;
49 43 /**
50   - * 退出处理类 Bean
51   - */
52   - @Resource
53   - private LogoutSuccessHandler logoutSuccessHandler;
54   - /**
55 44 * Token 认证过滤器 Bean
56 45 */
57 46 @Resource
... ... @@ -77,14 +66,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
77 66 }
78 67  
79 68 /**
80   - * 身份认证接口
81   - */
82   - @Override
83   - protected void configure(AuthenticationManagerBuilder auth) throws Exception {
84   - auth.authenticationProvider(authenticationProvider);
85   - }
86   -
87   - /**
88 69 * 配置 URL 的安全配置
89 70 *
90 71 * anyRequest | 匹配所有请求路径
... ... @@ -114,11 +95,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
114 95 .headers().frameOptions().disable().and()
115 96 // 一堆自定义的 Spring Security 处理器
116 97 .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
117   - .accessDeniedHandler(accessDeniedHandler).and()
118   - // 登出地址的配置
119   - .logout().logoutSuccessHandler(logoutSuccessHandler).logoutRequestMatcher(request -> // 匹配多种用户类型的登出
120   - StrUtil.equalsAny(request.getRequestURI(), buildAdminApi("/system/logout"),
121   - buildAppApi("/member/logout")));
  98 + .accessDeniedHandler(accessDeniedHandler);
  99 + // 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高
122 100  
123 101 // 设置每个请求的权限
124 102 httpSecurity
... ... @@ -140,11 +118,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
140 118 // 添加 JWT Filter
141 119 httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
142 120 }
143   -
144   - private String buildAdminApi(String url) {
145   - return webProperties.getAdminApi().getPrefix() + url;
146   - }
147   -
  121 +
148 122 private String buildAppApi(String url) {
149 123 return webProperties.getAppApi().getPrefix() + url;
150 124 }
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java
1 1 package cn.iocoder.yudao.framework.security.core;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
4   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6 5 import com.fasterxml.jackson.annotation.JsonIgnore;
7 6 import lombok.Data;
8   -import org.springframework.security.core.GrantedAuthority;
9   -import org.springframework.security.core.userdetails.UserDetails;
10 7  
11   -import java.util.*;
  8 +import java.util.HashMap;
  9 +import java.util.Map;
12 10  
13 11 /**
14 12 * 登录用户信息
... ... @@ -16,7 +14,7 @@ import java.util.*;
16 14 * @author 芋道源码
17 15 */
18 16 @Data
19   -public class LoginUser implements UserDetails {
  17 +public class LoginUser {
20 18  
21 19 /**
22 20 * 用户编号
... ... @@ -29,38 +27,10 @@ public class LoginUser implements UserDetails {
29 27 */
30 28 private Integer userType;
31 29 /**
32   - * 最后更新时间
33   - */
34   - private Date updateTime;
35   -
36   - /**
37   - * 用户名
38   - */
39   - private String username;
40   - /**
41   - * 密码
42   - */
43   - private String password;
44   - /**
45   - * 状态
46   - */
47   - private Integer status;
48   - /**
49 30 * 租户编号
50 31 */
51 32 private Long tenantId;
52 33  
53   - // ========== UserTypeEnum.ADMIN 独有字段 ==========
54   - // TODO 芋艿:可以通过定义一个 Map<String, String> exts 的方式,去除管理员的字段。不过这样会导致系统比较复杂,所以暂时不去掉先;
55   - /**
56   - * 角色编号数组
57   - */
58   - private Set<Long> roleIds;
59   - /**
60   - * 部门编号
61   - */
62   - private Long deptId;
63   -
64 34 // ========== 上下文 ==========
65 35 /**
66 36 * 上下文字段,不进行持久化
... ... @@ -70,49 +40,6 @@ public class LoginUser implements UserDetails {
70 40 @JsonIgnore
71 41 private Map<String, Object> context;
72 42  
73   - @Override
74   - @JsonIgnore// 避免序列化
75   - public String getPassword() {
76   - return password;
77   - }
78   -
79   - @Override
80   - public String getUsername() {
81   - return username;
82   - }
83   -
84   - @Override
85   - @JsonIgnore// 避免序列化
86   - public boolean isEnabled() {
87   - return CommonStatusEnum.ENABLE.getStatus().equals(status);
88   - }
89   -
90   - @Override
91   - @JsonIgnore// 避免序列化
92   - public Collection<? extends GrantedAuthority> getAuthorities() {
93   - return new HashSet<>();
94   - }
95   -
96   - @Override
97   - @JsonIgnore// 避免序列化
98   - public boolean isAccountNonExpired() {
99   - return true; // 返回 true,不依赖 Spring Security 判断
100   - }
101   -
102   - @Override
103   - @JsonIgnore// 避免序列化
104   - public boolean isAccountNonLocked() {
105   - return true; // 返回 true,不依赖 Spring Security 判断
106   - }
107   -
108   - @Override
109   - @JsonIgnore// 避免序列化
110   - public boolean isCredentialsNonExpired() {
111   - return true; // 返回 true,不依赖 Spring Security 判断
112   - }
113   -
114   - // ========== 上下文 ==========
115   -
116 43 public void setContext(String key, Object value) {
117 44 if (context == null) {
118 45 context = new HashMap<>();
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java deleted
1   -package cn.iocoder.yudao.framework.security.core.authentication;
2   -
3   -import cn.hutool.core.lang.Assert;
4   -import cn.hutool.core.util.StrUtil;
5   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6   -import cn.iocoder.yudao.framework.security.core.LoginUser;
7   -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
8   -import cn.iocoder.yudao.framework.web.config.WebProperties;
9   -import org.springframework.security.authentication.AuthenticationProvider;
10   -import org.springframework.security.authentication.BadCredentialsException;
11   -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
12   -import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
13   -import org.springframework.security.core.AuthenticationException;
14   -import org.springframework.security.core.userdetails.UserDetails;
15   -import org.springframework.security.crypto.password.PasswordEncoder;
16   -
17   -import javax.servlet.http.HttpServletRequest;
18   -import java.util.HashMap;
19   -import java.util.List;
20   -import java.util.Map;
21   -
22   -/**
23   - * 支持多用户类型的 AuthenticationProvider 实现类
24   - *
25   - * 为什么不用 {@link org.springframework.security.authentication.ProviderManager} 呢?
26   - * 原因是,需要每个用户类型实现对应的 {@link AuthenticationProvider} + authentication,略显麻烦。实际,也是可以实现的。
27   - *
28   - * 另外,额外支持 verifyTokenAndRefresh 校验令牌、logout 登出、mockLogin 模拟登陆等操作。
29   - * 实际上,它就是 {@link SecurityAuthFrameworkService} 定义的三个接口。
30   - * 因为需要支持多种类型,所以需要根据请求的 URL,判断出对应的用户类型,从而使用对应的 SecurityAuthFrameworkService 是吸纳
31   - *
32   - * @see cn.iocoder.yudao.framework.common.enums.UserTypeEnum
33   - * @author 芋道源码
34   - */
35   -public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
36   -
37   - private final Map<UserTypeEnum, SecurityAuthFrameworkService> services = new HashMap<>();
38   -
39   - private final WebProperties properties;
40   -
41   - private final PasswordEncoder passwordEncoder;
42   -
43   - public MultiUserDetailsAuthenticationProvider(List<SecurityAuthFrameworkService> serviceList,
44   - WebProperties properties, PasswordEncoder passwordEncoder) {
45   - serviceList.forEach(service -> services.put(service.getUserType(), service));
46   - this.properties = properties;
47   - this.passwordEncoder = passwordEncoder;
48   - }
49   -
50   - // ========== AuthenticationProvider 相关 ==========
51   -
52   - @Override
53   - protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
54   - throws AuthenticationException {
55   - // 执行用户的加载
56   - return selectService(authentication).loadUserByUsername(username);
57   - }
58   -
59   - private SecurityAuthFrameworkService selectService(UsernamePasswordAuthenticationToken authentication) {
60   - // 第一步,获得用户类型
61   - UserTypeEnum userType = getUserType(authentication);
62   - // 第二步,获得 SecurityAuthFrameworkService
63   - SecurityAuthFrameworkService service = services.get(userType);
64   - Assert.notNull(service, "用户类型({}) 找不到 SecurityAuthFrameworkService 实现类", userType);
65   - return service;
66   - }
67   -
68   - private UserTypeEnum getUserType(UsernamePasswordAuthenticationToken authentication) {
69   - Assert.isInstanceOf(MultiUsernamePasswordAuthenticationToken.class, authentication);
70   - MultiUsernamePasswordAuthenticationToken multiAuthentication = (MultiUsernamePasswordAuthenticationToken) authentication;
71   - UserTypeEnum userType = multiAuthentication.getUserType();
72   - Assert.notNull(userType, "用户类型不能为空");
73   - return userType;
74   - }
75   -
76   - @Override // copy 自 DaoAuthenticationProvider 的 additionalAuthenticationChecks 方法
77   - protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
78   - throws AuthenticationException {
79   - // 校验 credentials
80   - if (authentication.getCredentials() == null) {
81   - this.logger.debug("Failed to authenticate since no credentials provided");
82   - throw new BadCredentialsException(this.messages.getMessage(
83   - "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
84   - }
85   - // 校验 password
86   - String presentedPassword = authentication.getCredentials().toString();
87   - if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
88   - this.logger.debug("Failed to authenticate since password does not match stored value");
89   - throw new BadCredentialsException(this.messages.getMessage(
90   - "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
91   - }
92   - }
93   -
94   - // ========== SecurityAuthFrameworkService 相关 ==========
95   -
96   - /**
97   - * 校验 token 的有效性,并获取用户信息
98   - * 通过后,刷新 token 的过期时间
99   - *
100   - * @param request 请求
101   - * @param token token
102   - * @return 用户信息
103   - */
104   - public LoginUser verifyTokenAndRefresh(HttpServletRequest request, String token) {
105   - return selectService(request).verifyTokenAndRefresh(token);
106   - }
107   -
108   - /**
109   - * 模拟指定用户编号的 LoginUser
110   - *
111   - * @param request 请求
112   - * @param userId 用户编号
113   - * @return 登录用户
114   - */
115   - public LoginUser mockLogin(HttpServletRequest request, Long userId) {
116   - return selectService(request).mockLogin(userId);
117   - }
118   -
119   - /**
120   - * 基于 token 退出登录
121   - *
122   - * @param request 请求
123   - * @param token token
124   - */
125   - public void logout(HttpServletRequest request, String token) {
126   - selectService(request).logout(token);
127   - }
128   -
129   - private SecurityAuthFrameworkService selectService(HttpServletRequest request) {
130   - // 第一步,获得用户类型
131   - UserTypeEnum userType = getUserType(request);
132   - // 第二步,获得 SecurityAuthFrameworkService
133   - SecurityAuthFrameworkService service = services.get(userType);
134   - Assert.notNull(service, "URI({}) 用户类型({}) 找不到 SecurityAuthFrameworkService 实现类",
135   - request.getRequestURI(), userType);
136   - return service;
137   - }
138   -
139   - private UserTypeEnum getUserType(HttpServletRequest request) {
140   - if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) {
141   - return UserTypeEnum.ADMIN;
142   - }
143   - if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) {
144   - return UserTypeEnum.MEMBER;
145   - }
146   - throw new IllegalArgumentException(StrUtil.format("URI({}) 找不到匹配的用户类型", request.getRequestURI()));
147   - }
148   -
149   -}
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java deleted
1   -package cn.iocoder.yudao.framework.security.core.authentication;
2   -
3   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4   -import lombok.Getter;
5   -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
6   -import org.springframework.security.core.GrantedAuthority;
7   -
8   -import java.util.Collection;
9   -
10   -/**
11   - * 支持多用户的 UsernamePasswordAuthenticationToken 实现类
12   - *
13   - * @author 芋道源码
14   - */
15   -@Getter
16   -public class MultiUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
17   -
18   - /**
19   - * 用户类型
20   - */
21   - private UserTypeEnum userType;
22   -
23   - public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials) {
24   - super(principal, credentials);
25   - }
26   -
27   - public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
28   - Collection<? extends GrantedAuthority> authorities) {
29   - super(principal, credentials, authorities);
30   - }
31   -
32   - public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials, UserTypeEnum userType) {
33   - super(principal, credentials);
34   - this.userType = userType;
35   - }
36   -
37   - public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
38   - Collection<? extends GrantedAuthority> authorities, UserTypeEnum userType) {
39   - super(principal, credentials, authorities);
40   - this.userType = userType;
41   - }
42   -
43   -}
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
1 1 package cn.iocoder.yudao.framework.security.core.filter;
2 2  
  3 +import cn.hutool.core.util.ObjectUtil;
3 4 import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
4 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 7 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
6 8 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
7 9 import cn.iocoder.yudao.framework.security.core.LoginUser;
8   -import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
9 10 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
10 11 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
  12 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  13 +import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
  14 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
11 15 import lombok.RequiredArgsConstructor;
  16 +import org.springframework.security.access.AccessDeniedException;
12 17 import org.springframework.web.filter.OncePerRequestFilter;
13 18  
14 19 import javax.servlet.FilterChain;
... ... @@ -28,24 +33,26 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
28 33  
29 34 private final SecurityProperties securityProperties;
30 35  
31   - private final MultiUserDetailsAuthenticationProvider authenticationProvider;
32   -
33 36 private final GlobalExceptionHandler globalExceptionHandler;
34 37  
  38 + private final OAuth2TokenApi oauth2TokenApi;
  39 +
35 40 @Override
36 41 @SuppressWarnings("NullableProblems")
37 42 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
38 43 throws ServletException, IOException {
39 44 String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
40 45 if (StrUtil.isNotEmpty(token)) {
  46 + Integer userType = WebFrameworkUtils.getLoginUserType(request);
41 47 try {
42   - // 验证 token 有效性
43   - LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token);
44   - // 模拟 Login 功能,方便日常开发调试
  48 + // 1.1 基于 token 构建登录用户
  49 + LoginUser loginUser = buildLoginUserByToken(token, userType);
  50 + // 1.2 模拟 Login 功能,方便日常开发调试
45 51 if (loginUser == null) {
46   - loginUser = mockLoginUser(request, token);
  52 + loginUser = mockLoginUser(request, token, userType);
47 53 }
48   - // 设置当前用户
  54 +
  55 + // 2. 设置当前用户
49 56 if (loginUser != null) {
50 57 SecurityFrameworkUtils.setLoginUser(loginUser, request);
51 58 }
... ... @@ -60,6 +67,25 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
60 67 chain.doFilter(request, response);
61 68 }
62 69  
  70 + private LoginUser buildLoginUserByToken(String token, Integer userType) {
  71 + try {
  72 + OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token);
  73 + if (accessToken == null) {
  74 + return null;
  75 + }
  76 + // 用户类型不匹配,无权限
  77 + if (ObjectUtil.notEqual(accessToken.getUserType(), userType)) {
  78 + throw new AccessDeniedException("错误的用户类型");
  79 + }
  80 + // 构建登录用户
  81 + return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
  82 + .setTenantId(accessToken.getTenantId());
  83 + } catch (ServiceException serviceException) {
  84 + // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
  85 + return null;
  86 + }
  87 + }
  88 +
63 89 /**
64 90 * 模拟登录用户,方便日常开发调试
65 91 *
... ... @@ -67,9 +93,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
67 93 *
68 94 * @param request 请求
69 95 * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
  96 + * @param userType 用户类型
70 97 * @return 模拟的 LoginUser
71 98 */
72   - private LoginUser mockLoginUser(HttpServletRequest request, String token) {
  99 + private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) {
73 100 if (!securityProperties.getMockEnable()) {
74 101 return null;
75 102 }
... ... @@ -77,8 +104,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
77 104 if (!token.startsWith(securityProperties.getMockSecret())) {
78 105 return null;
79 106 }
  107 + // 构建模拟用户
80 108 Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
81   - return authenticationProvider.mockLogin(request, userId);
  109 + return new LoginUser().setId(userId).setUserType(userType)
  110 + .setTenantId(WebFrameworkUtils.getTenantId(request));
82 111 }
83 112  
84 113 }
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java deleted
1   -package cn.iocoder.yudao.framework.security.core.handler;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5   -import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
6   -import cn.iocoder.yudao.framework.security.config.SecurityProperties;
7   -import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
8   -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
9   -import lombok.AllArgsConstructor;
10   -import org.springframework.security.core.Authentication;
11   -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
12   -
13   -import javax.servlet.http.HttpServletRequest;
14   -import javax.servlet.http.HttpServletResponse;
15   -
16   -
17   -/**
18   - * 自定义退出处理器
19   - *
20   - * @author ruoyi
21   - */
22   -@AllArgsConstructor
23   -public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
24   -
25   - private final SecurityProperties securityProperties;
26   -
27   - private final MultiUserDetailsAuthenticationProvider authenticationProvider;
28   -
29   - @Override
30   - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
31   - // 执行退出
32   - String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
33   - if (StrUtil.isNotBlank(token)) {
34   - authenticationProvider.logout(request, token);
35   - }
36   - // 返回成功
37   - ServletUtils.writeJSON(response, CommonResult.success(null));
38   - }
39   -
40   -}
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java deleted
1   -package cn.iocoder.yudao.framework.security.core.service;
2   -
3   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4   -import cn.iocoder.yudao.framework.security.core.LoginUser;
5   -import org.springframework.security.core.userdetails.UserDetailsService;
6   -
7   -/**
8   - * Security 框架 Auth Service 接口,定义不同用户类型的 {@link UserTypeEnum} 需要实现的方法
9   - *
10   - * @author 芋道源码
11   - */
12   -public interface SecurityAuthFrameworkService extends UserDetailsService {
13   -
14   - /**
15   - * 校验 token 的有效性,并获取用户信息
16   - * 通过后,刷新 token 的过期时间
17   - *
18   - * @param token token
19   - * @return 用户信息
20   - */
21   - LoginUser verifyTokenAndRefresh(String token);
22   -
23   - /**
24   - * 模拟指定用户编号的 LoginUser
25   - *
26   - * @param userId 用户编号
27   - * @return 登录用户
28   - */
29   - LoginUser mockLogin(Long userId);
30   -
31   - /**
32   - * 基于 token 退出登录
33   - *
34   - * @param token token
35   - */
36   - void logout(String token);
37   -
38   - /**
39   - * 获得用户类型。每个用户类型,对应一个 SecurityAuthFrameworkService 实现类。
40   - *
41   - * @return 用户类型
42   - */
43   - UserTypeEnum getUserType();
44   -
45   -}
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
11 11 import org.springframework.util.StringUtils;
12 12  
13 13 import javax.servlet.http.HttpServletRequest;
14   -import java.util.Set;
  14 +import java.util.Collections;
15 15  
16 16 /**
17 17 * 安全服务工具类
... ... @@ -80,17 +80,6 @@ public class SecurityFrameworkUtils {
80 80 }
81 81  
82 82 /**
83   - * 获得当前用户的角色编号数组
84   - *
85   - * @return 角色编号数组
86   - */
87   - @Nullable
88   - public static Set<Long> getLoginUserRoleIds() {
89   - LoginUser loginUser = getLoginUser();
90   - return loginUser != null ? loginUser.getRoleIds() : null;
91   - }
92   -
93   - /**
94 83 * 设置当前用户
95 84 *
96 85 * @param loginUser 登录用户
... ... @@ -110,7 +99,7 @@ public class SecurityFrameworkUtils {
110 99 private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
111 100 // 创建 UsernamePasswordAuthenticationToken 对象
112 101 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
113   - loginUser, null, loginUser.getAuthorities());
  102 + loginUser, null, Collections.emptyList());
114 103 authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
115 104 return authenticationToken;
116 105 }
... ...
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;
7 7 import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
8 8 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
9 9 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
  10 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
10 11 import org.springframework.beans.factory.annotation.Value;
11 12 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
12 13 import org.springframework.boot.context.properties.EnableConfigurationProperties;
... ... @@ -65,6 +66,13 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
65 66 return new GlobalResponseBodyHandler();
66 67 }
67 68  
  69 + @Bean
  70 + @SuppressWarnings("InstantiationOfUtilityClass")
  71 + public WebFrameworkUtils webFrameworkUtils(WebProperties webProperties) {
  72 + // 由于 WebFrameworkUtils 需要使用到 webProperties 属性,所以注册为一个 Bean
  73 + return new WebFrameworkUtils(webProperties);
  74 + }
  75 +
68 76 // ========== Filter 相关 ==========
69 77  
70 78 /**
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java
1 1 package cn.iocoder.yudao.framework.web.core.util;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.web.config.WebProperties;
5 7 import org.springframework.web.context.request.RequestAttributes;
6 8 import org.springframework.web.context.request.RequestContextHolder;
7 9 import org.springframework.web.context.request.ServletRequestAttributes;
... ... @@ -21,16 +23,43 @@ public class WebFrameworkUtils {
21 23  
22 24 private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result";
23 25  
  26 + private static final String HEADER_TENANT_ID = "tenant-id";
  27 +
  28 + private static WebProperties properties;
  29 +
  30 + public WebFrameworkUtils(WebProperties webProperties) {
  31 + WebFrameworkUtils.properties = webProperties;
  32 + }
  33 +
  34 + /**
  35 + * 获得租户编号,从 header 中
  36 + * 考虑到其它 framework 组件也会使用到租户编号,所以不得不放在 WebFrameworkUtils 统一提供
  37 + *
  38 + * @param request 请求
  39 + * @return 租户编号
  40 + */
  41 + public static Long getTenantId(HttpServletRequest request) {
  42 + String tenantId = request.getHeader(HEADER_TENANT_ID);
  43 + return StrUtil.isNotEmpty(tenantId) ? Long.valueOf(tenantId) : null;
  44 + }
  45 +
24 46 public static void setLoginUserId(ServletRequest request, Long userId) {
25 47 request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId);
26 48 }
27 49  
  50 + /**
  51 + * 设置用户类型
  52 + *
  53 + * @param request 请求
  54 + * @param userType 用户类型
  55 + */
28 56 public static void setLoginUserType(ServletRequest request, Integer userType) {
29 57 request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType);
30 58 }
31 59  
32 60 /**
33 61 * 获得当前用户的编号,从请求中
  62 + * 注意:该方法仅限于 framework 框架使用!!!
34 63 *
35 64 * @param request 请求
36 65 * @return 用户编号
... ... @@ -43,7 +72,8 @@ public class WebFrameworkUtils {
43 72 }
44 73  
45 74 /**
46   - * 获得当前用户的类型,从请求中
  75 + * 获得当前用户的类型
  76 + * 注意:该方法仅限于 web 相关的 framework 组件使用!!!
47 77 *
48 78 * @param request 请求
49 79 * @return 用户编号
... ... @@ -52,7 +82,19 @@ public class WebFrameworkUtils {
52 82 if (request == null) {
53 83 return null;
54 84 }
55   - return (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);
  85 + // 1. 优先,从 Attribute 中获取
  86 + Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);
  87 + if (userType != null) {
  88 + return userType;
  89 + }
  90 + // 2. 其次,基于 URL 前缀的约定
  91 + if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) {
  92 + return UserTypeEnum.ADMIN.getValue();
  93 + }
  94 + if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) {
  95 + return UserTypeEnum.MEMBER.getValue();
  96 + }
  97 + return null;
56 98 }
57 99  
58 100 public static Integer getLoginUserType() {
... ...
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;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
  7 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
7 8 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
8 9 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO;
9 10 import cn.iocoder.yudao.module.infra.convert.file.FileConvert;
... ... @@ -42,8 +43,9 @@ public class FileController {
42 43 @ApiImplicitParam(name = "file", value = "文件附件", required = true, dataTypeClass = MultipartFile.class),
43 44 @ApiImplicitParam(name = "path", value = "文件路径", example = "yudaoyuanma.png", dataTypeClass = String.class)
44 45 })
  46 + @OperateLog(logArgs = false) // 上传文件,没有记录操作日志的必要
45 47 public CommonResult<String> uploadFile(@RequestParam("file") MultipartFile file,
46   - @RequestParam("path") String path) throws Exception {
  48 + @RequestParam(value = "path", required = false) String path) throws Exception {
47 49 return success(fileService.createFile(path, IoUtil.readBytes(file.getInputStream())));
48 50 }
49 51  
... ...
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;
2 2  
3 3 import cn.hutool.core.io.FileTypeUtil;
4 4 import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.crypto.digest.DigestUtil;
5 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 8 import cn.iocoder.yudao.framework.file.core.client.FileClient;
7 9 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
... ... @@ -36,6 +38,12 @@ public class FileServiceImpl implements FileService {
36 38  
37 39 @Override
38 40 public String createFile(String path, byte[] content) throws Exception {
  41 + // 计算默认的 path 名
  42 + String type = FileTypeUtil.getType(new ByteArrayInputStream(content));
  43 + if (StrUtil.isEmpty(path)) {
  44 + path = DigestUtil.md5Hex(content) + '.' + type;
  45 + }
  46 +
39 47 // 上传到文件存储器
40 48 FileClient client = fileConfigService.getMasterFileClient();
41 49 Assert.notNull(client, "客户端(master) 不能为空");
... ... @@ -46,7 +54,7 @@ public class FileServiceImpl implements FileService {
46 54 file.setConfigId(client.getId());
47 55 file.setPath(path);
48 56 file.setUrl(url);
49   - file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content)));
  57 + file.setType(type);
50 58 file.setSize(content.length);
51 59 fileMapper.insert(file);
52 60 return url;
... ...
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 {
17 17 // ========== AUTH 模块 1004003000 ==========
18 18 ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");
19 19 ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用");
20   - ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1004003002, "登录失败"); // 登录失败的兜底,未知原因
21 20 ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期");
22 21 ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定");
23 22  
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
1 1 ### 请求 /login 接口 => 成功
2   -POST {{appApi}}/member/login
  2 +POST {{appApi}}/member/auth/login
3 3 Content-Type: application/json
4 4 tenant-id: {{appTenentId}}
5 5  
... ... @@ -19,7 +19,7 @@ tenant-id: {{appTenentId}}
19 19 }
20 20  
21 21 ### 请求 /sms-login 接口 => 成功
22   -POST {{appApi}}/member/sms-login
  22 +POST {{appApi}}/member/auth/sms-login
23 23 Content-Type: application/json
24 24 tenant-id: {{appTenentId}}
25 25  
... ... @@ -29,7 +29,12 @@ tenant-id: {{appTenentId}}
29 29 }
30 30  
31 31 ### 请求 /logout 接口 => 成功
32   -POST {{appApi}}/member/logout
  32 +POST {{appApi}}/member/auth/logout
33 33 Content-Type: application/json
34 34 Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66
35 35 tenant-id: {{appTenentId}}
  36 +
  37 +### 请求 /auth/refresh-token 接口 => 成功
  38 +POST {{appApi}}/member/auth/refresh-token?refreshToken=bc43d929094849a28b3a69f6e6940d70
  39 +Content-Type: application/json
  40 +tenant-id: {{appTenentId}}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
1 1 package cn.iocoder.yudao.module.member.controller.app.auth;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  6 +import cn.iocoder.yudao.framework.security.config.SecurityProperties;
4 7 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
  8 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
5 9 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
6 10 import cn.iocoder.yudao.module.member.service.auth.MemberAuthService;
7 11 import io.swagger.annotations.Api;
... ... @@ -13,11 +17,10 @@ import org.springframework.validation.annotation.Validated;
13 17 import org.springframework.web.bind.annotation.*;
14 18  
15 19 import javax.annotation.Resource;
  20 +import javax.servlet.http.HttpServletRequest;
16 21 import javax.validation.Valid;
17 22  
18 23 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
19   -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
20   -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
21 24 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
22 25  
23 26 @Api(tags = "用户 APP - 认证")
... ... @@ -30,19 +33,39 @@ public class AppAuthController {
30 33 @Resource
31 34 private MemberAuthService authService;
32 35  
  36 + @Resource
  37 + private SecurityProperties securityProperties;
  38 +
33 39 @PostMapping("/login")
34 40 @ApiOperation("使用手机 + 密码登录")
35 41 public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
36   - String token = authService.login(reqVO, getClientIP(), getUserAgent());
37   - return success(AppAuthLoginRespVO.builder().token(token).build());
  42 + return success(authService.login(reqVO));
  43 + }
  44 +
  45 + @PostMapping("/logout")
  46 + @ApiOperation("登出系统")
  47 + public CommonResult<Boolean> logout(HttpServletRequest request) {
  48 + String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
  49 + if (StrUtil.isNotBlank(token)) {
  50 + authService.logout(token);
  51 + }
  52 + return success(true);
  53 + }
  54 +
  55 + @PostMapping("/refresh-token")
  56 + @ApiOperation("刷新令牌")
  57 + @ApiImplicitParam(name = "refreshToken", value = "刷新令牌", required = true, dataTypeClass = String.class)
  58 + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
  59 + public CommonResult<AppAuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
  60 + return success(authService.refreshToken(refreshToken));
38 61 }
39 62  
  63 + // ========== 短信登录相关 ==========
  64 +
40 65 @PostMapping("/sms-login")
41 66 @ApiOperation("使用手机 + 验证码登录")
42 67 public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
43   - String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
44   - // 返回结果
45   - return success(AppAuthLoginRespVO.builder().token(token).build());
  68 + return success(authService.smsLogin(reqVO));
46 69 }
47 70  
48 71 @PostMapping("/send-sms-code")
... ... @@ -83,16 +106,14 @@ public class AppAuthController {
83 106  
84 107 @PostMapping("/social-quick-login")
85 108 @ApiOperation(value = "社交快捷登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户")
86   - public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialQuickLoginReqVO reqVO) {
87   - String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent());
88   - return success(AppAuthLoginRespVO.builder().token(token).build());
  109 + public CommonResult<AppAuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AppAuthSocialQuickLoginReqVO reqVO) {
  110 + return success(authService.socialQuickLogin(reqVO));
89 111 }
90 112  
91 113 @PostMapping("/social-bind-login")
92 114 @ApiOperation(value = "社交绑定登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定")
93   - public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialBindLoginReqVO reqVO) {
94   - String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent());
95   - return success(AppAuthLoginRespVO.builder().token(token).build());
  115 + public CommonResult<AppAuthLoginRespVO> socialBindLogin(@RequestBody @Valid AppAuthSocialBindLoginReqVO reqVO) {
  116 + return success(authService.socialBindLogin(reqVO));
96 117 }
97 118  
98 119 }
... ...
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;
7 7 import lombok.Data;
8 8 import lombok.NoArgsConstructor;
9 9  
10   -@ApiModel("用户 APP - 手机密码登录 Response VO")
  10 +import java.util.Date;
  11 +
  12 +@ApiModel("用户 APP - 登录 Response VO")
11 13 @Data
12 14 @NoArgsConstructor
13 15 @AllArgsConstructor
14 16 @Builder
15 17 public class AppAuthLoginRespVO {
16 18  
17   - @ApiModelProperty(value = "token", required = true, example = "yudaoyuanma")
18   - private String token;
  19 + @ApiModelProperty(value = "用户编号", required = true, example = "1024")
  20 + private Long userId;
  21 +
  22 + @ApiModelProperty(value = "访问令牌", required = true, example = "happy")
  23 + private String accessToken;
  24 +
  25 + @ApiModelProperty(value = "刷新令牌", required = true, example = "nice")
  26 + private String refreshToken;
  27 +
  28 + @ApiModelProperty(value = "过期时间", required = true)
  29 + private Date expiresTime;
19 30  
20 31 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
1 1 package cn.iocoder.yudao.module.member.convert.auth;
2 2  
3   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4   -import cn.iocoder.yudao.framework.security.core.LoginUser;
5 3 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
6 4 import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
7   -import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
  5 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
8 6 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
9 7 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
10 8 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
11 9 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
12 10 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
13 11 import org.mapstruct.Mapper;
14   -import org.mapstruct.Mapping;
15 12 import org.mapstruct.factory.Mappers;
16 13  
17 14 @Mapper
... ... @@ -19,14 +16,6 @@ public interface AuthConvert {
19 16  
20 17 AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
21 18  
22   - @Mapping(source = "mobile", target = "username")
23   - LoginUser convert0(MemberUserDO bean);
24   -
25   - default LoginUser convert(MemberUserDO bean) {
26   - // 目的,为了设置 UserTypeEnum.MEMBER.getValue()
27   - return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue());
28   - }
29   -
30 19 SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO);
31 20 SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO);
32 21 SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
... ... @@ -35,4 +24,6 @@ public interface AuthConvert {
35 24 SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
36 25 SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
37 26  
  27 + AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean);
  28 +
38 29 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java 0 → 100644
  1 +/**
  2 + * 属于 system 模块的 framework 封装
  3 + *
  4 + * @author 芋道源码
  5 + */
  6 +package cn.iocoder.yudao.module.member.framework;
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/config/SecurityConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.framework.security.config;
  2 +
  3 +import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
  4 +import org.springframework.context.annotation.Bean;
  5 +import org.springframework.context.annotation.Configuration;
  6 +import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  7 +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
  8 +
  9 +/**
  10 + * Member 模块的 Security 配置
  11 + */
  12 +@Configuration("memberSecurityConfiguration")
  13 +public class SecurityConfiguration {
  14 +
  15 + @Bean("memberAuthorizeRequestsCustomizer")
  16 + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
  17 + return new AuthorizeRequestsCustomizer() {
  18 +
  19 + @Override
  20 + public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
  21 + // 登录的接口
  22 + registry.antMatchers(buildAdminApi("/member/auth/logout")).permitAll();
  23 + }
  24 +
  25 + };
  26 + }
  27 +
  28 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/core/package-info.java 0 → 100644
  1 +/**
  2 + * 占位
  3 + */
  4 +package cn.iocoder.yudao.module.member.framework.security.core;
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java
1 1 package cn.iocoder.yudao.module.member.service.auth;
2 2  
3   -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
4 3 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
5 4  
6 5 import javax.validation.Valid;
... ... @@ -12,47 +11,46 @@ import javax.validation.Valid;
12 11 *
13 12 * @author 芋道源码
14 13 */
15   -public interface MemberAuthService extends SecurityAuthFrameworkService {
  14 +public interface MemberAuthService {
16 15  
17 16 /**
18 17 * 手机 + 密码登录
19 18 *
20 19 * @param reqVO 登录信息
21   - * @param userIp 用户 IP
22   - * @param userAgent 用户 UA
23   - * @return 身份令牌,使用 JWT 方式
  20 + * @return 登录结果
24 21 */
25   - String login(@Valid AppAuthLoginReqVO reqVO, String userIp, String userAgent);
  22 + AppAuthLoginRespVO login(@Valid AppAuthLoginReqVO reqVO);
  23 +
  24 + /**
  25 + * 基于 token 退出登录
  26 + *
  27 + * @param token token
  28 + */
  29 + void logout(String token);
26 30  
27 31 /**
28 32 * 手机 + 验证码登陆
29 33 *
30 34 * @param reqVO 登陆信息
31   - * @param userIp 用户 IP
32   - * @param userAgent 用户 UA
33   - * @return 身份令牌,使用 JWT 方式
  35 + * @return 登录结果
34 36 */
35   - String smsLogin(@Valid AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent);
  37 + AppAuthLoginRespVO smsLogin(@Valid AppAuthSmsLoginReqVO reqVO);
36 38  
37 39 /**
38 40 * 社交登录,使用 code 授权码
39 41 *
40 42 * @param reqVO 登录信息
41   - * @param userIp 用户 IP
42   - * @param userAgent 用户 UA
43   - * @return 身份令牌,使用 JWT 方式
  43 + * @return 登录结果
44 44 */
45   - String socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
  45 + AppAuthLoginRespVO socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO);
46 46  
47 47 /**
48 48 * 社交登录,使用 手机号 + 手机验证码
49 49 *
50 50 * @param reqVO 登录信息
51   - * @param userIp 用户 IP
52   - * @param userAgent 用户 UA
53   - * @return 身份令牌,使用 JWT 方式
  51 + * @return 登录结果
54 52 */
55   - String socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
  53 + AppAuthLoginRespVO socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO);
56 54  
57 55 /**
58 56 * 获得社交认证 URL
... ... @@ -84,4 +82,11 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
84 82 */
85 83 void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO);
86 84  
  85 + /**
  86 + * 刷新访问令牌
  87 + *
  88 + * @param refreshToken 刷新令牌
  89 + * @return 登录结果
  90 + */
  91 + AppAuthLoginRespVO refreshToken(String refreshToken);
87 92 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
1 1 package cn.iocoder.yudao.module.member.service.auth;
2 2  
3 3 import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.ObjectUtil;
4 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 6 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6 7 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
7 8 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
8   -import cn.iocoder.yudao.framework.security.core.LoginUser;
9   -import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
10 9 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
11 10 import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
12 11 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
13 12 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
14 13 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
15   -import cn.iocoder.yudao.module.system.api.auth.UserSessionApi;
  14 +import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
  15 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
  16 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
16 17 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
17 18 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
18 19 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
19 20 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
  21 +import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientIdEnum;
20 22 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
21 23 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
22 24 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
23 25 import com.google.common.annotations.VisibleForTesting;
24 26 import lombok.extern.slf4j.Slf4j;
25   -import org.springframework.context.annotation.Lazy;
26   -import org.springframework.security.authentication.AuthenticationManager;
27   -import org.springframework.security.authentication.BadCredentialsException;
28   -import org.springframework.security.authentication.DisabledException;
29   -import org.springframework.security.core.Authentication;
30   -import org.springframework.security.core.AuthenticationException;
31   -import org.springframework.security.core.userdetails.UserDetails;
32   -import org.springframework.security.core.userdetails.UsernameNotFoundException;
33 27 import org.springframework.security.crypto.password.PasswordEncoder;
34 28 import org.springframework.stereotype.Service;
35 29 import org.springframework.transaction.annotation.Transactional;
... ... @@ -51,19 +45,15 @@ import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
51 45 public class MemberAuthServiceImpl implements MemberAuthService {
52 46  
53 47 @Resource
54   - @Lazy // 延迟加载,因为存在相互依赖的问题
55   - private AuthenticationManager authenticationManager;
56   -
57   - @Resource
58 48 private MemberUserService userService;
59 49 @Resource
60 50 private SmsCodeApi smsCodeApi;
61 51 @Resource
62 52 private LoginLogApi loginLogApi;
63 53 @Resource
64   - private UserSessionApi userSessionApi;
65   - @Resource
66 54 private SocialUserApi socialUserApi;
  55 + @Resource
  56 + private OAuth2TokenApi oauth2TokenApi;
67 57  
68 58 @Resource
69 59 private PasswordEncoder passwordEncoder;
... ... @@ -71,44 +61,31 @@ public class MemberAuthServiceImpl implements MemberAuthService {
71 61 private MemberUserMapper userMapper;
72 62  
73 63 @Override
74   - public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
75   - // 获取 username 对应的 SysUserDO
76   - MemberUserDO user = userService.getUserByMobile(mobile);
77   - if (user == null) {
78   - throw new UsernameNotFoundException(mobile);
79   - }
80   - // 创建 LoginUser 对象
81   - return AuthConvert.INSTANCE.convert(user);
82   - }
83   -
84   - @Override
85   - public String login(AppAuthLoginReqVO reqVO, String userIp, String userAgent) {
  64 + public AppAuthLoginRespVO login(AppAuthLoginReqVO reqVO) {
86 65 // 使用手机 + 密码,进行登录。
87   - LoginUser loginUser = this.login0(reqVO.getMobile(), reqVO.getPassword());
  66 + MemberUserDO user = login0(reqVO.getMobile(), reqVO.getPassword());
88 67  
89   - // 缓存登录用户到 Redis 中,返回 Token 令牌
90   - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
  68 + // 创建 Token 令牌,记录登录日志
  69 + return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
91 70 }
92 71  
93 72 @Override
94 73 @Transactional
95   - public String smsLogin(AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
  74 + public AppAuthLoginRespVO smsLogin(AppAuthSmsLoginReqVO reqVO) {
96 75 // 校验验证码
  76 + String userIp = getClientIP();
97 77 smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));
98 78  
99 79 // 获得获得注册用户
100 80 MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp);
101 81 Assert.notNull(user, "获取用户失败,结果为空");
102 82  
103   - // 执行登陆
104   - LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
105   -
106   - // 缓存登录用户到 Redis 中,返回 Token 令牌
107   - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SMS, userIp, userAgent);
  83 + // 创建 Token 令牌,记录登录日志
  84 + return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS);
108 85 }
109 86  
110 87 @Override
111   - public String socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) {
  88 + public AppAuthLoginRespVO socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO) {
112 89 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
113 90 Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
114 91 reqVO.getCode(), reqVO.getState());
... ... @@ -122,31 +99,30 @@ public class MemberAuthServiceImpl implements MemberAuthService {
122 99 throw exception(USER_NOT_EXISTS);
123 100 }
124 101  
125   - // 创建 LoginUser 对象
126   - LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
127   -
128   - // 缓存登录用户到 Redis 中,返回 Token 令牌
129   - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
  102 + // 创建 Token 令牌,记录登录日志
  103 + return createTokenAfterLoginSuccess(user, null, LoginLogTypeEnum.LOGIN_SOCIAL);
130 104 }
131 105  
132 106 @Override
133   - public String socialBindLogin(AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) {
  107 + public AppAuthLoginRespVO socialBindLogin(AppAuthSocialBindLoginReqVO reqVO) {
134 108 // 使用手机号、手机验证码登录
135 109 AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
136 110 .mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build();
137   - String token = this.smsLogin(loginReqVO, userIp, userAgent);
138   - LoginUser loginUser = userSessionApi.getLoginUser(token);
  111 + AppAuthLoginRespVO token = smsLogin(loginReqVO);
139 112  
140 113 // 绑定社交用户
141   - socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
  114 + socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(token.getUserId(), getUserType().getValue(), reqVO));
142 115 return token;
143 116 }
144 117  
145   - private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) {
  118 + private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) {
146 119 // 插入登陆日志
147   - createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS);
148   - // 缓存登录用户到 Redis 中,返回 Token 令牌
149   - return userSessionApi.createUserSession(loginUser, userIp, userAgent);
  120 + createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
  121 + // 创建 Token 令牌
  122 + OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
  123 + .setUserId(user.getId()).setUserType(getUserType().getValue()).setClientId(OAuth2ClientIdEnum.DEFAULT.getId()));
  124 + // 构建返回结果
  125 + return AuthConvert.INSTANCE.convert(accessTokenRespDTO);
150 126 }
151 127  
152 128 @Override
... ... @@ -154,40 +130,32 @@ public class MemberAuthServiceImpl implements MemberAuthService {
154 130 return socialUserApi.getAuthorizeUrl(type, redirectUri);
155 131 }
156 132  
157   - private LoginUser login0(String username, String password) {
158   - final LoginLogTypeEnum logType = LoginLogTypeEnum.LOGIN_USERNAME;
159   - // 用户验证
160   - Authentication authentication;
161   - try {
162   - // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
163   - // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
164   - authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
165   - username, password, getUserType()));
166   - } catch (BadCredentialsException badCredentialsException) {
167   - this.createLoginLog(username, logType, LoginResultEnum.BAD_CREDENTIALS);
  133 + private MemberUserDO login0(String mobile, String password) {
  134 + final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_MOBILE;
  135 + // 校验账号是否存在
  136 + MemberUserDO user = userService.getUserByMobile(mobile);
  137 + if (user == null) {
  138 + createLoginLog(null, mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
168 139 throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
169   - } catch (DisabledException disabledException) {
170   - this.createLoginLog(username, logType, LoginResultEnum.USER_DISABLED);
  140 + }
  141 + if (!userService.isPasswordMatch(password, user.getPassword())) {
  142 + createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
  143 + throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
  144 + }
  145 + // 校验是否禁用
  146 + if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
  147 + createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.USER_DISABLED);
171 148 throw exception(AUTH_LOGIN_USER_DISABLED);
172   - } catch (AuthenticationException authenticationException) {
173   - log.error("[login0][username({}) 发生未知异常]", username, authenticationException);
174   - this.createLoginLog(username, logType, LoginResultEnum.UNKNOWN_ERROR);
175   - throw exception(AUTH_LOGIN_FAIL_UNKNOWN);
176 149 }
177   - Assert.notNull(authentication.getPrincipal(), "Principal 不会为空");
178   - return (LoginUser) authentication.getPrincipal();
  150 + return user;
179 151 }
180 152  
181   - private void createLoginLog(String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
182   - // 获得用户
183   - MemberUserDO user = userService.getUserByMobile(mobile);
  153 + private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
184 154 // 插入登录日志
185 155 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
186 156 reqDTO.setLogType(logType.getType());
187 157 reqDTO.setTraceId(TracerUtils.getTraceId());
188   - if (user != null) {
189   - reqDTO.setUserId(user.getId());
190   - }
  158 + reqDTO.setUserId(userId);
191 159 reqDTO.setUserType(getUserType().getValue());
192 160 reqDTO.setUsername(mobile);
193 161 reqDTO.setUserAgent(ServletUtils.getUserAgent());
... ... @@ -195,72 +163,20 @@ public class MemberAuthServiceImpl implements MemberAuthService {
195 163 reqDTO.setResult(loginResult.getResult());
196 164 loginLogApi.createLoginLog(reqDTO);
197 165 // 更新最后登录时间
198   - if (user != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
199   - userService.updateUserLogin(user.getId(), getClientIP());
  166 + if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
  167 + userService.updateUserLogin(userId, getClientIP());
200 168 }
201 169 }
202 170  
203 171 @Override
204   - public LoginUser verifyTokenAndRefresh(String token) {
205   - // 获得 LoginUser
206   - LoginUser loginUser = userSessionApi.getLoginUser(token);
207   - if (loginUser == null) {
208   - return null;
209   - }
210   - // 刷新 LoginUser 缓存
211   - this.refreshLoginUserCache(token, loginUser);
212   - return loginUser;
213   - }
214   -
215   - private void refreshLoginUserCache(String token, LoginUser loginUser) {
216   - // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
217   - if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
218   - userSessionApi.getSessionTimeoutMillis() / 3) {
219   - return;
220   - }
221   -
222   - // 重新加载 UserDO 信息
223   - MemberUserDO user = userService.getUser(loginUser.getId());
224   - if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
225   - // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面
226   - throw exception(AUTH_TOKEN_EXPIRED);
227   - }
228   -
229   - // 刷新 LoginUser 缓存
230   - userSessionApi.refreshUserSession(token, loginUser);
231   - }
232   -
233   - @Override
234   - public LoginUser mockLogin(Long userId) {
235   - // 获取用户编号对应的 UserDO
236   - MemberUserDO user = userService.getUser(userId);
237   - if (user == null) {
238   - throw new UsernameNotFoundException(String.valueOf(userId));
239   - }
240   -
241   - // 执行登陆
242   - this.createLoginLog(user.getMobile(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS);
243   -
244   - // 创建 LoginUser 对象
245   - return AuthConvert.INSTANCE.convert(user);
246   - }
247   -
248   - @Override
249 172 public void logout(String token) {
250   - // 查询用户信息
251   - LoginUser loginUser = userSessionApi.getLoginUser(token);
252   - if (loginUser == null) {
  173 + // 删除访问令牌
  174 + OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.removeAccessToken(token);
  175 + if (accessTokenRespDTO == null) {
253 176 return;
254 177 }
255   - // 删除 session
256   - userSessionApi.deleteUserSession(token);
257   - // 记录登出日志
258   - this.createLogoutLog(loginUser.getId(), loginUser.getUsername());
259   - }
260   -
261   - @Override
262   - public UserTypeEnum getUserType() {
263   - return UserTypeEnum.MEMBER;
  178 + // 删除成功,则记录登出日志
  179 + createLogoutLog(accessTokenRespDTO.getUserId());
264 180 }
265 181  
266 182 @Override
... ... @@ -269,6 +185,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
269 185 MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
270 186  
271 187 // 更新用户密码
  188 + // TODO 芋艿:需要重构到用户模块
272 189 userMapper.updateById(MemberUserDO.builder().id(userDO.getId())
273 190 .password(passwordEncoder.encode(reqVO.getPassword())).build());
274 191 }
... ... @@ -293,6 +210,12 @@ public class MemberAuthServiceImpl implements MemberAuthService {
293 210 smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
294 211 }
295 212  
  213 + @Override
  214 + public AppAuthLoginRespVO refreshToken(String refreshToken) {
  215 + OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, OAuth2ClientIdEnum.DEFAULT.getId());
  216 + return AuthConvert.INSTANCE.convert(accessTokenDO);
  217 + }
  218 +
296 219 /**
297 220 * 校验旧密码
298 221 *
... ... @@ -321,17 +244,29 @@ public class MemberAuthServiceImpl implements MemberAuthService {
321 244 return user;
322 245 }
323 246  
324   - private void createLogoutLog(Long userId, String username) {
  247 + private void createLogoutLog(Long userId) {
325 248 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
326 249 reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
327 250 reqDTO.setTraceId(TracerUtils.getTraceId());
328 251 reqDTO.setUserId(userId);
329 252 reqDTO.setUserType(getUserType().getValue());
330   - reqDTO.setUsername(username);
  253 + reqDTO.setUsername(getMobile(userId));
331 254 reqDTO.setUserAgent(ServletUtils.getUserAgent());
332 255 reqDTO.setUserIp(getClientIP());
333 256 reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
334 257 loginLogApi.createLoginLog(reqDTO);
335 258 }
336 259  
  260 + private String getMobile(Long userId) {
  261 + if (userId == null) {
  262 + return null;
  263 + }
  264 + MemberUserDO user = userService.getUser(userId);
  265 + return user != null ? user.getMobile() : null;
  266 + }
  267 +
  268 + private UserTypeEnum getUserType() {
  269 + return UserTypeEnum.MEMBER;
  270 + }
  271 +
337 272 }
... ...
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 {
69 69 */
70 70 void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO);
71 71  
  72 + /**
  73 + * 判断密码是否匹配
  74 + *
  75 + * @param rawPassword 未加密的密码
  76 + * @param encodedPassword 加密后的密码
  77 + * @return 是否匹配
  78 + */
  79 + boolean isPasswordMatch(String rawPassword, String encodedPassword);
  80 +
72 81 }
... ...
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 {
69 69 MemberUserDO user = new MemberUserDO();
70 70 user.setMobile(mobile);
71 71 user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
72   - user.setPassword(passwordEncoder.encode(password)); // 加密密码
  72 + user.setPassword(encodePassword(password)); // 加密密码
73 73 user.setRegisterIp(registerIp);
74 74 memberUserMapper.insert(user);
75 75 return user;
... ... @@ -127,6 +127,21 @@ public class MemberUserServiceImpl implements MemberUserService {
127 127 memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build());
128 128 }
129 129  
  130 + @Override
  131 + public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
  132 + return passwordEncoder.matches(rawPassword, encodedPassword);
  133 + }
  134 +
  135 + /**
  136 + * 对密码进行加密
  137 + *
  138 + * @param password 密码
  139 + * @return 加密后的密码
  140 + */
  141 + private String encodePassword(String password) {
  142 + return passwordEncoder.encode(password);
  143 + }
  144 +
130 145 @VisibleForTesting
131 146 public MemberUserDO checkUserExists(Long id) {
132 147 if (id == null) {
... ...
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
9 9 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
10 10 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
11 11 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
12   -import cn.iocoder.yudao.module.system.api.auth.UserSessionApi;
  12 +import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
13 13 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
14 14 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
15 15 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
16 16 import org.junit.jupiter.api.Test;
17 17 import org.springframework.boot.test.mock.mockito.MockBean;
18 18 import org.springframework.context.annotation.Import;
19   -import org.springframework.security.authentication.AuthenticationManager;
20 19 import org.springframework.security.crypto.password.PasswordEncoder;
21 20  
22 21 import javax.annotation.Resource;
... ... @@ -38,8 +37,8 @@ import static org.mockito.Mockito.when;
38 37 @Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class})
39 38 public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
40 39  
41   - @MockBean
42   - private AuthenticationManager authenticationManager;
  40 + // TODO @芋艿:登录相关的单测,待补全
  41 +
43 42 @MockBean
44 43 private MemberUserService userService;
45 44 @MockBean
... ... @@ -47,7 +46,7 @@ public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
47 46 @MockBean
48 47 private LoginLogApi loginLogApi;
49 48 @MockBean
50   - private UserSessionApi userSessionApi;
  49 + private OAuth2TokenApi oauth2TokenApi;
51 50 @MockBean
52 51 private SocialUserApi socialUserApi;
53 52 @MockBean
... ...
yudao-module-system/yudao-module-system-api/pom.xml
... ... @@ -29,13 +29,6 @@
29 29 <optional>true</optional>
30 30 </dependency>
31 31  
32   - <!-- 用户信息 -->
33   - <dependency>
34   - <groupId>cn.iocoder.boot</groupId>
35   - <artifactId>yudao-spring-boot-starter-security</artifactId>
36   - <optional>true</optional>
37   - </dependency>
38   -
39 32 </dependencies>
40 33  
41 34 </project>
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.auth;
  2 +
  3 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
  4 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
  5 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
  6 +
  7 +import javax.validation.Valid;
  8 +
  9 +/**
  10 + * OAuth2.0 Token API 接口
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public interface OAuth2TokenApi {
  15 +
  16 + /**
  17 + * 创建访问令牌
  18 + *
  19 + * @param reqDTO 访问令牌的创建信息
  20 + * @return 访问令牌的信息
  21 + */
  22 + OAuth2AccessTokenRespDTO createAccessToken(@Valid OAuth2AccessTokenCreateReqDTO reqDTO);
  23 +
  24 + /**
  25 + * 校验访问令牌
  26 + *
  27 + * @param accessToken 访问令牌
  28 + * @return 访问令牌的信息
  29 + */
  30 + OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken);
  31 +
  32 + /**
  33 + * 移除访问令牌
  34 + *
  35 + * @param accessToken 访问令牌
  36 + * @return 访问令牌的信息
  37 + */
  38 + OAuth2AccessTokenRespDTO removeAccessToken(String accessToken);
  39 +
  40 + /**
  41 + * 刷新访问令牌
  42 + *
  43 + * @param refreshToken 刷新令牌
  44 + * @param clientId 客户端编号
  45 + * @return 访问令牌的信息
  46 + */
  47 + OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, Long clientId);
  48 +
  49 +}
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java deleted
1   -package cn.iocoder.yudao.module.system.api.auth;
2   -
3   -import cn.iocoder.yudao.framework.security.core.LoginUser;
4   -
5   -import javax.validation.constraints.NotEmpty;
6   -import javax.validation.constraints.NotNull;
7   -
8   -/**
9   - * 在线用户 Session API 接口
10   - *
11   - * @author 芋道源码
12   - */
13   -public interface UserSessionApi {
14   -
15   - /**
16   - * 创建在线用户 Session
17   - *
18   - * @param loginUser 登录用户
19   - * @param userIp 用户 IP
20   - * @param userAgent 用户 UA
21   - * @return Token 令牌
22   - */
23   - String createUserSession(@NotNull(message = "登录用户不能为空") LoginUser loginUser, String userIp, String userAgent);
24   -
25   - /**
26   - * 刷新在线用户 Session 的更新时间
27   - *
28   - * @param token Token 令牌
29   - * @param loginUser 登录用户
30   - */
31   - void refreshUserSession(@NotEmpty(message = "Token 令牌不能为空") String token,
32   - @NotNull(message = "登录用户不能为空") LoginUser loginUser);
33   -
34   - /**
35   - * 删除在线用户 Session
36   - *
37   - * @param token Token 令牌
38   - */
39   - void deleteUserSession(String token);
40   -
41   - /**
42   - * 获得 Token 令牌对应的在线用户
43   - *
44   - * @param token Token 令牌
45   - * @return 在线用户
46   - */
47   - LoginUser getLoginUser(String token);
48   -
49   - /**
50   - * 获得 Session 超时时间,单位:毫秒
51   - *
52   - * @return 超时时间
53   - */
54   - Long getSessionTimeoutMillis();
55   -
56   -}
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.auth.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.io.Serializable;
  6 +
  7 +/**
  8 + * OAuth2.0 访问令牌的校验 Response DTO
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class OAuth2AccessTokenCheckRespDTO implements Serializable {
  14 +
  15 + /**
  16 + * 用户编号
  17 + */
  18 + private Long userId;
  19 + /**
  20 + * 用户类型
  21 + */
  22 + private Integer userType;
  23 + /**
  24 + * 租户编号
  25 + */
  26 + private Long tenantId;
  27 +
  28 +}
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.auth.dto;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +import java.io.Serializable;
  9 +
  10 +/**
  11 + * OAuth2.0 访问令牌创建 Request DTO
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Data
  16 +public class OAuth2AccessTokenCreateReqDTO implements Serializable {
  17 +
  18 + /**
  19 + * 用户编号
  20 + */
  21 + @NotNull(message = "用户编号不能为空")
  22 + private Long userId;
  23 + /**
  24 + * 用户类型
  25 + */
  26 + @NotNull(message = "用户类型不能为空")
  27 + @InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}")
  28 + private Integer userType;
  29 + /**
  30 + * 客户端编号
  31 + */
  32 + @NotNull(message = "客户端编号不能为空")
  33 + private Long clientId;
  34 +
  35 +}
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.auth.dto;
  2 +
  3 +import lombok.Data;
  4 +import lombok.experimental.Accessors;
  5 +
  6 +import java.io.Serializable;
  7 +import java.util.Date;
  8 +
  9 +/**
  10 + * OAuth2.0 访问令牌的信息 Response DTO
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Data
  15 +@Accessors(chain = true)
  16 +public class OAuth2AccessTokenRespDTO implements Serializable {
  17 +
  18 + /**
  19 + * 访问令牌
  20 + */
  21 + private String accessToken;
  22 + /**
  23 + * 刷新令牌
  24 + */
  25 + private String refreshToken;
  26 + /**
  27 + * 用户编号
  28 + */
  29 + private Long userId;
  30 + /**
  31 + * 用户类型
  32 + */
  33 + private Integer userType;
  34 + /**
  35 + * 过期时间
  36 + */
  37 + private Date expiresTime;
  38 +
  39 +}
... ...
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 {
12 12 // ========== AUTH 模块 1002000000 ==========
13 13 ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确");
14 14 ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用");
15   - ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登录失败的兜底,未知原因
16 15 ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
17 16 ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
18 17 ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
... ... @@ -120,8 +119,11 @@ public interface ErrorCodeConstants {
120 119 ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定");
121 120 ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户");
122 121  
123   - // ========== 系统铭感词 1002019000 =========
  122 + // ========== 系统敏感词 1002019000 =========
124 123 ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在");
125 124 ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在");
126 125  
  126 + // ========== 系统敏感词 1002020000 =========
  127 + ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1002020000, "OAuth2 客户端不存在");
  128 +
127 129 }
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientIdEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.enums.auth;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * OAuth2.0 客户端的编号枚举
  8 + */
  9 +@AllArgsConstructor
  10 +@Getter
  11 +public enum OAuth2ClientIdEnum {
  12 +
  13 + DEFAULT(1L); // 系统默认
  14 +
  15 + private final Long id;
  16 +
  17 +}
... ...
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 {
12 12  
13 13 LOGIN_USERNAME(100), // 使用账号登录
14 14 LOGIN_SOCIAL(101), // 使用社交登录
15   - LOGIN_MOCK(102), // 使用 Mock 登录
16 15 LOGIN_MOBILE(103), // 使用手机登陆
17 16 LOGIN_SMS(104), // 使用短信登陆
18 17  
19 18 LOGOUT_SELF(200), // 自己主动登出
20   - LOGOUT_TIMEOUT(201), // 超时登出
21 19 LOGOUT_DELETE(202), // 强制退出
22 20 ;
23 21  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.auth;
  2 +
  3 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
  4 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
  5 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
  6 +import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  8 +import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService;
  9 +import org.springframework.stereotype.Service;
  10 +
  11 +import javax.annotation.Resource;
  12 +
  13 +/**
  14 + * OAuth2.0 Token API 实现类
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@Service
  19 +public class OAuth2TokenApiImpl implements OAuth2TokenApi {
  20 +
  21 + @Resource
  22 + private OAuth2TokenService oauth2TokenService;
  23 +
  24 + @Override
  25 + public OAuth2AccessTokenRespDTO createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) {
  26 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(
  27 + reqDTO.getUserId(), reqDTO.getUserType(), reqDTO.getClientId());
  28 + return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
  29 + }
  30 +
  31 + @Override
  32 + public OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken) {
  33 + return OAuth2TokenConvert.INSTANCE.convert(oauth2TokenService.checkAccessToken(accessToken));
  34 + }
  35 +
  36 + @Override
  37 + public OAuth2AccessTokenRespDTO removeAccessToken(String accessToken) {
  38 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(accessToken);
  39 + return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
  40 + }
  41 +
  42 + @Override
  43 + public OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, Long clientId) {
  44 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId);
  45 + return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
  46 + }
  47 +
  48 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java deleted
1   -package cn.iocoder.yudao.module.system.api.auth;
2   -
3   -import cn.iocoder.yudao.framework.security.core.LoginUser;
4   -import cn.iocoder.yudao.module.system.service.auth.UserSessionService;
5   -import org.springframework.stereotype.Service;
6   -import org.springframework.validation.annotation.Validated;
7   -
8   -import javax.annotation.Resource;
9   -
10   -/**
11   - * 在线用户 Session API 实现类
12   - *
13   - * @author 芋道源码
14   - */
15   -@Service
16   -@Validated
17   -public class UserSessionApiImpl implements UserSessionApi {
18   -
19   - @Resource
20   - private UserSessionService userSessionService;
21   -
22   - @Override
23   - public String createUserSession(LoginUser loginUser, String userIp, String userAgent) {
24   - return userSessionService.createUserSession(loginUser, userIp, userAgent);
25   - }
26   -
27   - @Override
28   - public void refreshUserSession(String token, LoginUser loginUser) {
29   - userSessionService.refreshUserSession(token, loginUser);
30   - }
31   -
32   - @Override
33   - public void deleteUserSession(String token) {
34   - userSessionService.deleteUserSession(token);
35   - }
36   -
37   - @Override
38   - public LoginUser getLoginUser(String token) {
39   - return userSessionService.getLoginUser(token);
40   - }
41   -
42   - @Override
43   - public Long getSessionTimeoutMillis() {
44   - return userSessionService.getSessionTimeoutMillis();
45   - }
46   -
47   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http
1 1 ### 请求 /login 接口 => 成功
2   -POST {{baseUrl}}/system/login
  2 +POST {{baseUrl}}/system/auth/login
3 3 Content-Type: application/json
4 4 tenant-id: {{adminTenentId}}
5 5  
... ... @@ -11,7 +11,7 @@ tenant-id: {{adminTenentId}}
11 11 }
12 12  
13 13 ### 请求 /login 接口 => 成功(无验证码)
14   -POST {{baseUrl}}/system/login
  14 +POST {{baseUrl}}/system/auth/login
15 15 Content-Type: application/json
16 16 tenant-id: {{adminTenentId}}
17 17  
... ... @@ -21,7 +21,7 @@ tenant-id: {{adminTenentId}}
21 21 }
22 22  
23 23 ### 请求 /get-permission-info 接口 => 成功
24   -GET {{baseUrl}}/system/get-permission-info
  24 +GET {{baseUrl}}/system/auth/get-permission-info
25 25 Authorization: Bearer {{token}}
26 26 tenant-id: {{adminTenentId}}
27 27  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
1 1 package cn.iocoder.yudao.module.system.controller.admin.auth;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 6 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
6 7 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  8 +import cn.iocoder.yudao.framework.security.config.SecurityProperties;
7 9 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
8 10 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
9 11 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
10 12 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
11 13 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
  14 +import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
12 15 import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
13 16 import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
14 17 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
... ... @@ -24,18 +27,19 @@ import org.springframework.validation.annotation.Validated;
24 27 import org.springframework.web.bind.annotation.*;
25 28  
26 29 import javax.annotation.Resource;
  30 +import javax.servlet.http.HttpServletRequest;
27 31 import javax.validation.Valid;
28 32 import java.util.List;
  33 +import java.util.Set;
29 34  
30 35 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
31   -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
32   -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
33 36 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
34   -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserRoleIds;
  37 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
  38 +import static java.util.Collections.singleton;
35 39  
36 40 @Api(tags = "管理后台 - 认证")
37 41 @RestController
38   -@RequestMapping("/system/auth") // 暂时不跟 /auth 结尾
  42 +@RequestMapping("/system/auth")
39 43 @Validated
40 44 @Slf4j
41 45 public class AuthController {
... ... @@ -51,13 +55,33 @@ public class AuthController {
51 55 @Resource
52 56 private SocialUserService socialUserService;
53 57  
  58 + @Resource
  59 + private SecurityProperties securityProperties;
  60 +
54 61 @PostMapping("/login")
55 62 @ApiOperation("使用账号密码登录")
56 63 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
57 64 public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
58   - String token = authService.login(reqVO, getClientIP(), getUserAgent());
59   - // 返回结果
60   - return success(AuthLoginRespVO.builder().token(token).build());
  65 + return success(authService.login(reqVO));
  66 + }
  67 +
  68 + @PostMapping("/logout")
  69 + @ApiOperation("登出系统")
  70 + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
  71 + public CommonResult<Boolean> logout(HttpServletRequest request) {
  72 + String token = obtainAuthorization(request, securityProperties.getTokenHeader());
  73 + if (StrUtil.isNotBlank(token)) {
  74 + authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
  75 + }
  76 + return success(true);
  77 + }
  78 +
  79 + @PostMapping("/refresh-token")
  80 + @ApiOperation("刷新令牌")
  81 + @ApiImplicitParam(name = "refreshToken", value = "刷新令牌", required = true, dataTypeClass = String.class)
  82 + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
  83 + public CommonResult<AuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
  84 + return success(authService.refreshToken(refreshToken));
61 85 }
62 86  
63 87 @GetMapping("/get-permission-info")
... ... @@ -69,12 +93,12 @@ public class AuthController {
69 93 return null;
70 94 }
71 95 // 获得角色列表
72   - List<RoleDO> roleList = roleService.getRolesFromCache(getLoginUserRoleIds());
  96 + Set<Long> roleIds = permissionService.getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
  97 + List<RoleDO> roleList = roleService.getRolesFromCache(roleIds);
73 98 // 获得菜单列表
74   - List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(
75   - getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它
  99 + List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
76 100 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
77   - SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus()));
  101 + singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
78 102 // 拼接结果返回
79 103 return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
80 104 }
... ... @@ -82,11 +106,12 @@ public class AuthController {
82 106 @GetMapping("/list-menus")
83 107 @ApiOperation("获得登录用户的菜单列表")
84 108 public CommonResult<List<AuthMenuRespVO>> getMenus() {
  109 + // 获得角色列表
  110 + Set<Long> roleIds = permissionService.getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
85 111 // 获得用户拥有的菜单列表
86   - List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(
87   - getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它
  112 + List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
88 113 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
89   - SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
  114 + singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
90 115 // 转换成 Tree 结构返回
91 116 return success(AuthConvert.INSTANCE.buildMenuTree(menuList));
92 117 }
... ... @@ -97,9 +122,7 @@ public class AuthController {
97 122 @ApiOperation("使用短信验证码登录")
98 123 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
99 124 public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
100   - String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
101   - // 返回结果
102   - return success(AuthLoginRespVO.builder().token(token).build());
  125 + return success(authService.smsLogin(reqVO));
103 126 }
104 127  
105 128 @PostMapping("/send-sms-code")
... ... @@ -127,18 +150,14 @@ public class AuthController {
127 150 @ApiOperation("社交快捷登录,使用 code 授权码")
128 151 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
129 152 public CommonResult<AuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) {
130   - String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent());
131   - // 返回结果
132   - return success(AuthLoginRespVO.builder().token(token).build());
  153 + return success(authService.socialQuickLogin(reqVO));
133 154 }
134 155  
135 156 @PostMapping("/social-bind-login")
136 157 @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码")
137 158 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
138 159 public CommonResult<AuthLoginRespVO> socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) {
139   - String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent());
140   - // 返回结果
141   - return success(AuthLoginRespVO.builder().token(token).build());
  160 + return success(authService.socialBindLogin(reqVO));
142 161 }
143 162  
144 163 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2ClientController.java 0 → 100755
  1 +package cn.iocoder.yudao.module.system.controller.admin.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
  6 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
  7 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientRespVO;
  8 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
  9 +import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert;
  10 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
  11 +import cn.iocoder.yudao.module.system.service.auth.OAuth2ClientService;
  12 +import io.swagger.annotations.Api;
  13 +import io.swagger.annotations.ApiImplicitParam;
  14 +import io.swagger.annotations.ApiOperation;
  15 +import org.springframework.security.access.prepost.PreAuthorize;
  16 +import org.springframework.validation.annotation.Validated;
  17 +import org.springframework.web.bind.annotation.*;
  18 +
  19 +import javax.annotation.Resource;
  20 +import javax.validation.Valid;
  21 +
  22 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  23 +
  24 +@Api(tags = "管理后台 - OAuth2 客户端")
  25 +@RestController
  26 +@RequestMapping("/system/oauth2-client")
  27 +@Validated
  28 +public class OAuth2ClientController {
  29 +
  30 + @Resource
  31 + private OAuth2ClientService oAuth2ClientService;
  32 +
  33 + @PostMapping("/create")
  34 + @ApiOperation("创建 OAuth2 客户端")
  35 + @PreAuthorize("@ss.hasPermission('system:oauth2-client:create')")
  36 + public CommonResult<Long> createOAuth2Client(@Valid @RequestBody OAuth2ClientCreateReqVO createReqVO) {
  37 + return success(oAuth2ClientService.createOAuth2Client(createReqVO));
  38 + }
  39 +
  40 + @PutMapping("/update")
  41 + @ApiOperation("更新 OAuth2 客户端")
  42 + @PreAuthorize("@ss.hasPermission('system:oauth2-client:update')")
  43 + public CommonResult<Boolean> updateOAuth2Client(@Valid @RequestBody OAuth2ClientUpdateReqVO updateReqVO) {
  44 + oAuth2ClientService.updateOAuth2Client(updateReqVO);
  45 + return success(true);
  46 + }
  47 +
  48 + @DeleteMapping("/delete")
  49 + @ApiOperation("删除 OAuth2 客户端")
  50 + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
  51 + @PreAuthorize("@ss.hasPermission('system:oauth2-client:delete')")
  52 + public CommonResult<Boolean> deleteOAuth2Client(@RequestParam("id") Long id) {
  53 + oAuth2ClientService.deleteOAuth2Client(id);
  54 + return success(true);
  55 + }
  56 +
  57 + @GetMapping("/get")
  58 + @ApiOperation("获得 OAuth2 客户端")
  59 + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
  60 + @PreAuthorize("@ss.hasPermission('system:oauth2-client:query')")
  61 + public CommonResult<OAuth2ClientRespVO> getOAuth2Client(@RequestParam("id") Long id) {
  62 + OAuth2ClientDO oAuth2Client = oAuth2ClientService.getOAuth2Client(id);
  63 + return success(OAuth2ClientConvert.INSTANCE.convert(oAuth2Client));
  64 + }
  65 +
  66 + @GetMapping("/page")
  67 + @ApiOperation("获得OAuth2 客户端分页")
  68 + @PreAuthorize("@ss.hasPermission('system:oauth2-client:query')")
  69 + public CommonResult<PageResult<OAuth2ClientRespVO>> getOAuth2ClientPage(@Valid OAuth2ClientPageReqVO pageVO) {
  70 + PageResult<OAuth2ClientDO> pageResult = oAuth2ClientService.getOAuth2ClientPage(pageVO);
  71 + return success(OAuth2ClientConvert.INSTANCE.convertPage(pageResult));
  72 + }
  73 +
  74 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2Controller.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.controller.admin.auth;
  2 +
  3 +import io.swagger.annotations.Api;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.validation.annotation.Validated;
  6 +import org.springframework.web.bind.annotation.RequestMapping;
  7 +import org.springframework.web.bind.annotation.RestController;
  8 +
  9 +@Api(tags = "管理后台 - OAuth2.0 授权")
  10 +@RestController
  11 +@RequestMapping("/system/oauth2")
  12 +@Validated
  13 +@Slf4j
  14 +public class OAuth2Controller {
  15 +
  16 +// POST oauth/token TokenEndpoint:Password、Implicit、Code、Refresh Token
  17 +
  18 +// POST oauth/check_token CheckTokenEndpoint
  19 +
  20 +// DELETE oauth/token ConsumerTokenServices#revokeToken
  21 +
  22 +// GET oauth/authorize AuthorizationEndpoint
  23 +
  24 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2TokenController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.controller.admin.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
  6 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenRespVO;
  7 +import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert;
  8 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  9 +import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
  10 +import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
  11 +import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService;
  12 +import io.swagger.annotations.Api;
  13 +import io.swagger.annotations.ApiImplicitParam;
  14 +import io.swagger.annotations.ApiOperation;
  15 +import org.springframework.security.access.prepost.PreAuthorize;
  16 +import org.springframework.web.bind.annotation.*;
  17 +
  18 +import javax.annotation.Resource;
  19 +import javax.validation.Valid;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  22 +
  23 +@Api(tags = "管理后台 - OAuth2.0 令牌")
  24 +@RestController
  25 +@RequestMapping("/system/oauth2-token")
  26 +public class OAuth2TokenController {
  27 +
  28 + @Resource
  29 + private OAuth2TokenService oauth2TokenService;
  30 + @Resource
  31 + private AdminAuthService authService;
  32 +
  33 + @GetMapping("/page")
  34 + @ApiOperation(value = "获得访问令牌分页", notes = "只返回有效期内的")
  35 + @PreAuthorize("@ss.hasPermission('system:oauth2-token:page')")
  36 + public CommonResult<PageResult<OAuth2AccessTokenRespVO>> getAccessTokenPage(@Valid OAuth2AccessTokenPageReqVO reqVO) {
  37 + PageResult<OAuth2AccessTokenDO> pageResult = oauth2TokenService.getAccessTokenPage(reqVO);
  38 + return success(OAuth2TokenConvert.INSTANCE.convert(pageResult));
  39 + }
  40 +
  41 + @DeleteMapping("/delete")
  42 + @ApiOperation("删除访问令牌")
  43 + @ApiImplicitParam(name = "accessToken", value = "访问令牌", required = true, dataTypeClass = String.class, example = "tudou")
  44 + @PreAuthorize("@ss.hasPermission('system:oauth2-token:delete')")
  45 + public CommonResult<Boolean> deleteAccessToken(@RequestParam("accessToken") String accessToken) {
  46 + authService.logout(accessToken, LoginLogTypeEnum.LOGOUT_DELETE.getType());
  47 + return success(true);
  48 + }
  49 +
  50 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java deleted
1   -package cn.iocoder.yudao.module.system.controller.admin.auth;
2   -
3   -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageItemRespVO;
4   -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
5   -import cn.iocoder.yudao.module.system.convert.auth.UserSessionConvert;
6   -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
7   -import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
8   -import cn.iocoder.yudao.module.system.service.auth.UserSessionService;
9   -import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
10   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
11   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
12   -import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
13   -import cn.iocoder.yudao.module.system.service.dept.DeptService;
14   -import cn.iocoder.yudao.module.system.service.user.AdminUserService;
15   -import io.swagger.annotations.Api;
16   -import io.swagger.annotations.ApiImplicitParam;
17   -import io.swagger.annotations.ApiOperation;
18   -import org.springframework.security.access.prepost.PreAuthorize;
19   -import org.springframework.validation.annotation.Validated;
20   -import org.springframework.web.bind.annotation.*;
21   -
22   -import javax.annotation.Resource;
23   -import java.util.ArrayList;
24   -import java.util.List;
25   -import java.util.Map;
26   -
27   -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
28   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
29   -
30   -@Api(tags = "管理后台 - 用户 Session")
31   -@RestController
32   -@RequestMapping("/system/user-session")
33   -public class UserSessionController {
34   -
35   - @Resource
36   - private UserSessionService userSessionService;
37   - @Resource
38   - private AdminUserService userService;
39   -
40   - @Resource
41   - private DeptService deptService;
42   -
43   - @GetMapping("/page")
44   - @ApiOperation("获得 Session 分页列表")
45   - @PreAuthorize("@ss.hasPermission('system:user-session:page')")
46   - public CommonResult<PageResult<UserSessionPageItemRespVO>> getUserSessionPage(@Validated UserSessionPageReqVO reqVO) {
47   - // 获得 Session 分页
48   - PageResult<UserSessionDO> pageResult = userSessionService.getUserSessionPage(reqVO);
49   -
50   - // 获得拼接需要的数据
51   - Map<Long, AdminUserDO> userMap = userService.getUserMap(
52   - convertList(pageResult.getList(), UserSessionDO::getUserId));
53   - Map<Long, DeptDO> deptMap = deptService.getDeptMap(
54   - convertList(userMap.values(), AdminUserDO::getDeptId));
55   - // 拼接结果返回
56   - List<UserSessionPageItemRespVO> sessionList = new ArrayList<>(pageResult.getList().size());
57   - pageResult.getList().forEach(session -> {
58   - UserSessionPageItemRespVO respVO = UserSessionConvert.INSTANCE.convert(session);
59   - sessionList.add(respVO);
60   - // 设置用户账号
61   - MapUtils.findAndThen(userMap, session.getUserId(), user -> {
62   - respVO.setUsername(user.getUsername());
63   - // 设置用户部门
64   - MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> respVO.setDeptName(dept.getName()));
65   - });
66   - });
67   - return success(new PageResult<>(sessionList, pageResult.getTotal()));
68   - }
69   -
70   - @DeleteMapping("/delete")
71   - @ApiOperation("删除 Session")
72   - @ApiImplicitParam(name = "id", value = "Session 编号", required = true, dataTypeClass = Long.class, example = "1024")
73   - @PreAuthorize("@ss.hasPermission('system:user-session:delete')")
74   - public CommonResult<Boolean> deleteUserSession(@RequestParam("id") Long id) {
75   - userSessionService.deleteUserSession(id);
76   - return success(true);
77   - }
78   -
79   -}
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;
7 7 import lombok.Data;
8 8 import lombok.NoArgsConstructor;
9 9  
10   -@ApiModel("管理后台 - 账号密码登录 Response VO")
  10 +import java.util.Date;
  11 +
  12 +@ApiModel("管理后台 - 登录 Response VO")
11 13 @Data
12 14 @NoArgsConstructor
13 15 @AllArgsConstructor
14 16 @Builder
15 17 public class AuthLoginRespVO {
16 18  
17   - @ApiModelProperty(value = "token", required = true, example = "yudaoyuanma")
18   - private String token;
  19 + @ApiModelProperty(value = "用户编号", required = true, example = "1024")
  20 + private Long userId;
  21 +
  22 + @ApiModelProperty(value = "访问令牌", required = true, example = "happy")
  23 + private String accessToken;
  24 +
  25 + @ApiModelProperty(value = "刷新令牌", required = true, example = "nice")
  26 + private String refreshToken;
  27 +
  28 + @ApiModelProperty(value = "过期时间", required = true)
  29 + private Date expiresTime;
19 30  
20 31 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientBaseVO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotNull;
  7 +import java.util.List;
  8 +
  9 +/**
  10 +* OAuth2 客户端 Base VO,提供给添加、修改、详细的子 VO 使用
  11 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  12 +*/
  13 +@Data
  14 +public class OAuth2ClientBaseVO {
  15 +
  16 + @ApiModelProperty(value = "客户端编号", required = true)
  17 + @NotNull(message = "客户端编号不能为空")
  18 + private Long id;
  19 +
  20 + @ApiModelProperty(value = "客户端密钥", required = true)
  21 + @NotNull(message = "客户端密钥不能为空")
  22 + private String secret;
  23 +
  24 + @ApiModelProperty(value = "应用名", required = true)
  25 + @NotNull(message = "应用名不能为空")
  26 + private String name;
  27 +
  28 + @ApiModelProperty(value = "应用图标", required = true)
  29 + @NotNull(message = "应用图标不能为空")
  30 + private String logo;
  31 +
  32 + @ApiModelProperty(value = "应用描述")
  33 + private String description;
  34 +
  35 + @ApiModelProperty(value = "状态", required = true)
  36 + @NotNull(message = "状态不能为空")
  37 + private Integer status;
  38 +
  39 + @ApiModelProperty(value = "访问令牌的有效期", required = true)
  40 + @NotNull(message = "访问令牌的有效期不能为空")
  41 + private Integer accessTokenValiditySeconds;
  42 +
  43 + @ApiModelProperty(value = "刷新令牌的有效期", required = true)
  44 + @NotNull(message = "刷新令牌的有效期不能为空")
  45 + private Integer refreshTokenValiditySeconds;
  46 +
  47 + @ApiModelProperty(value = "可重定向的 URI 地址", required = true)
  48 + @NotNull(message = "可重定向的 URI 地址不能为空")
  49 + private List<String> redirectUris;
  50 +
  51 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientCreateReqVO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
  2 +
  3 +import lombok.*;
  4 +import io.swagger.annotations.*;
  5 +
  6 +@ApiModel("管理后台 - OAuth2 客户端创建 Request VO")
  7 +@Data
  8 +@EqualsAndHashCode(callSuper = true)
  9 +@ToString(callSuper = true)
  10 +public class OAuth2ClientCreateReqVO extends OAuth2ClientBaseVO {
  11 +
  12 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientPageReqVO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
  2 +
  3 +import lombok.*;
  4 +import io.swagger.annotations.*;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  6 +
  7 +@ApiModel("管理后台 - OAuth2 客户端分页 Request VO")
  8 +@Data
  9 +@EqualsAndHashCode(callSuper = true)
  10 +@ToString(callSuper = true)
  11 +public class OAuth2ClientPageReqVO extends PageParam {
  12 +
  13 + @ApiModelProperty(value = "应用名")
  14 + private String name;
  15 +
  16 + @ApiModelProperty(value = "状态")
  17 + private Integer status;
  18 +
  19 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientRespVO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import java.util.Date;
  10 +
  11 +@ApiModel("管理后台 - OAuth2 客户端 Response VO")
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +@ToString(callSuper = true)
  15 +public class OAuth2ClientRespVO extends OAuth2ClientBaseVO {
  16 +
  17 + @ApiModelProperty(value = "创建时间", required = true)
  18 + private Date createTime;
  19 +
  20 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientUpdateReqVO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +@ApiModel("管理后台 - OAuth2 客户端更新 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@ToString(callSuper = true)
  12 +public class OAuth2ClientUpdateReqVO extends OAuth2ClientBaseVO {
  13 +
  14 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageItemRespVO.java deleted
1   -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.session;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
4   -import io.swagger.annotations.ApiModel;
5   -import io.swagger.annotations.ApiModelProperty;
6   -import lombok.AllArgsConstructor;
7   -import lombok.Data;
8   -import lombok.EqualsAndHashCode;
9   -import lombok.NoArgsConstructor;
10   -
11   -import java.util.Date;
12   -
13   -@ApiModel(value = "管理后台 - 用户在线 Session Response VO", description = "相比用户基本信息来说,会多部门、用户账号等信息")
14   -@Data
15   -@NoArgsConstructor
16   -@AllArgsConstructor
17   -@EqualsAndHashCode(callSuper = true)
18   -public class UserSessionPageItemRespVO extends PageParam {
19   -
20   - @ApiModelProperty(value = "Session 编号", required = true, example = "fe50b9f6-d177-44b1-8da9-72ea34f63db7")
21   - private String id;
22   -
23   - @ApiModelProperty(value = "用户 IP", required = true, example = "127.0.0.1")
24   - private String userIp;
25   -
26   - @ApiModelProperty(value = "浏览器 UserAgent", required = true, example = "Mozilla/5.0")
27   - private String userAgent;
28   -
29   - @ApiModelProperty(value = "登录时间", required = true)
30   - private Date createTime;
31   -
32   - @ApiModelProperty(value = "用户账号", required = true, example = "yudao")
33   - private String username;
34   -
35   - @ApiModelProperty(value = "部门名称", example = "研发部")
36   - private String deptName;
37   -
38   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageReqVO.java deleted
1   -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.session;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
4   -import io.swagger.annotations.ApiModel;
5   -import io.swagger.annotations.ApiModelProperty;
6   -import lombok.Data;
7   -import lombok.EqualsAndHashCode;
8   -
9   -@ApiModel("管理后台 - 在线用户 Session 分页 Request VO")
10   -@Data
11   -@EqualsAndHashCode(callSuper = true)
12   -public class UserSessionPageReqVO extends PageParam {
13   -
14   - @ApiModelProperty(value = "用户 IP", example = "127.0.0.1", notes = "模糊匹配")
15   - private String userIp;
16   -
17   - @ApiModelProperty(value = "用户账号", example = "yudao", notes = "模糊匹配")
18   - private String username;
19   -
20   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.controller.admin.auth.vo.token;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.annotations.ApiModel;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +import lombok.EqualsAndHashCode;
  8 +
  9 +@ApiModel("管理后台 - 访问令牌分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +public class OAuth2AccessTokenPageReqVO extends PageParam {
  13 +
  14 + @ApiModelProperty(value = "用户编号", required = true, example = "666")
  15 + private Long userId;
  16 +
  17 + @ApiModelProperty(value = "用户类型", required = true, example = "2", notes = "参见 UserTypeEnum 枚举")
  18 + private Integer userType;
  19 +
  20 + @ApiModelProperty(value = "客户端编号", required = true, example = "2")
  21 + private Long clientId;
  22 +
  23 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.controller.admin.auth.vo.token;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Data;
  7 +import lombok.NoArgsConstructor;
  8 +
  9 +import java.util.Date;
  10 +
  11 +@ApiModel("管理后台 - 访问令牌 Response VO")
  12 +@Data
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
  15 +public class OAuth2AccessTokenRespVO {
  16 +
  17 + @ApiModelProperty(value = "编号", required = true, example = "1024")
  18 + private Long id;
  19 +
  20 + @ApiModelProperty(value = "访问令牌", required = true, example = "tudou")
  21 + private String accessToken;
  22 +
  23 + @ApiModelProperty(value = "刷新令牌", required = true, example = "nice")
  24 + private String refreshToken;
  25 +
  26 + @ApiModelProperty(value = "用户编号", required = true, example = "666")
  27 + private Long userId;
  28 +
  29 + @ApiModelProperty(value = "用户类型", required = true, example = "2", notes = "参见 UserTypeEnum 枚举")
  30 + private Integer userType;
  31 +
  32 + @ApiModelProperty(value = "客户端编号", required = true, example = "2")
  33 + private Long clientId;
  34 +
  35 + @ApiModelProperty(value = "创建时间", required = true)
  36 + private Date createTime;
  37 +
  38 + @ApiModelProperty(value = "过期时间", required = true)
  39 + private Date expiresTime;
  40 +
  41 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java
1 1 package cn.iocoder.yudao.module.system.convert.auth;
2 2  
3   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 3 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5   -import cn.iocoder.yudao.framework.security.core.LoginUser;
6   -import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
7 4 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
8 5 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
9 6 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
10   -import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
11 7 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
  8 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
12 9 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
13 10 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
14 11 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
15 12 import cn.iocoder.yudao.module.system.enums.permission.MenuIdEnum;
16 13 import org.mapstruct.Mapper;
17   -import org.mapstruct.Mapping;
18 14 import org.mapstruct.factory.Mappers;
19 15 import org.slf4j.LoggerFactory;
20 16  
... ... @@ -25,13 +21,7 @@ public interface AuthConvert {
25 21  
26 22 AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
27 23  
28   - @Mapping(source = "updateTime", target = "updateTime", ignore = true) // 字段相同,但是含义不同,忽略
29   - LoginUser convert0(AdminUserDO bean);
30   -
31   - default LoginUser convert(AdminUserDO bean) {
32   - // 目的,为了设置 UserTypeEnum.ADMIN.getValue()
33   - return convert0(bean).setUserType(UserTypeEnum.ADMIN.getValue());
34   - }
  24 + AuthLoginRespVO convert(OAuth2AccessTokenDO bean);
35 25  
36 26 default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
37 27 return AuthPermissionInfoRespVO.builder()
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/OAuth2ClientConvert.java 0 → 100755
  1 +package cn.iocoder.yudao.module.system.convert.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
  5 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientRespVO;
  6 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
  8 +import org.mapstruct.Mapper;
  9 +import org.mapstruct.factory.Mappers;
  10 +
  11 +import java.util.List;
  12 +
  13 +/**
  14 + * OAuth2 客户端 Convert
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@Mapper
  19 +public interface OAuth2ClientConvert {
  20 +
  21 + OAuth2ClientConvert INSTANCE = Mappers.getMapper(OAuth2ClientConvert.class);
  22 +
  23 + OAuth2ClientDO convert(OAuth2ClientCreateReqVO bean);
  24 +
  25 + OAuth2ClientDO convert(OAuth2ClientUpdateReqVO bean);
  26 +
  27 + OAuth2ClientRespVO convert(OAuth2ClientDO bean);
  28 +
  29 + List<OAuth2ClientRespVO> convertList(List<OAuth2ClientDO> list);
  30 +
  31 + PageResult<OAuth2ClientRespVO> convertPage(PageResult<OAuth2ClientDO> page);
  32 +
  33 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/OAuth2TokenConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.convert.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
  5 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
  6 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenRespVO;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  8 +import org.mapstruct.Mapper;
  9 +import org.mapstruct.factory.Mappers;
  10 +
  11 +@Mapper
  12 +public interface OAuth2TokenConvert {
  13 +
  14 + OAuth2TokenConvert INSTANCE = Mappers.getMapper(OAuth2TokenConvert.class);
  15 +
  16 + OAuth2AccessTokenCheckRespDTO convert(OAuth2AccessTokenDO bean);
  17 +
  18 + PageResult<OAuth2AccessTokenRespVO> convert(PageResult<OAuth2AccessTokenDO> page);
  19 +
  20 + OAuth2AccessTokenRespDTO convert2(OAuth2AccessTokenDO bean);
  21 +
  22 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java deleted
1   -package cn.iocoder.yudao.module.system.convert.auth;
2   -
3   -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageItemRespVO;
4   -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
5   -import org.mapstruct.Mapper;
6   -import org.mapstruct.factory.Mappers;
7   -
8   -@Mapper
9   -public interface UserSessionConvert {
10   -
11   - UserSessionConvert INSTANCE = Mappers.getMapper(UserSessionConvert.class);
12   -
13   - UserSessionPageItemRespVO convert(UserSessionDO session);
14   -
15   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.dataobject.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
  5 +import com.baomidou.mybatisplus.annotation.TableId;
  6 +import com.baomidou.mybatisplus.annotation.TableName;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +import lombok.experimental.Accessors;
  10 +
  11 +import java.util.Date;
  12 +
  13 +/**
  14 + * OAuth2 访问令牌 DO
  15 + *
  16 + * 如下字段,暂时未使用,暂时不支持:
  17 + * user_name、authentication(用户信息)
  18 + *
  19 + * @author 芋道源码
  20 + */
  21 +@TableName("system_oauth2_access_token")
  22 +@Data
  23 +@EqualsAndHashCode(callSuper = true)
  24 +@Accessors(chain = true)
  25 +public class OAuth2AccessTokenDO extends TenantBaseDO {
  26 +
  27 + /**
  28 + * 编号,数据库递增
  29 + */
  30 + @TableId
  31 + private Long id;
  32 + /**
  33 + * 访问令牌
  34 + */
  35 + private String accessToken;
  36 + /**
  37 + * 刷新令牌
  38 + */
  39 + private String refreshToken;
  40 + /**
  41 + * 用户编号
  42 + */
  43 + private Long userId;
  44 + /**
  45 + * 用户类型
  46 + *
  47 + * 枚举 {@link UserTypeEnum}
  48 + */
  49 + private Integer userType;
  50 + /**
  51 + * 客户端编号
  52 + *
  53 + * 关联 {@link OAuth2ClientDO#getId()}
  54 + */
  55 + private Long clientId;
  56 + /**
  57 + * 过期时间
  58 + */
  59 + private Date expiresTime;
  60 +
  61 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.dataobject.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import com.baomidou.mybatisplus.annotation.IdType;
  6 +import com.baomidou.mybatisplus.annotation.TableField;
  7 +import com.baomidou.mybatisplus.annotation.TableId;
  8 +import com.baomidou.mybatisplus.annotation.TableName;
  9 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
  10 +import lombok.Data;
  11 +import lombok.EqualsAndHashCode;
  12 +import lombok.experimental.Accessors;
  13 +
  14 +import java.util.List;
  15 +
  16 +/**
  17 + * OAuth2 客户端 DO
  18 + *
  19 + * 如下字段,考虑到使用相对不是很高频,主要是一些开关,暂时不支持:
  20 + * authorized_grant_types、authorities、additional_information、autoapprove、resource_ids、scope
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +@TableName(value = "system_oauth2_client", autoResultMap = true)
  25 +@Data
  26 +@EqualsAndHashCode(callSuper = true)
  27 +@Accessors(chain = true)
  28 +public class OAuth2ClientDO extends BaseDO {
  29 +
  30 + /**
  31 + * 客户端编号
  32 + *
  33 + * 由于 SQL Server 在存储 String 主键有点问题,所以暂时使用 Long 类型
  34 + */
  35 + @TableId(type = IdType.INPUT)
  36 + private Long id;
  37 + /**
  38 + * 客户端密钥
  39 + */
  40 + private String secret;
  41 + /**
  42 + * 应用名
  43 + */
  44 + private String name;
  45 + /**
  46 + * 应用图标
  47 + */
  48 + private String logo;
  49 + /**
  50 + * 应用描述
  51 + */
  52 + private String description;
  53 + /**
  54 + * 状态
  55 + *
  56 + * 枚举 {@link CommonStatusEnum}
  57 + */
  58 + private Integer status;
  59 + /**
  60 + * 访问令牌的有效期
  61 + */
  62 + private Integer accessTokenValiditySeconds;
  63 + /**
  64 + * 刷新令牌的有效期
  65 + */
  66 + private Integer refreshTokenValiditySeconds;
  67 + /**
  68 + * 可重定向的 URI 地址
  69 + */
  70 + @TableField(typeHandler = JacksonTypeHandler.class)
  71 + private List<String> redirectUris;
  72 +
  73 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java
... ... @@ -2,43 +2,34 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5   -import cn.iocoder.yudao.framework.security.core.LoginUser;
6   -import com.baomidou.mybatisplus.annotation.*;
7   -import lombok.Builder;
  5 +import com.baomidou.mybatisplus.annotation.TableName;
8 6 import lombok.Data;
9 7 import lombok.EqualsAndHashCode;
  8 +import lombok.experimental.Accessors;
10 9  
11 10 import java.util.Date;
12 11  
13 12 /**
14   - * 在线用户表
15   - *
16   - * 我们已经将 {@link LoginUser} 缓存在 Redis 当中。
17   - * 这里额外存储在线用户到 MySQL 中,目的是为了方便管理界面可以灵活查询。
18   - * 同时,通过定时轮询 UserSessionDO 表,可以主动删除 Redis 的缓存,因为 Redis 的过期删除是延迟的。
  13 + * OAuth2 授权码 DO
19 14 *
20 15 * @author 芋道源码
21 16 */
22   -@TableName(value = "system_user_session")
23   -@KeySequence(value = "system_user_session_seq")
  17 +@TableName("system_oauth2_code")
24 18 @Data
25   -@Builder
26 19 @EqualsAndHashCode(callSuper = true)
27   -public class UserSessionDO extends BaseDO {
  20 +@Accessors(chain = true)
  21 +public class OAuth2CodeDO extends BaseDO {
28 22  
29 23 /**
30   - * 会话编号
  24 + * 编号,数据库递增
31 25 */
32 26 private Long id;
33 27 /**
34   - * 令牌
  28 + * 授权码
35 29 */
36   - private String token;
37   -
  30 + private String code;
38 31 /**
39 32 * 用户编号
40   - *
41   - * 关联 AdminUserDO.id 或者 MemberUserDO.id
42 33 */
43 34 private Long userId;
44 35 /**
... ... @@ -47,25 +38,25 @@ public class UserSessionDO extends BaseDO {
47 38 * 枚举 {@link UserTypeEnum}
48 39 */
49 40 private Integer userType;
50   -
51 41 /**
52   - * 用户账号
  42 + * 客户端编号
53 43 *
54   - * 冗余,因为账号可以变更
  44 + * 关联 {@link OAuth2ClientDO#getId()}
55 45 */
56   - private String username;
57   -
  46 + private Long clientId;
58 47 /**
59   - * 用户 IP
  48 + * 刷新令牌
  49 + *
  50 + * 关联 {@link OAuth2RefreshTokenDO#getRefreshToken()}
60 51 */
61   - private String userIp;
  52 + private String refreshToken;
62 53 /**
63   - * 浏览器 UA
  54 + * 过期时间
64 55 */
65   - private String userAgent;
  56 + private Date expiresTime;
66 57 /**
67   - * 会话超时时间
  58 + * 创建 IP
68 59 */
69   - private Date sessionTimeout;
  60 + private String createIp;
70 61  
71 62 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.dataobject.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import com.baomidou.mybatisplus.annotation.TableName;
  6 +import lombok.Data;
  7 +import lombok.EqualsAndHashCode;
  8 +import lombok.experimental.Accessors;
  9 +
  10 +import java.util.Date;
  11 +
  12 +/**
  13 + * OAuth2 刷新令牌
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@TableName("system_oauth2_refresh_token")
  18 +@Data
  19 +@EqualsAndHashCode(callSuper = true)
  20 +@Accessors(chain = true)
  21 +public class OAuth2RefreshTokenDO extends BaseDO {
  22 +
  23 + /**
  24 + * 编号,数据库字典
  25 + */
  26 + private Long id;
  27 + /**
  28 + * 刷新令牌
  29 + */
  30 + private String refreshToken;
  31 + /**
  32 + * 用户编号
  33 + */
  34 + private Long userId;
  35 + /**
  36 + * 用户类型
  37 + *
  38 + * 枚举 {@link UserTypeEnum}
  39 + */
  40 + private Integer userType;
  41 + /**
  42 + * 客户端编号
  43 + *
  44 + * 关联 {@link OAuth2ClientDO#getId()}
  45 + */
  46 + private Long clientId;
  47 + /**
  48 + * 过期时间
  49 + */
  50 + private Date expiresTime;
  51 +
  52 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.mysql.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.Date;
  11 +import java.util.List;
  12 +
  13 +@Mapper
  14 +public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO> {
  15 +
  16 + default OAuth2AccessTokenDO selectByAccessToken(String accessToken) {
  17 + return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken);
  18 + }
  19 +
  20 + default List<OAuth2AccessTokenDO> selectListByRefreshToken(String refreshToken) {
  21 + return selectList(OAuth2AccessTokenDO::getRefreshToken, refreshToken);
  22 + }
  23 +
  24 + default PageResult<OAuth2AccessTokenDO> selectPage(OAuth2AccessTokenPageReqVO reqVO) {
  25 + return selectPage(reqVO, new LambdaQueryWrapperX<OAuth2AccessTokenDO>()
  26 + .eqIfPresent(OAuth2AccessTokenDO::getUserId, reqVO.getUserId())
  27 + .eqIfPresent(OAuth2AccessTokenDO::getUserType, reqVO.getUserType())
  28 + .eqIfPresent(OAuth2AccessTokenDO::getClientId, reqVO.getClientId())
  29 + .gt(OAuth2AccessTokenDO::getExpiresTime, new Date())
  30 + .orderByDesc(OAuth2AccessTokenDO::getId));
  31 + }
  32 +
  33 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2ClientMapper.java 0 → 100755
  1 +package cn.iocoder.yudao.module.system.dal.mysql.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +/**
  11 + * OAuth2 客户端 Mapper
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Mapper
  16 +public interface OAuth2ClientMapper extends BaseMapperX<OAuth2ClientDO> {
  17 +
  18 + default PageResult<OAuth2ClientDO> selectPage(OAuth2ClientPageReqVO reqVO) {
  19 + return selectPage(reqVO, new LambdaQueryWrapperX<OAuth2ClientDO>()
  20 + .likeIfPresent(OAuth2ClientDO::getName, reqVO.getName())
  21 + .eqIfPresent(OAuth2ClientDO::getStatus, reqVO.getStatus())
  22 + .orderByDesc(OAuth2ClientDO::getId));
  23 + }
  24 +
  25 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.mysql.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +
  8 +@Mapper
  9 +public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshTokenDO> {
  10 +
  11 + default int deleteByRefreshToken(String refreshToken) {
  12 + return delete(new LambdaQueryWrapperX<OAuth2RefreshTokenDO>()
  13 + .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken));
  14 + }
  15 +
  16 + default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) {
  17 + return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken);
  18 + }
  19 +
  20 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java deleted
1   -package cn.iocoder.yudao.module.system.dal.mysql.auth;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6   -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
7   -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
8   -import org.apache.ibatis.annotations.Mapper;
9   -
10   -import java.util.Collection;
11   -import java.util.Date;
12   -import java.util.List;
13   -
14   -@Mapper
15   -public interface UserSessionMapper extends BaseMapperX<UserSessionDO> {
16   -
17   - default PageResult<UserSessionDO> selectPage(UserSessionPageReqVO reqVO, Collection<Long> userIds) {
18   - return selectPage(reqVO, new LambdaQueryWrapperX<UserSessionDO>()
19   - .inIfPresent(UserSessionDO::getUserId, userIds)
20   - .likeIfPresent(UserSessionDO::getUserIp, reqVO.getUserIp()));
21   - }
22   -
23   - default List<UserSessionDO> selectListBySessionTimoutLt() {
24   - return selectList(new LambdaQueryWrapperX<UserSessionDO>()
25   - .lt(UserSessionDO::getSessionTimeout, new Date()));
26   - }
27   -
28   - default void updateByToken(String token, UserSessionDO updateObj) {
29   - update(updateObj, new LambdaQueryWrapperX<UserSessionDO>()
30   - .eq(UserSessionDO::getToken, token));
31   - }
32   -
33   - default void deleteByToken(String token) {
34   - delete(new LambdaQueryWrapperX<UserSessionDO>().eq(UserSessionDO::getToken, token));
35   - }
36   -
37   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
1 1 package cn.iocoder.yudao.module.system.dal.redis;
2 2  
3 3 import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
4   -import cn.iocoder.yudao.framework.security.core.LoginUser;
  4 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
5 5  
6 6 import java.time.Duration;
7 7  
... ... @@ -18,9 +18,9 @@ public interface RedisKeyConstants {
18 18 "captcha_code:%s", // 参数为 uuid
19 19 STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
20 20  
21   - RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登录用户的缓存",
22   - "login_user:%s", // 参数为 token 令牌
23   - STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
  21 + RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存",
  22 + "oauth2_access_token:%s", // 参数为访问令牌 token
  23 + STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
24 24  
25 25 RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
26 26 "social_auth_state:%s", // 参数为 state
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/LoginUserRedisDAO.java deleted
1   -package cn.iocoder.yudao.module.system.dal.redis.auth;
2   -
3   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4   -import cn.iocoder.yudao.framework.security.config.SecurityProperties;
5   -import cn.iocoder.yudao.framework.security.core.LoginUser;
6   -import org.springframework.data.redis.core.StringRedisTemplate;
7   -import org.springframework.stereotype.Repository;
8   -
9   -import javax.annotation.Resource;
10   -
11   -import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.LOGIN_USER;
12   -
13   -/**
14   - * {@link LoginUser} 的 RedisDAO
15   - *
16   - * @author 芋道源码
17   - */
18   -@Repository
19   -public class LoginUserRedisDAO {
20   -
21   - @Resource
22   - private StringRedisTemplate stringRedisTemplate;
23   -
24   - @Resource
25   - private SecurityProperties securityProperties;
26   -
27   - public LoginUser get(String token) {
28   - String redisKey = formatKey(token);
29   - return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), LoginUser.class);
30   - }
31   -
32   - public Boolean exists(String token) {
33   - String redisKey = formatKey(token);
34   - return stringRedisTemplate.hasKey(redisKey);
35   - }
36   -
37   - public void set(String token, LoginUser loginUser) {
38   - String redisKey = formatKey(token);
39   - stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser),
40   - securityProperties.getSessionTimeout());
41   - }
42   -
43   - public void delete(String token) {
44   - String redisKey = formatKey(token);
45   - stringRedisTemplate.delete(redisKey);
46   - }
47   -
48   - private static String formatKey(String token) {
49   - return LOGIN_USER.formatKey(token);
50   - }
51   -
52   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.redis.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  4 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  5 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  6 +import org.springframework.data.redis.core.StringRedisTemplate;
  7 +import org.springframework.stereotype.Repository;
  8 +
  9 +import javax.annotation.Resource;
  10 +import java.util.Collection;
  11 +import java.util.List;
  12 +import java.util.concurrent.TimeUnit;
  13 +
  14 +import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
  15 +
  16 +/**
  17 + * {@link OAuth2AccessTokenDO} 的 RedisDAO
  18 + *
  19 + * @author 芋道源码
  20 + */
  21 +@Repository
  22 +public class OAuth2AccessTokenRedisDAO {
  23 +
  24 + @Resource
  25 + private StringRedisTemplate stringRedisTemplate;
  26 +
  27 + public OAuth2AccessTokenDO get(String accessToken) {
  28 + String redisKey = formatKey(accessToken);
  29 + return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class);
  30 + }
  31 +
  32 + public void set(OAuth2AccessTokenDO accessTokenDO) {
  33 + String redisKey = formatKey(accessTokenDO.getAccessToken());
  34 + // 清理多余字段,避免缓存
  35 + accessTokenDO.setUpdater(null).setUpdateTime(null).setCreateTime(null).setCreator(null).setDeleted(null);
  36 + stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(accessTokenDO),
  37 + accessTokenDO.getExpiresTime().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
  38 + }
  39 +
  40 + public void delete(String accessToken) {
  41 + String redisKey = formatKey(accessToken);
  42 + stringRedisTemplate.delete(redisKey);
  43 + }
  44 +
  45 + public void deleteList(Collection<String> accessTokens) {
  46 + List<String> redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey);
  47 + stringRedisTemplate.delete(redisKeys);
  48 + }
  49 +
  50 + private static String formatKey(String accessToken) {
  51 + return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken);
  52 + }
  53 +
  54 +}
... ...
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 {
20 20 public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
21 21 // 登录的接口
22 22 registry.antMatchers(buildAdminApi("/system/auth/login")).permitAll();
  23 + registry.antMatchers(buildAdminApi("/system/auth/logout")).permitAll();
  24 + registry.antMatchers(buildAdminApi("/system/auth/refresh-token")).permitAll();
23 25 // 社交登陆的接口
24 26 registry.antMatchers(buildAdminApi("/system/auth/social-auth-redirect")).permitAll();
25 27 registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll();
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java deleted
1   -package cn.iocoder.yudao.module.system.job.auth;
2   -
3   -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
4   -import cn.iocoder.yudao.module.system.service.auth.UserSessionService;
5   -import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 用户 Session 超时 Job
13   - *
14   - * @author 願
15   - */
16   -@Component
17   -@TenantJob
18   -@Slf4j
19   -public class UserSessionTimeoutJob implements JobHandler {
20   -
21   - @Resource
22   - private UserSessionService userSessionService;
23   -
24   - @Override
25   - public String execute(String param) throws Exception {
26   - // 执行过期
27   - Long timeoutCount = userSessionService.deleteTimeoutSession();
28   - // 返回结果,记录每次的超时数量
29   - return String.format("移除在线会话数量为 %s 个", timeoutCount);
30   - }
31   -
32   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java
1 1 package cn.iocoder.yudao.module.system.service.auth;
2 2  
3 3 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
4   -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
5 4  
6 5 import javax.validation.Valid;
7 6  
8 7 /**
9 8 * 管理后台的认证 Service 接口
10 9 *
11   - * 提供用户的账号密码登录、token 的校验等认证相关的功能
  10 + * 提供用户的登录、登出的能力
12 11 *
13 12 * @author 芋道源码
14 13 */
15   -public interface AdminAuthService extends SecurityAuthFrameworkService {
  14 +public interface AdminAuthService {
16 15  
17 16 /**
18 17 * 账号登录
19 18 *
20 19 * @param reqVO 登录信息
21   - * @param userIp 用户 IP
22   - * @param userAgent 用户 UA
23   - * @return 身份令牌,使用 JWT 方式
  20 + * @return 登录结果
24 21 */
25   - String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent);
  22 + AuthLoginRespVO login(@Valid AuthLoginReqVO reqVO);
  23 +
  24 + /**
  25 + * 基于 token 退出登录
  26 + *
  27 + * @param token token
  28 + * @param logType 登出类型
  29 + */
  30 + void logout(String token, Integer logType);
26 31  
27 32 /**
28 33 * 短信验证码发送
... ... @@ -35,30 +40,32 @@ public interface AdminAuthService extends SecurityAuthFrameworkService {
35 40 * 短信登录
36 41 *
37 42 * @param reqVO 登录信息
38   - * @param userIp 用户 IP
39   - * @param userAgent 用户 UA
40   - * @return 身份令牌,使用 JWT 方式
  43 + * @return 登录结果
41 44 */
42   - String smsLogin(AuthSmsLoginReqVO reqVO, String userIp, String userAgent) ;
  45 + AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) ;
43 46  
44 47 /**
45 48 * 社交快捷登录,使用 code 授权码
46 49 *
47 50 * @param reqVO 登录信息
48   - * @param userIp 用户 IP
49   - * @param userAgent 用户 UA
50   - * @return 身份令牌,使用 JWT 方式
  51 + * @return 登录结果
51 52 */
52   - String socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
  53 + AuthLoginRespVO socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO);
53 54  
54 55 /**
55 56 * 社交绑定登录,使用 code 授权码 + 账号密码
56 57 *
57 58 * @param reqVO 登录信息
58   - * @param userIp 用户 IP
59   - * @param userAgent 用户 UA
60   - * @return 身份令牌,使用 JWT 方式
  59 + * @return 登录结果
  60 + */
  61 + AuthLoginRespVO socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO);
  62 +
  63 + /**
  64 + * 刷新访问令牌
  65 + *
  66 + * @param refreshToken 刷新令牌
  67 + * @return 登录结果
61 68 */
62   - String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
  69 + AuthLoginRespVO refreshToken(String refreshToken);
63 70  
64 71 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
1 1 package cn.iocoder.yudao.module.system.service.auth;
2 2  
  3 +import cn.hutool.core.util.ObjectUtil;
3 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 5 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 6 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
6 7 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
7 8 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
8   -import cn.iocoder.yudao.framework.security.core.LoginUser;
9   -import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
10 9 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
11 10 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
12 11 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
13 12 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
  13 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
14 14 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
  15 +import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientIdEnum;
15 16 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
16 17 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
17 18 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
18 19 import cn.iocoder.yudao.module.system.service.common.CaptchaService;
19 20 import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
20   -import cn.iocoder.yudao.module.system.service.permission.PermissionService;
  21 +import cn.iocoder.yudao.module.system.service.member.MemberService;
21 22 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
22 23 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
  24 +import com.google.common.annotations.VisibleForTesting;
23 25 import lombok.extern.slf4j.Slf4j;
24   -import org.springframework.beans.factory.annotation.Autowired;
25   -import org.springframework.context.annotation.Lazy;
26   -import org.springframework.security.authentication.AuthenticationManager;
27   -import org.springframework.security.authentication.BadCredentialsException;
28   -import org.springframework.security.authentication.DisabledException;
29   -import org.springframework.security.core.Authentication;
30   -import org.springframework.security.core.AuthenticationException;
31   -import org.springframework.security.core.userdetails.UserDetails;
32   -import org.springframework.security.core.userdetails.UsernameNotFoundException;
33 26 import org.springframework.stereotype.Service;
34   -import org.springframework.util.Assert;
35 27  
36 28 import javax.annotation.Resource;
37 29 import javax.validation.Validator;
38 30 import java.util.Objects;
39   -import java.util.Set;
40 31  
41 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
42 33 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
43 34 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
44   -import static java.util.Collections.singleton;
45 35  
46 36 /**
47 37 * Auth Service 实现类
... ... @@ -53,22 +43,17 @@ import static java.util.Collections.singleton;
53 43 public class AdminAuthServiceImpl implements AdminAuthService {
54 44  
55 45 @Resource
56   - @Lazy // 延迟加载,因为存在相互依赖的问题
57   - private AuthenticationManager authenticationManager;
58   -
59   - @Autowired
60   - @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") // UserService 存在重名
61 46 private AdminUserService userService;
62 47 @Resource
63   - private PermissionService permissionService;
64   - @Resource
65 48 private CaptchaService captchaService;
66 49 @Resource
67 50 private LoginLogService loginLogService;
68 51 @Resource
69   - private UserSessionService userSessionService;
  52 + private OAuth2TokenService oauth2TokenService;
70 53 @Resource
71 54 private SocialUserService socialUserService;
  55 + @Resource
  56 + private MemberService memberService;
72 57  
73 58 @Resource
74 59 private Validator validator;
... ... @@ -77,39 +62,15 @@ public class AdminAuthServiceImpl implements AdminAuthService {
77 62 private SmsCodeApi smsCodeApi;
78 63  
79 64 @Override
80   - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
81   - // 获取 username 对应的 AdminUserDO
82   - AdminUserDO user = userService.getUserByUsername(username);
83   - if (user == null) {
84   - throw new UsernameNotFoundException(username);
85   - }
86   - // 创建 LoginUser 对象
87   - return buildLoginUser(user);
88   - }
89   -
90   - @Override
91   - public LoginUser mockLogin(Long userId) {
92   - // 获取用户编号对应的 AdminUserDO
93   - AdminUserDO user = userService.getUser(userId);
94   - if (user == null) {
95   - throw new UsernameNotFoundException(String.valueOf(userId));
96   - }
97   - createLoginLog(user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS);
98   -
99   - // 创建 LoginUser 对象
100   - return buildLoginUser(user);
101   - }
102   -
103   - @Override
104   - public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) {
  65 + public AuthLoginRespVO login(AuthLoginReqVO reqVO) {
105 66 // 判断验证码是否正确
106 67 verifyCaptcha(reqVO);
107 68  
108 69 // 使用账号密码,进行登录
109   - LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword());
  70 + AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword());
110 71  
111   - // 缓存登陆用户到 Redis 中,返回 Token 令牌
112   - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
  72 + // 创建 Token 令牌,记录登录日志
  73 + return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
113 74 }
114 75  
115 76 @Override
... ... @@ -123,9 +84,9 @@ public class AdminAuthServiceImpl implements AdminAuthService {
123 84 }
124 85  
125 86 @Override
126   - public String smsLogin(AuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
  87 + public AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) {
127 88 // 校验验证码
128   - smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), userIp));
  89 + smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), getClientIP()));
129 90  
130 91 // 获得用户信息
131 92 AdminUserDO user = userService.getUserByMobile(reqVO.getMobile());
... ... @@ -133,14 +94,12 @@ public class AdminAuthServiceImpl implements AdminAuthService {
133 94 throw exception(USER_NOT_EXISTS);
134 95 }
135 96  
136   - // 创建 LoginUser 对象
137   - LoginUser loginUser = buildLoginUser(user);
138   -
139 97 // 缓存登陆用户到 Redis 中,返回 sessionId 编号
140   - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_MOBILE, userIp, userAgent);
  98 + return createTokenAfterLoginSuccess(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
141 99 }
142 100  
143   - private void verifyCaptcha(AuthLoginReqVO reqVO) {
  101 + @VisibleForTesting
  102 + void verifyCaptcha(AuthLoginReqVO reqVO) {
144 103 // 如果验证码关闭,则不进行校验
145 104 if (!captchaService.isCaptchaEnable()) {
146 105 return;
... ... @@ -152,53 +111,47 @@ public class AdminAuthServiceImpl implements AdminAuthService {
152 111 String code = captchaService.getCaptchaCode(reqVO.getUuid());
153 112 if (code == null) {
154 113 // 创建登录失败日志(验证码不存在)
155   - this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND);
  114 + createLoginLog(null, reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND);
156 115 throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND);
157 116 }
158 117 // 验证码不正确
159 118 if (!code.equals(reqVO.getCode())) {
160 119 // 创建登录失败日志(验证码不正确)
161   - this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR);
  120 + createLoginLog(null, reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR);
162 121 throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR);
163 122 }
164 123 // 正确,所以要删除下验证码
165 124 captchaService.deleteCaptchaCode(reqVO.getUuid());
166 125 }
167 126  
168   - private LoginUser login0(String username, String password) {
  127 + @VisibleForTesting
  128 + AdminUserDO login0(String username, String password) {
169 129 final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
170   - // 用户验证
171   - Authentication authentication;
172   - try {
173   - // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
174   - // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
175   - authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
176   - username, password, getUserType()));
177   - } catch (BadCredentialsException badCredentialsException) {
178   - this.createLoginLog(username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
  130 + // 校验账号是否存在
  131 + AdminUserDO user = userService.getUserByUsername(username);
  132 + if (user == null) {
  133 + createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
179 134 throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
180   - } catch (DisabledException disabledException) {
181   - this.createLoginLog(username, logTypeEnum, LoginResultEnum.USER_DISABLED);
  135 + }
  136 + if (!userService.isPasswordMatch(password, user.getPassword())) {
  137 + createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
  138 + throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
  139 + }
  140 + // 校验是否禁用
  141 + if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
  142 + createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED);
182 143 throw exception(AUTH_LOGIN_USER_DISABLED);
183   - } catch (AuthenticationException authenticationException) {
184   - log.error("[login0][username({}) 发生未知异常]", username, authenticationException);
185   - this.createLoginLog(username, logTypeEnum, LoginResultEnum.UNKNOWN_ERROR);
186   - throw exception(AUTH_LOGIN_FAIL_UNKNOWN);
187 144 }
188   - Assert.notNull(authentication.getPrincipal(), "Principal 不会为空");
189   - return (LoginUser) authentication.getPrincipal();
  145 + return user;
190 146 }
191 147  
192   - private void createLoginLog(String username, LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {
193   - // 获得用户
194   - AdminUserDO user = userService.getUserByUsername(username);
  148 + private void createLoginLog(Long userId, String username,
  149 + LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {
195 150 // 插入登录日志
196 151 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
197 152 reqDTO.setLogType(logTypeEnum.getType());
198 153 reqDTO.setTraceId(TracerUtils.getTraceId());
199   - if (user != null) {
200   - reqDTO.setUserId(user.getId());
201   - }
  154 + reqDTO.setUserId(userId);
202 155 reqDTO.setUserType(getUserType().getValue());
203 156 reqDTO.setUsername(username);
204 157 reqDTO.setUserAgent(ServletUtils.getUserAgent());
... ... @@ -206,23 +159,13 @@ public class AdminAuthServiceImpl implements AdminAuthService {
206 159 reqDTO.setResult(loginResult.getResult());
207 160 loginLogService.createLoginLog(reqDTO);
208 161 // 更新最后登录时间
209   - if (user != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
210   - userService.updateUserLogin(user.getId(), ServletUtils.getClientIP());
  162 + if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
  163 + userService.updateUserLogin(userId, ServletUtils.getClientIP());
211 164 }
212 165 }
213 166  
214   - /**
215   - * 获得 User 拥有的角色编号数组
216   - *
217   - * @param userId 用户编号
218   - * @return 角色编号数组
219   - */
220   - private Set<Long> getUserRoleIds(Long userId) {
221   - return permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
222   - }
223   -
224 167 @Override
225   - public String socialQuickLogin(AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) {
  168 + public AuthLoginRespVO socialQuickLogin(AuthSocialQuickLoginReqVO reqVO) {
226 169 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
227 170 Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
228 171 reqVO.getCode(), reqVO.getState());
... ... @@ -230,105 +173,82 @@ public class AdminAuthServiceImpl implements AdminAuthService {
230 173 throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
231 174 }
232 175  
233   - // 自动登录
  176 + // 获得用户
234 177 AdminUserDO user = userService.getUser(userId);
235 178 if (user == null) {
236 179 throw exception(USER_NOT_EXISTS);
237 180 }
238 181  
239   - // 创建 LoginUser 对象
240   - LoginUser loginUser = buildLoginUser(user);
241   -
242   - // 缓存登录用户到 Redis 中,返回 Token 令牌
243   - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
  182 + // 创建 Token 令牌,记录登录日志
  183 + return createTokenAfterLoginSuccess(user.getId(), null, LoginLogTypeEnum.LOGIN_SOCIAL);
244 184 }
245 185  
246 186 @Override
247   - public String socialBindLogin(AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) {
  187 + public AuthLoginRespVO socialBindLogin(AuthSocialBindLoginReqVO reqVO) {
248 188 // 使用账号密码,进行登录。
249   - LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword());
  189 + AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword());
250 190  
251 191 // 绑定社交用户
252   - socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
  192 + socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(user.getId(), getUserType().getValue(), reqVO));
253 193  
254   - // 缓存登录用户到 Redis 中,返回 Token 令牌
255   - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
  194 + // 创建 Token 令牌,记录登录日志
  195 + return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL);
256 196 }
257 197  
258   - private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) {
  198 + @Override
  199 + public AuthLoginRespVO refreshToken(String refreshToken) {
  200 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientIdEnum.DEFAULT.getId());
  201 + return AuthConvert.INSTANCE.convert(accessTokenDO);
  202 + }
  203 +
  204 + private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) {
259 205 // 插入登陆日志
260   - createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS);
261   - // 缓存登录用户到 Redis 中,返回 Token 令牌
262   - return userSessionService.createUserSession(loginUser, userIp, userAgent);
  206 + createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
  207 + // 创建访问令牌
  208 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
  209 + OAuth2ClientIdEnum.DEFAULT.getId());
  210 + // 构建返回结果
  211 + return AuthConvert.INSTANCE.convert(accessTokenDO);
263 212 }
264 213  
265 214 @Override
266   - public void logout(String token) {
267   - // 查询用户信息
268   - LoginUser loginUser = userSessionService.getLoginUser(token);
269   - if (loginUser == null) {
  215 + public void logout(String token, Integer logType) {
  216 + // 删除访问令牌
  217 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token);
  218 + if (accessTokenDO == null) {
270 219 return;
271 220 }
272   - // 删除 session
273   - userSessionService.deleteUserSession(token);
274   - // 记录登出日志
275   - createLogoutLog(loginUser.getId(), loginUser.getUsername());
276   - }
277   -
278   - @Override
279   - public UserTypeEnum getUserType() {
280   - return UserTypeEnum.ADMIN;
  221 + // 删除成功,则记录登出日志
  222 + createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType);
281 223 }
282 224  
283   - private void createLogoutLog(Long userId, String username) {
  225 + private void createLogoutLog(Long userId, Integer userType, Integer logType) {
284 226 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
285   - reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
  227 + reqDTO.setLogType(logType);
286 228 reqDTO.setTraceId(TracerUtils.getTraceId());
287 229 reqDTO.setUserId(userId);
288   - reqDTO.setUserType(getUserType().getValue());
289   - reqDTO.setUsername(username);
  230 + reqDTO.setUserType(userType);
  231 + if (ObjectUtil.notEqual(getUserType(), userType)) {
  232 + reqDTO.setUsername(getUsername(userId));
  233 + } else {
  234 + reqDTO.setUsername(memberService.getMemberUserMobile(userId));
  235 + }
290 236 reqDTO.setUserAgent(ServletUtils.getUserAgent());
291 237 reqDTO.setUserIp(ServletUtils.getClientIP());
292 238 reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
293 239 loginLogService.createLoginLog(reqDTO);
294 240 }
295 241  
296   - @Override
297   - public LoginUser verifyTokenAndRefresh(String token) {
298   - // 获得 LoginUser
299   - LoginUser loginUser = userSessionService.getLoginUser(token);
300   - if (loginUser == null) {
  242 + private String getUsername(Long userId) {
  243 + if (userId == null) {
301 244 return null;
302 245 }
303   - // 刷新 LoginUser 缓存
304   - return this.refreshLoginUserCache(token, loginUser);
305   - }
306   -
307   - private LoginUser refreshLoginUserCache(String token, LoginUser loginUser) {
308   - // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
309   - if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
310   - userSessionService.getSessionTimeoutMillis() / 3) {
311   - return loginUser;
312   - }
313   -
314   - // 重新加载 AdminUserDO 信息
315   - AdminUserDO user = userService.getUser(loginUser.getId());
316   - if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
317   - throw exception(AUTH_TOKEN_EXPIRED); // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面
318   - }
319   -
320   - // 刷新 LoginUser 缓存
321   - LoginUser newLoginUser= this.buildLoginUser(user);
322   - userSessionService.refreshUserSession(token, newLoginUser);
323   - return newLoginUser;
  246 + AdminUserDO user = userService.getUser(userId);
  247 + return user != null ? user.getUsername() : null;
324 248 }
325 249  
326   - private LoginUser buildLoginUser(AdminUserDO user) {
327   - LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
328   - // 补全字段
329   - loginUser.setDeptId(user.getDeptId());
330   - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId()));
331   - return loginUser;
  250 + private UserTypeEnum getUserType() {
  251 + return UserTypeEnum.ADMIN;
332 252 }
333 253  
334 254 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminOAuth2Service.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +/**
  4 + * 管理后台的 OAuth2 Service 接口
  5 + *
  6 + * 将自身的 AdminUser 用户,授权给第三方应用,采用 OAuth2.0 的协议。
  7 + *
  8 + * 问题:为什么自身也作为一个第三方应用,也走这套流程呢?
  9 + * 回复:当然可以这么做,采用 Implicit 模式。考虑到大多数开发者使用不到这个特性,OAuth2.0 毕竟有一定学习成本,所以暂时没有采取这种方式。
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +public interface AdminOAuth2Service {
  14 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
  5 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
  6 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
  8 +
  9 +import javax.validation.Valid;
  10 +
  11 +/**
  12 + * OAuth2.0 Client Service 接口
  13 + *
  14 + * 从功能上,和 JdbcClientDetailsService 的功能,提供客户端的操作
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +public interface OAuth2ClientService {
  19 +
  20 + /**
  21 + * 创建OAuth2 客户端
  22 + *
  23 + * @param createReqVO 创建信息
  24 + * @return 编号
  25 + */
  26 + Long createOAuth2Client(@Valid OAuth2ClientCreateReqVO createReqVO);
  27 +
  28 + /**
  29 + * 更新OAuth2 客户端
  30 + *
  31 + * @param updateReqVO 更新信息
  32 + */
  33 + void updateOAuth2Client(@Valid OAuth2ClientUpdateReqVO updateReqVO);
  34 +
  35 + /**
  36 + * 删除OAuth2 客户端
  37 + *
  38 + * @param id 编号
  39 + */
  40 + void deleteOAuth2Client(Long id);
  41 +
  42 + /**
  43 + * 获得OAuth2 客户端
  44 + *
  45 + * @param id 编号
  46 + * @return OAuth2 客户端
  47 + */
  48 + OAuth2ClientDO getOAuth2Client(Long id);
  49 +
  50 + /**
  51 + * 获得OAuth2 客户端分页
  52 + *
  53 + * @param pageReqVO 分页查询
  54 + * @return OAuth2 客户端分页
  55 + */
  56 + PageResult<OAuth2ClientDO> getOAuth2ClientPage(OAuth2ClientPageReqVO pageReqVO);
  57 +
  58 + /**
  59 + * 从缓存中,校验客户端是否合法
  60 + *
  61 + * @param id 客户端编号
  62 + * @return 客户端
  63 + */
  64 + OAuth2ClientDO validOAuthClientFromCache(Long id);
  65 +
  66 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
  5 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
  6 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
  7 +import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert;
  8 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
  9 +import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper;
  10 +import org.springframework.stereotype.Service;
  11 +
  12 +import javax.annotation.Resource;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  15 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS;
  16 +
  17 +/**
  18 + * OAuth2.0 Client Service 实现类
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +@Service
  23 +public class OAuth2ClientServiceImpl implements OAuth2ClientService {
  24 +
  25 + @Resource
  26 + private OAuth2ClientMapper oauth2ClientMapper;
  27 +
  28 + @Override
  29 + public Long createOAuth2Client(OAuth2ClientCreateReqVO createReqVO) {
  30 + // 插入
  31 + OAuth2ClientDO oAuth2Client = OAuth2ClientConvert.INSTANCE.convert(createReqVO);
  32 + oauth2ClientMapper.insert(oAuth2Client);
  33 + // 返回
  34 + return oAuth2Client.getId();
  35 + }
  36 +
  37 + @Override
  38 + public void updateOAuth2Client(OAuth2ClientUpdateReqVO updateReqVO) {
  39 + // 校验存在
  40 + this.validateOAuth2ClientExists(updateReqVO.getId());
  41 + // 更新
  42 + OAuth2ClientDO updateObj = OAuth2ClientConvert.INSTANCE.convert(updateReqVO);
  43 + oauth2ClientMapper.updateById(updateObj);
  44 + }
  45 +
  46 + @Override
  47 + public void deleteOAuth2Client(Long id) {
  48 + // 校验存在
  49 + this.validateOAuth2ClientExists(id);
  50 + // 删除
  51 + oauth2ClientMapper.deleteById(id);
  52 + }
  53 +
  54 + private void validateOAuth2ClientExists(Long id) {
  55 + if (oauth2ClientMapper.selectById(id) == null) {
  56 + throw exception(OAUTH2_CLIENT_NOT_EXISTS);
  57 + }
  58 + }
  59 +
  60 + @Override
  61 + public OAuth2ClientDO getOAuth2Client(Long id) {
  62 + return oauth2ClientMapper.selectById(id);
  63 + }
  64 +
  65 + @Override
  66 + public PageResult<OAuth2ClientDO> getOAuth2ClientPage(OAuth2ClientPageReqVO pageReqVO) {
  67 + return oauth2ClientMapper.selectPage(pageReqVO);
  68 + }
  69 +
  70 + @Override
  71 + public OAuth2ClientDO validOAuthClientFromCache(Long id) {
  72 + return new OAuth2ClientDO().setId(id)
  73 + .setAccessTokenValiditySeconds(60 * 30)
  74 + .setRefreshTokenValiditySeconds(60 * 60 * 24 * 30);
  75 + }
  76 +
  77 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2CodeService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +/**
  4 + * OAuth2.0 授权码 Service 接口
  5 + *
  6 + * 从功能上,和 Spring Security OAuth 的 JdbcAuthorizationCodeServices 的功能,提供授权码的操作
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +public class OAuth2CodeService {
  11 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
  5 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  6 +
  7 +/**
  8 + * OAuth2.0 Token Service 接口
  9 + *
  10 + * 从功能上,和 Spring Security OAuth 的 DefaultTokenServices + JdbcTokenStore 的功能,提供访问令牌、刷新令牌的操作
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public interface OAuth2TokenService {
  15 +
  16 + /**
  17 + * 创建访问令牌
  18 + * 注意:该流程中,会包含创建刷新令牌的创建
  19 + *
  20 + * 参考 DefaultTokenServices 的 createAccessToken 方法
  21 + *
  22 + * @param userId 用户编号
  23 + * @param userType 用户类型
  24 + * @param clientId 客户端编号
  25 + * @return 访问令牌的信息
  26 + */
  27 + OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId);
  28 +
  29 + /**
  30 + * 刷新访问令牌
  31 + *
  32 + * 参考 DefaultTokenServices 的 refreshAccessToken 方法
  33 + *
  34 + * @param refreshToken 刷新令牌
  35 + * @param clientId 客户端编号
  36 + * @return 访问令牌的信息
  37 + */
  38 + OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId);
  39 +
  40 + /**
  41 + * 获得访问令牌
  42 + *
  43 + * 参考 DefaultTokenServices 的 getAccessToken 方法
  44 + *
  45 + * @param accessToken 访问令牌
  46 + * @return 访问令牌的信息
  47 + */
  48 + OAuth2AccessTokenDO getAccessToken(String accessToken);
  49 +
  50 + /**
  51 + * 校验访问令牌
  52 + *
  53 + * @param accessToken 访问令牌
  54 + * @return 访问令牌的信息
  55 + */
  56 + OAuth2AccessTokenDO checkAccessToken(String accessToken);
  57 +
  58 + /**
  59 + * 移除访问令牌
  60 + * 注意:该流程中,会移除相关的刷新令牌
  61 + *
  62 + * 参考 DefaultTokenServices 的 revokeToken 方法
  63 + *
  64 + * @param accessToken 刷新令牌
  65 + * @return 访问令牌的信息
  66 + */
  67 + OAuth2AccessTokenDO removeAccessToken(String accessToken);
  68 +
  69 + /**
  70 + * 获得访问令牌分页
  71 + *
  72 + * @param reqVO 请求
  73 + * @return 访问令牌分页
  74 + */
  75 + PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO);
  76 +
  77 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.IdUtil;
  5 +import cn.hutool.core.util.ObjectUtil;
  6 +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
  7 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.date.DateUtils;
  9 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  10 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
  11 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  12 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
  13 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO;
  14 +import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2AccessTokenMapper;
  15 +import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2RefreshTokenMapper;
  16 +import cn.iocoder.yudao.module.system.dal.redis.auth.OAuth2AccessTokenRedisDAO;
  17 +import org.springframework.stereotype.Service;
  18 +import org.springframework.transaction.annotation.Transactional;
  19 +
  20 +import javax.annotation.Resource;
  21 +import java.util.Calendar;
  22 +import java.util.List;
  23 +
  24 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  25 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  26 +
  27 +/**
  28 + * OAuth2.0 Token Service 实现类
  29 + *
  30 + * @author 芋道源码
  31 + */
  32 +@Service
  33 +public class OAuth2TokenServiceImpl implements OAuth2TokenService {
  34 +
  35 + @Resource
  36 + private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
  37 + @Resource
  38 + private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;
  39 +
  40 + @Resource
  41 + private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO;
  42 +
  43 + @Resource
  44 + private OAuth2ClientService oauth2ClientService;
  45 +
  46 + @Override
  47 + @Transactional
  48 + public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId) {
  49 + OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
  50 + // 创建刷新令牌
  51 + OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO);
  52 + // 创建访问令牌
  53 + return createOAuth2AccessToken(refreshTokenDO, clientDO);
  54 + }
  55 +
  56 + @Override
  57 + public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId) {
  58 + // 查询访问令牌
  59 + OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken);
  60 + if (refreshTokenDO == null) {
  61 + throw exception(GlobalErrorCodeConstants.BAD_REQUEST, "无效的刷新令牌");
  62 + }
  63 +
  64 + // 校验 Client 匹配
  65 + OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
  66 + if (ObjectUtil.notEqual(clientId, refreshTokenDO.getClientId())) {
  67 + throw exception(GlobalErrorCodeConstants.BAD_REQUEST, "刷新令牌的客户端编号不正确");
  68 + }
  69 +
  70 + // 移除相关的访问令牌
  71 + List<OAuth2AccessTokenDO> accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshToken);
  72 + if (CollUtil.isNotEmpty(accessTokenDOs)) {
  73 + oauth2AccessTokenMapper.deleteBatchIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId));
  74 + oauth2AccessTokenRedisDAO.deleteList(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getAccessToken));
  75 + }
  76 +
  77 + // 已过期的情况下,删除刷新令牌
  78 + if (DateUtils.isExpired(refreshTokenDO.getExpiresTime())) {
  79 + oauth2AccessTokenMapper.deleteById(refreshTokenDO.getId());
  80 + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "刷新令牌已过期");
  81 + }
  82 +
  83 + // 创建访问令牌
  84 + return createOAuth2AccessToken(refreshTokenDO, clientDO);
  85 + }
  86 +
  87 + @Override
  88 + public OAuth2AccessTokenDO getAccessToken(String accessToken) {
  89 + // 优先从 Redis 中获取
  90 + OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken);
  91 + if (accessTokenDO != null) {
  92 + return accessTokenDO;
  93 + }
  94 +
  95 + // 获取不到,从 MySQL 中获取
  96 + accessTokenDO = oauth2AccessTokenMapper.selectById(accessToken);
  97 + // 如果在 MySQL 存在,则往 Redis 中写入
  98 + if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
  99 + oauth2AccessTokenRedisDAO.set(accessTokenDO);
  100 + }
  101 + return accessTokenDO;
  102 + }
  103 +
  104 + @Override
  105 + public OAuth2AccessTokenDO checkAccessToken(String accessToken) {
  106 + OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken);
  107 + if (accessTokenDO == null) {
  108 + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌不存在");
  109 + }
  110 + if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
  111 + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌已过期");
  112 + }
  113 + return accessTokenDO;
  114 + }
  115 +
  116 + @Override
  117 + public OAuth2AccessTokenDO removeAccessToken(String accessToken) {
  118 + // 删除访问令牌
  119 + OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken);
  120 + if (accessTokenDO == null) {
  121 + return null;
  122 + }
  123 + oauth2AccessTokenMapper.deleteById(accessTokenDO.getId());
  124 + oauth2AccessTokenRedisDAO.delete(accessToken);
  125 + // 删除刷新令牌
  126 + oauth2RefreshTokenMapper.deleteByRefreshToken(accessTokenDO.getRefreshToken());
  127 + return accessTokenDO;
  128 + }
  129 +
  130 + @Override
  131 + public PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO) {
  132 + return oauth2AccessTokenMapper.selectPage(reqVO);
  133 + }
  134 +
  135 + private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
  136 + OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())
  137 + .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getId())
  138 + .setRefreshToken(refreshTokenDO.getRefreshToken())
  139 + .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds()));
  140 + accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
  141 + oauth2AccessTokenMapper.insert(accessTokenDO);
  142 + // 记录到 Redis 中
  143 + oauth2AccessTokenRedisDAO.set(accessTokenDO);
  144 + return accessTokenDO;
  145 + }
  146 +
  147 + private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) {
  148 + OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken())
  149 + .setUserId(userId).setUserType(userType).setClientId(clientDO.getId())
  150 + .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getRefreshTokenValiditySeconds()));
  151 + oauth2RefreshTokenMapper.insert(refreshToken);
  152 + return refreshToken;
  153 + }
  154 +
  155 + private static String generateAccessToken() {
  156 + return IdUtil.fastSimpleUUID();
  157 + }
  158 +
  159 + private static String generateRefreshToken() {
  160 + return IdUtil.fastSimpleUUID();
  161 + }
  162 +
  163 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java deleted
1   -package cn.iocoder.yudao.module.system.service.auth;
2   -
3   -import cn.iocoder.yudao.framework.security.core.LoginUser;
4   -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
5   -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
6   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
7   -
8   -/**
9   - * 在线用户 Session Service 接口
10   - *
11   - * @author 芋道源码
12   - */
13   -public interface UserSessionService {
14   -
15   - /**
16   - * 获得在线用户分页列表
17   - *
18   - * @param reqVO 分页条件
19   - * @return 份额与列表
20   - */
21   - PageResult<UserSessionDO> getUserSessionPage(UserSessionPageReqVO reqVO);
22   -
23   - /**
24   - * 移除超时的在线用户
25   - *
26   - * @return {@link Long } 移出的超时用户数量
27   - **/
28   - long deleteTimeoutSession();
29   -
30   - /**
31   - * 创建在线用户 Session
32   - *
33   - * @param loginUser 登录用户
34   - * @param userIp 用户 IP
35   - * @param userAgent 用户 UA
36   - * @return Token 令牌
37   - */
38   - String createUserSession(LoginUser loginUser, String userIp, String userAgent);
39   -
40   - /**
41   - * 刷新在线用户 Session 的更新时间
42   - *
43   - * @param token 令牌
44   - * @param loginUser 登录用户
45   - */
46   - void refreshUserSession(String token, LoginUser loginUser);
47   -
48   - /**
49   - * 删除在线用户 Session
50   - *
51   - * @param token token 令牌
52   - */
53   - void deleteUserSession(String token);
54   -
55   - /**
56   - * 删除在线用户 Session
57   - *
58   - * @param id 编号
59   - */
60   - void deleteUserSession(Long id);
61   -
62   - /**
63   - * 获得 Token 对应的在线用户
64   - *
65   - * @param token 令牌
66   - * @return 在线用户
67   - */
68   - LoginUser getLoginUser(String token);
69   -
70   - /**
71   - * 获得 Session 超时时间,单位:毫秒
72   - *
73   - * @return 超时时间
74   - */
75   - Long getSessionTimeoutMillis();
76   -
77   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.system.service.auth;
2   -
3   -import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.util.IdUtil;
5   -import cn.hutool.core.util.StrUtil;
6   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
7   -import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
8   -import cn.iocoder.yudao.framework.security.config.SecurityProperties;
9   -import cn.iocoder.yudao.framework.security.core.LoginUser;
10   -import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
11   -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
12   -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
13   -import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
14   -import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper;
15   -import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO;
16   -import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
17   -import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
18   -import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
19   -import cn.iocoder.yudao.module.system.service.user.AdminUserService;
20   -import lombok.extern.slf4j.Slf4j;
21   -import org.springframework.stereotype.Service;
22   -
23   -import javax.annotation.Resource;
24   -import java.time.Duration;
25   -import java.util.Collection;
26   -import java.util.Date;
27   -import java.util.List;
28   -
29   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
30   -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
31   -
32   -/**
33   - * 在线用户 Session Service 实现类
34   - *
35   - * @author 芋道源码
36   - */
37   -@Slf4j
38   -@Service
39   -public class UserSessionServiceImpl implements UserSessionService {
40   -
41   - @Resource
42   - private UserSessionMapper userSessionMapper;
43   -
44   - @Resource
45   - private AdminUserService userService;
46   - @Resource
47   - private LoginLogService loginLogService;
48   -
49   - @Resource
50   - private LoginUserRedisDAO loginUserRedisDAO;
51   -
52   - @Resource
53   - private SecurityProperties securityProperties;
54   -
55   - @Override
56   - public PageResult<UserSessionDO> getUserSessionPage(UserSessionPageReqVO reqVO) {
57   - // 处理基于用户昵称的查询
58   - Collection<Long> userIds = null;
59   - if (StrUtil.isNotEmpty(reqVO.getUsername())) {
60   - userIds = convertSet(userService.getUsersByUsername(reqVO.getUsername()), AdminUserDO::getId);
61   - if (CollUtil.isEmpty(userIds)) {
62   - return PageResult.empty();
63   - }
64   - }
65   - return userSessionMapper.selectPage(reqVO, userIds);
66   - }
67   -
68   - @Override
69   - public long deleteTimeoutSession() {
70   - // 获取 db 里已经超时的用户列表
71   - List<UserSessionDO> timeoutSessions = userSessionMapper.selectListBySessionTimoutLt();
72   - if (CollUtil.isEmpty(timeoutSessions)) {
73   - return 0L;
74   - }
75   -
76   - // 由于过期的用户一般不多,所以顺序遍历,进行清理
77   - int count = 0;
78   - for (UserSessionDO session : timeoutSessions) {
79   - // 基于 Redis 二次判断,同时也保证 Redis Key 的立即过期,避免延迟导致浪费内存空间
80   - if (loginUserRedisDAO.exists(session.getToken())) {
81   - continue;
82   - }
83   - userSessionMapper.deleteById(session.getId());
84   - // 记录退出日志
85   - createLogoutLog(session, LoginLogTypeEnum.LOGOUT_TIMEOUT);
86   - count++;
87   - }
88   - return count;
89   - }
90   -
91   - private void createLogoutLog(UserSessionDO session, LoginLogTypeEnum type) {
92   - LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
93   - reqDTO.setLogType(type.getType());
94   - reqDTO.setTraceId(TracerUtils.getTraceId());
95   - reqDTO.setUserId(session.getUserId());
96   - reqDTO.setUserType(session.getUserType());
97   - reqDTO.setUsername(session.getUsername());
98   - reqDTO.setUserAgent(session.getUserAgent());
99   - reqDTO.setUserIp(session.getUserIp());
100   - reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
101   - loginLogService.createLoginLog(reqDTO);
102   - }
103   -
104   - @Override
105   - public String createUserSession(LoginUser loginUser, String userIp, String userAgent) {
106   - // 生成 Session 编号
107   - String token = generateToken();
108   - // 写入 Redis 缓存
109   - loginUser.setUpdateTime(new Date());
110   - loginUserRedisDAO.set(token, loginUser);
111   - // 写入 DB 中
112   - UserSessionDO userSession = UserSessionDO.builder().token(token)
113   - .userId(loginUser.getId()).userType(loginUser.getUserType())
114   - .userIp(userIp).userAgent(userAgent).username(loginUser.getUsername())
115   - .sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())))
116   - .build();
117   - userSessionMapper.insert(userSession);
118   - // 返回 Token 令牌
119   - return token;
120   - }
121   -
122   - @Override
123   - public void refreshUserSession(String token, LoginUser loginUser) {
124   - // 写入 Redis 缓存
125   - loginUser.setUpdateTime(new Date());
126   - loginUserRedisDAO.set(token, loginUser);
127   - // 更新 DB 中
128   - UserSessionDO updateObj = UserSessionDO.builder().build();
129   - updateObj.setUsername(loginUser.getUsername());
130   - updateObj.setUpdateTime(new Date());
131   - updateObj.setSessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())));
132   - userSessionMapper.updateByToken(token, updateObj);
133   - }
134   -
135   - @Override
136   - public void deleteUserSession(String token) {
137   - // 删除 Redis 缓存
138   - loginUserRedisDAO.delete(token);
139   - // 删除 DB 记录
140   - userSessionMapper.deleteByToken(token);
141   - // 无需记录日志,因为退出那已经记录
142   - }
143   -
144   - @Override
145   - public void deleteUserSession(Long id) {
146   - UserSessionDO session = userSessionMapper.selectById(id);
147   - if (session == null) {
148   - return;
149   - }
150   - // 删除 Redis 缓存
151   - loginUserRedisDAO.delete(session.getToken());
152   - // 删除 DB 记录
153   - userSessionMapper.deleteById(id);
154   - // 记录退出日志
155   - createLogoutLog(session, LoginLogTypeEnum.LOGOUT_DELETE);
156   - }
157   -
158   - @Override
159   - public LoginUser getLoginUser(String token) {
160   - return loginUserRedisDAO.get(token);
161   - }
162   -
163   - @Override
164   - public Long getSessionTimeoutMillis() {
165   - return securityProperties.getSessionTimeout().toMillis();
166   - }
167   -
168   - /**
169   - * 生成 Token 令牌,目前采用 UUID 算法
170   - *
171   - * @return Session 编号
172   - */
173   - private static String generateToken() {
174   - return IdUtil.fastSimpleUUID();
175   - }
176   -
177   -}
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;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.CollectionUtil;
5 5 import cn.hutool.core.util.ArrayUtil;
  6 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 8 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
8 9 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  10 +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
9 11 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
10   -import cn.iocoder.yudao.framework.security.core.LoginUser;
11   -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
12 12 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
13 13 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
14 14 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
... ... @@ -22,6 +22,8 @@ import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
22 22 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
23 23 import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
24 24 import cn.iocoder.yudao.module.system.service.dept.DeptService;
  25 +import cn.iocoder.yudao.module.system.service.user.AdminUserService;
  26 +import com.google.common.base.Suppliers;
25 27 import com.google.common.collect.ImmutableMultimap;
26 28 import com.google.common.collect.Multimap;
27 29 import com.google.common.collect.Sets;
... ... @@ -36,6 +38,10 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
36 38 import javax.annotation.PostConstruct;
37 39 import javax.annotation.Resource;
38 40 import java.util.*;
  41 +import java.util.function.Supplier;
  42 +
  43 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  44 +import static java.util.Collections.singleton;
39 45  
40 46 /**
41 47 * 权限 Service 实现类
... ... @@ -47,11 +53,6 @@ import java.util.*;
47 53 public class PermissionServiceImpl implements PermissionService {
48 54  
49 55 /**
50   - * LoginUser 的 Context 缓存 Key
51   - */
52   - public static final String CONTEXT_KEY = PermissionServiceImpl.class.getSimpleName();
53   -
54   - /**
55 56 * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
56 57 * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
57 58 */
... ... @@ -93,6 +94,8 @@ public class PermissionServiceImpl implements PermissionService {
93 94 private MenuService menuService;
94 95 @Resource
95 96 private DeptService deptService;
  97 + @Resource
  98 + private AdminUserService userService;
96 99  
97 100 @Resource
98 101 private PermissionProducer permissionProducer;
... ... @@ -319,7 +322,7 @@ public class PermissionServiceImpl implements PermissionService {
319 322 }
320 323  
321 324 // 获得当前登录的角色。如果为空,说明没有权限
322   - Set<Long> roleIds = SecurityFrameworkUtils.getLoginUserRoleIds();
  325 + Set<Long> roleIds = getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
323 326 if (CollUtil.isEmpty(roleIds)) {
324 327 return false;
325 328 }
... ... @@ -354,7 +357,7 @@ public class PermissionServiceImpl implements PermissionService {
354 357 }
355 358  
356 359 // 获得当前登录的角色。如果为空,说明没有权限
357   - Set<Long> roleIds = SecurityFrameworkUtils.getLoginUserRoleIds();
  360 + Set<Long> roleIds = getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
358 361 if (CollUtil.isEmpty(roleIds)) {
359 362 return false;
360 363 }
... ... @@ -368,16 +371,18 @@ public class PermissionServiceImpl implements PermissionService {
368 371 }
369 372  
370 373 @Override
371   - public DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser) {
372   - // 判断是否 context 已经缓存
373   - DeptDataPermissionRespDTO result = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
374   - if (result != null) {
  374 + @DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题
  375 + public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
  376 + DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
  377 + // 获得用户的角色
  378 + Set<Long> roleIds = getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
  379 + if (CollUtil.isEmpty(roleIds)) {
375 380 return result;
376 381 }
377   -
378   - // 创建 DeptDataPermissionRespDTO 对象
379   - result = new DeptDataPermissionRespDTO();
380   - List<RoleDO> roles = roleService.getRolesFromCache(loginUser.getRoleIds());
  382 + List<RoleDO> roles = roleService.getRolesFromCache(roleIds);
  383 + // 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询
  384 + Supplier<Long> userDeptIdCache = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
  385 + // 遍历每个角色,计算
381 386 for (RoleDO role : roles) {
382 387 // 为空时,跳过
383 388 if (role.getDataScope() == null) {
... ... @@ -393,20 +398,20 @@ public class PermissionServiceImpl implements PermissionService {
393 398 CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
394 399 // 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
395 400 // 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
396   - CollUtil.addAll(result.getDeptIds(), loginUser.getDeptId());
  401 + CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
397 402 continue;
398 403 }
399 404 // 情况三,DEPT_ONLY
400 405 if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
401   - CollectionUtils.addIfNotNull(result.getDeptIds(), loginUser.getDeptId());
  406 + CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptIdCache.get());
402 407 continue;
403 408 }
404 409 // 情况四,DEPT_DEPT_AND_CHILD
405 410 if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
406   - List<DeptDO> depts = deptService.getDeptsByParentIdFromCache(loginUser.getDeptId(), true);
  411 + List<DeptDO> depts = deptService.getDeptsByParentIdFromCache(userDeptIdCache.get(), true);
407 412 CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, DeptDO::getId));
408   - //添加本身部门id
409   - CollUtil.addAll(result.getDeptIds(), loginUser.getDeptId());
  413 + // 添加本身部门编号
  414 + CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
410 415 continue;
411 416 }
412 417 // 情况五,SELF
... ... @@ -415,11 +420,8 @@ public class PermissionServiceImpl implements PermissionService {
415 420 continue;
416 421 }
417 422 // 未知情况,error log 即可
418   - log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", loginUser.getId(), JsonUtils.toJsonString(result));
  423 + log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, JsonUtils.toJsonString(result));
419 424 }
420   -
421   - // 添加到缓存,并返回
422   - loginUser.setContext(CONTEXT_KEY, result);
423 425 return result;
424 426 }
425 427  
... ...
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 {
81 81 @Getter
82 82 private volatile Date maxUpdateTime;
83 83  
  84 + @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
84 85 @Autowired(required = false) // 由于 yudao.tenant.enable 配置项,可以关闭多租户的功能,所以这里只能不强制注入
85 86 private TenantProperties tenantProperties;
86 87  
... ...
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 {
105 105 */
106 106 AdminUserDO getUserByMobile(String mobile);
107 107  
108   -
109 108 /**
110 109 * 获得用户分页列表
111 110 *
... ... @@ -209,4 +208,13 @@ public interface AdminUserService {
209 208 */
210 209 List<AdminUserDO> getUsersByStatus(Integer status);
211 210  
  211 + /**
  212 + * 判断密码是否匹配
  213 + *
  214 + * @param rawPassword 未加密的密码
  215 + * @param encodedPassword 加密后的密码
  216 + * @return 是否匹配
  217 + */
  218 + boolean isPasswordMatch(String rawPassword, String encodedPassword);
  219 +
212 220 }
... ...
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;
25 25 import com.google.common.annotations.VisibleForTesting;
26 26 import lombok.extern.slf4j.Slf4j;
27 27 import org.springframework.beans.factory.annotation.Value;
  28 +import org.springframework.context.annotation.Lazy;
28 29 import org.springframework.security.crypto.password.PasswordEncoder;
29 30 import org.springframework.stereotype.Service;
30 31 import org.springframework.transaction.annotation.Transactional;
... ... @@ -61,6 +62,7 @@ public class AdminUserServiceImpl implements AdminUserService {
61 62 @Resource
62 63 private PasswordEncoder passwordEncoder;
63 64 @Resource
  65 + @Lazy // 延迟,避免循环依赖报错
64 66 private TenantService tenantService;
65 67  
66 68 @Resource
... ... @@ -146,7 +148,7 @@ public class AdminUserServiceImpl implements AdminUserService {
146 148 checkOldPassword(id, reqVO.getOldPassword());
147 149 // 执行更新
148 150 AdminUserDO updateObj = new AdminUserDO().setId(id);
149   - updateObj.setPassword(passwordEncoder.encode(reqVO.getNewPassword())); // 加密密码
  151 + updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码
150 152 userMapper.updateById(updateObj);
151 153 }
152 154  
... ... @@ -170,7 +172,7 @@ public class AdminUserServiceImpl implements AdminUserService {
170 172 // 更新密码
171 173 AdminUserDO updateObj = new AdminUserDO();
172 174 updateObj.setId(id);
173   - updateObj.setPassword(passwordEncoder.encode(password)); // 加密密码
  175 + updateObj.setPassword(encodePassword(password)); // 加密密码
174 176 userMapper.updateById(updateObj);
175 177 }
176 178  
... ... @@ -203,11 +205,6 @@ public class AdminUserServiceImpl implements AdminUserService {
203 205 return userMapper.selectByUsername(username);
204 206 }
205 207  
206   - /**
207   - * 通过手机号获取用户
208   - * @param mobile
209   - * @return
210   - */
211 208 @Override
212 209 public AdminUserDO getUserByMobile(String mobile) {
213 210 return userMapper.selectByMobile(mobile);
... ... @@ -393,7 +390,7 @@ public class AdminUserServiceImpl implements AdminUserService {
393 390 if (user == null) {
394 391 throw exception(USER_NOT_EXISTS);
395 392 }
396   - if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
  393 + if (!isPasswordMatch(oldPassword, user.getPassword())) {
397 394 throw exception(USER_PASSWORD_FAILED);
398 395 }
399 396 }
... ... @@ -419,7 +416,7 @@ public class AdminUserServiceImpl implements AdminUserService {
419 416 AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
420 417 if (existUser == null) {
421 418 userMapper.insert(UserConvert.INSTANCE.convert(importUser)
422   - .setPassword(passwordEncoder.encode(userInitPassword))); // 设置默认密码
  419 + .setPassword(encodePassword(userInitPassword))); // 设置默认密码
423 420 respVO.getCreateUsernames().add(importUser.getUsername());
424 421 return;
425 422 }
... ... @@ -441,4 +438,19 @@ public class AdminUserServiceImpl implements AdminUserService {
441 438 return userMapper.selectListByStatus(status);
442 439 }
443 440  
  441 + @Override
  442 + public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
  443 + return passwordEncoder.matches(rawPassword, encodedPassword);
  444 + }
  445 +
  446 + /**
  447 + * 对密码进行加密
  448 + *
  449 + * @param password 密码
  450 + * @return 加密后的密码
  451 + */
  452 + private String encodePassword(String password) {
  453 + return passwordEncoder.encode(password);
  454 + }
  455 +
444 456 }
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java
1 1 package cn.iocoder.yudao.module.system.service.auth;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4   -import cn.iocoder.yudao.framework.security.core.LoginUser;
  4 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 5 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
6 6 import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
7 7 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
8 8 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO;
  9 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginRespVO;
  10 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
9 11 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
10 12 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
11 13 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
12 14 import cn.iocoder.yudao.module.system.service.common.CaptchaService;
13   -import cn.iocoder.yudao.module.system.service.dept.PostService;
14 15 import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
15   -import cn.iocoder.yudao.module.system.service.permission.PermissionService;
16 16 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
17 17 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
18 18 import org.junit.jupiter.api.BeforeEach;
19 19 import org.junit.jupiter.api.Test;
20 20 import org.springframework.boot.test.mock.mockito.MockBean;
21 21 import org.springframework.context.annotation.Import;
22   -import org.springframework.security.authentication.AuthenticationManager;
23   -import org.springframework.security.authentication.BadCredentialsException;
24   -import org.springframework.security.authentication.DisabledException;
25   -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
26   -import org.springframework.security.core.Authentication;
27   -import org.springframework.security.core.AuthenticationException;
28   -import org.springframework.security.core.userdetails.UsernameNotFoundException;
29 22  
30 23 import javax.annotation.Resource;
31 24 import javax.validation.Validator;
32   -import java.util.Set;
33 25  
  26 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
34 27 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
35   -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
  28 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  29 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
36 30 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
37   -import static java.util.Collections.singleton;
38   -import static org.junit.jupiter.api.Assertions.assertEquals;
39   -import static org.junit.jupiter.api.Assertions.assertThrows;
40 31 import static org.mockito.ArgumentMatchers.eq;
41 32 import static org.mockito.Mockito.*;
42 33  
... ... @@ -49,23 +40,15 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
49 40 @MockBean
50 41 private AdminUserService userService;
51 42 @MockBean
52   - private PermissionService permissionService;
53   - @MockBean
54   - private AuthenticationManager authenticationManager;
55   - @MockBean
56   - private Authentication authentication;
57   - @MockBean
58 43 private CaptchaService captchaService;
59 44 @MockBean
60 45 private LoginLogService loginLogService;
61 46 @MockBean
62   - private UserSessionService userSessionService;
63   - @MockBean
64 47 private SocialUserService socialService;
65 48 @MockBean
66   - private PostService postService;
67   - @MockBean
68 49 private SmsCodeApi smsCodeApi;
  50 + @MockBean
  51 + private OAuth2TokenService oauth2TokenService;
69 52  
70 53 @MockBean
71 54 private Validator validator;
... ... @@ -76,216 +59,184 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
76 59 }
77 60  
78 61 @Test
79   - public void testLoadUserByUsername_success() {
  62 + public void testLogin0_success() {
80 63 // 准备参数
81 64 String username = randomString();
82   - // mock 方法
83   - AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username));
  65 + String password = randomString();
  66 + // mock user 数据
  67 + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)
  68 + .setPassword(password).setStatus(CommonStatusEnum.ENABLE.getStatus()));
84 69 when(userService.getUserByUsername(eq(username))).thenReturn(user);
  70 + // mock password 匹配
  71 + when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true);
85 72  
86 73 // 调用
87   - LoginUser loginUser = (LoginUser) authService.loadUserByUsername(username);
  74 + AdminUserDO loginUser = authService.login0(username, password);
88 75 // 校验
89   - AssertUtils.assertPojoEquals(user, loginUser, "updateTime");
  76 + assertPojoEquals(user, loginUser);
90 77 }
91 78  
92 79 @Test
93   - public void testLoadUserByUsername_userNotFound() {
  80 + public void testLogin0_userNotFound() {
94 81 // 准备参数
95 82 String username = randomString();
96   - // mock 方法
  83 + String password = randomString();
97 84  
98 85 // 调用, 并断言异常
99   - assertThrows(UsernameNotFoundException.class, // 抛出 UsernameNotFoundException 异常
100   - () -> authService.loadUserByUsername(username),
101   - username); // 异常提示为 username
102   - }
103   -
104   - @Test
105   - public void testMockLogin_success() {
106   - // 准备参数
107   - Long userId = randomLongId();
108   - // mock 方法 01
109   - AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId));
110   - when(userService.getUser(eq(userId))).thenReturn(user);
111   - // mock 方法 02
112   - Set<Long> roleIds = randomSet(Long.class);
113   - when(permissionService.getUserRoleIds(eq(userId), eq(singleton(CommonStatusEnum.ENABLE.getStatus()))))
114   - .thenReturn(roleIds);
115   -
116   - // 调用
117   - LoginUser loginUser = authService.mockLogin(userId);
118   - // 断言
119   - AssertUtils.assertPojoEquals(user, loginUser, "updateTime");
120   - assertEquals(roleIds, loginUser.getRoleIds());
  86 + AssertUtils.assertServiceException(() -> authService.login0(username, password),
  87 + AUTH_LOGIN_BAD_CREDENTIALS);
  88 + verify(loginLogService).createLoginLog(
  89 + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
  90 + && o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult())
  91 + && o.getUserId() == null)
  92 + );
121 93 }
122 94  
123 95 @Test
124   - public void testMockLogin_userNotFound() {
  96 + public void testLogin0_badCredentials() {
125 97 // 准备参数
126   - Long userId = randomLongId();
127   - // mock 方法
  98 + String username = randomString();
  99 + String password = randomString();
  100 + // mock user 数据
  101 + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)
  102 + .setPassword(password).setStatus(CommonStatusEnum.ENABLE.getStatus()));
  103 + when(userService.getUserByUsername(eq(username))).thenReturn(user);
128 104  
129 105 // 调用, 并断言异常
130   - assertThrows(UsernameNotFoundException.class, // 抛出 UsernameNotFoundException 异常
131   - () -> authService.mockLogin(userId),
132   - String.valueOf(userId)); // 异常提示为 userId
  106 + AssertUtils.assertServiceException(() -> authService.login0(username, password),
  107 + AUTH_LOGIN_BAD_CREDENTIALS);
  108 + verify(loginLogService).createLoginLog(
  109 + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
  110 + && o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult())
  111 + && o.getUserId().equals(user.getId()))
  112 + );
133 113 }
134 114  
135 115 @Test
136   - public void testLogin_captchaNotFound() {
  116 + public void testLogin0_userDisabled() {
137 117 // 准备参数
138   - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
139   - String userIp = randomString();
140   - String userAgent = randomString();
  118 + String username = randomString();
  119 + String password = randomString();
  120 + // mock user 数据
  121 + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)
  122 + .setPassword(password).setStatus(CommonStatusEnum.DISABLE.getStatus()));
  123 + when(userService.getUserByUsername(eq(username))).thenReturn(user);
  124 + // mock password 匹配
  125 + when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true);
141 126  
142 127 // 调用, 并断言异常
143   - assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_NOT_FOUND);
144   - // 校验调用参数
145   - verify(loginLogService, times(1)).createLoginLog(
146   - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
147   - && o.getResult().equals(LoginResultEnum.CAPTCHA_NOT_FOUND.getResult()))
  128 + AssertUtils.assertServiceException(() -> authService.login0(username, password),
  129 + AUTH_LOGIN_USER_DISABLED);
  130 + verify(loginLogService).createLoginLog(
  131 + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
  132 + && o.getResult().equals(LoginResultEnum.USER_DISABLED.getResult())
  133 + && o.getUserId().equals(user.getId()))
148 134 );
149 135 }
150 136  
151 137 @Test
152   - public void testLogin_captchaCodeError() {
  138 + public void testCaptcha_success() {
153 139 // 准备参数
154   - String userIp = randomString();
155   - String userAgent = randomString();
156 140 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
157 141  
158   - // mock 验证码不正确
159   - String code = randomString();
160   - when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code);
  142 + // mock 验证码正确
  143 + when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
161 144  
162   - // 调用, 并断言异常
163   - assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_CODE_ERROR);
164   - // 校验调用参数
165   - verify(loginLogService, times(1)).createLoginLog(
166   - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
167   - && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult()))
168   - );
  145 + // 调用
  146 + authService.verifyCaptcha(reqVO);
  147 + // 断言
  148 + verify(captchaService).deleteCaptchaCode(reqVO.getUuid());
169 149 }
170 150  
171 151 @Test
172   - public void testLogin_badCredentials() {
  152 + public void testCaptcha_notFound() {
173 153 // 准备参数
174   - String userIp = randomString();
175   - String userAgent = randomString();
176 154 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
177   - // mock 验证码正确
178   - when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
179   - // mock 抛出异常
180   - when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
181   - .thenThrow(new BadCredentialsException("测试账号或密码不正确"));
182 155  
183 156 // 调用, 并断言异常
184   - assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_BAD_CREDENTIALS);
  157 + assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_NOT_FOUND);
185 158 // 校验调用参数
186   - verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid());
187 159 verify(loginLogService, times(1)).createLoginLog(
188 160 argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
189   - && o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult()))
  161 + && o.getResult().equals(LoginResultEnum.CAPTCHA_NOT_FOUND.getResult()))
190 162 );
191 163 }
192 164  
193 165 @Test
194   - public void testLogin_userDisabled() {
  166 + public void testCaptcha_codeError() {
195 167 // 准备参数
196   - String userIp = randomString();
197   - String userAgent = randomString();
198 168 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
199 169  
200   - // mock 验证码正确
201   - when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
202   - // mock 抛出异常
203   - when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
204   - .thenThrow(new DisabledException("测试用户被禁用"));
  170 + // mock 验证码不正确
  171 + String code = randomString();
  172 + when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code);
205 173  
206 174 // 调用, 并断言异常
207   - assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_USER_DISABLED);
  175 + assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR);
208 176 // 校验调用参数
209   - verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid());
210   - verify(loginLogService, times(1)).createLoginLog(
  177 + verify(loginLogService).createLoginLog(
211 178 argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
212   - && o.getResult().equals(LoginResultEnum.USER_DISABLED.getResult()))
  179 + && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult()))
213 180 );
214 181 }
215 182  
216 183 @Test
217   - public void testLogin_unknownError() {
  184 + public void testLogin_success() {
218 185 // 准备参数
219   - String userIp = randomString();
220   - String userAgent = randomString();
221   - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
  186 + AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o ->
  187 + o.setUsername("test_username").setPassword("test_password"));
  188 +
222 189 // mock 验证码正确
223 190 when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
224   - // mock 抛出异常
225   - when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
226   - .thenThrow(new AuthenticationException("测试未知异常") {});
  191 + // mock user 数据
  192 + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L).setUsername("test_username")
  193 + .setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus()));
  194 + when(userService.getUserByUsername(eq("test_username"))).thenReturn(user);
  195 + // mock password 匹配
  196 + when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true);
  197 + // mock 缓存登录用户到 Redis
  198 + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
  199 + .setUserType(UserTypeEnum.ADMIN.getValue()));
  200 + when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq(1L)))
  201 + .thenReturn(accessTokenDO);
227 202  
228 203 // 调用, 并断言异常
229   - assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_FAIL_UNKNOWN);
  204 + AuthLoginRespVO loginRespVO = authService.login(reqVO);
  205 + assertPojoEquals(accessTokenDO, loginRespVO);
230 206 // 校验调用参数
231   - verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid());
232   - verify(loginLogService, times(1)).createLoginLog(
  207 + verify(loginLogService).createLoginLog(
233 208 argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
234   - && o.getResult().equals(LoginResultEnum.UNKNOWN_ERROR.getResult()))
  209 + && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())
  210 + && o.getUserId().equals(user.getId()))
235 211 );
236 212 }
237 213  
238 214 @Test
239   - public void testLogin_success() {
  215 + public void testLogout_success() {
240 216 // 准备参数
241   - String userIp = randomString();
242   - String userAgent = randomString();
243   - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
244   -
245   - // mock 验证码正确
246   - when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
247   - // mock authentication
248   - Long userId = randomLongId();
249   - Set<Long> userRoleIds = randomSet(Long.class);
250   - LoginUser loginUser = randomPojo(LoginUser.class, o -> {
251   - o.setId(userId);
252   - o.setRoleIds(userRoleIds);
253   - });
254   - when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
255   - .thenReturn(authentication);
256   - when(authentication.getPrincipal()).thenReturn(loginUser);
257   - // mock 获得 User 拥有的角色编号数组
258   - when(permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()))).thenReturn(userRoleIds);
259   - // mock 缓存登录用户到 Redis
260 217 String token = randomString();
261   - when(userSessionService.createUserSession(loginUser, userIp, userAgent)).thenReturn(token);
  218 + // mock
  219 + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
  220 + .setUserType(UserTypeEnum.ADMIN.getValue()));
  221 + when(oauth2TokenService.removeAccessToken(eq(token))).thenReturn(accessTokenDO);
262 222  
263   - // 调用, 并断言异常
264   - String login = authService.login(reqVO, userIp, userAgent);
265   - assertEquals(token, login);
  223 + // 调用
  224 + authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
266 225 // 校验调用参数
267   - verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid());
268   - verify(loginLogService, times(1)).createLoginLog(
269   - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
  226 + verify(loginLogService).createLoginLog(argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType())
270 227 && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))
271 228 );
272 229 }
273 230  
274 231 @Test
275   - public void testLogout_success() {
  232 + public void testLogout_fail() {
276 233 // 准备参数
277 234 String token = randomString();
278   - LoginUser loginUser = randomPojo(LoginUser.class);
279   - // mock
280   - when(userSessionService.getLoginUser(token)).thenReturn(loginUser);
  235 +
281 236 // 调用
282   - authService.logout(token);
  237 + authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
283 238 // 校验调用参数
284   - verify(userSessionService, times(1)).deleteUserSession(token);
285   - verify(loginLogService, times(1)).createLoginLog(
286   - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType())
287   - && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))
288   - );
  239 + verify(loginLogService, never()).createLoginLog(any());
289 240 }
290 241  
291 242 }
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImplTest.java 0 → 100755
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  6 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
  7 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
  8 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
  9 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
  10 +import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper;
  11 +import org.junit.jupiter.api.Disabled;
  12 +import org.junit.jupiter.api.Test;
  13 +import org.springframework.context.annotation.Import;
  14 +
  15 +import javax.annotation.Resource;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
  18 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  19 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  20 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
  21 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  22 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS;
  23 +import static org.junit.jupiter.api.Assertions.*;
  24 +
  25 +/**
  26 +* {@link OAuth2ClientServiceImpl} 的单元测试类
  27 +*
  28 +* @author 芋道源码
  29 +*/
  30 +@Import(OAuth2ClientServiceImpl.class)
  31 +public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
  32 +
  33 + @Resource
  34 + private OAuth2ClientServiceImpl oAuth2ClientService;
  35 +
  36 + @Resource
  37 + private OAuth2ClientMapper oAuth2ClientMapper;
  38 +
  39 + @Test
  40 + public void testCreateOAuth2Client_success() {
  41 + // 准备参数
  42 + OAuth2ClientCreateReqVO reqVO = randomPojo(OAuth2ClientCreateReqVO.class);
  43 +
  44 + // 调用
  45 + Long oauth2ClientId = oAuth2ClientService.createOAuth2Client(reqVO);
  46 + // 断言
  47 + assertNotNull(oauth2ClientId);
  48 + // 校验记录的属性是否正确
  49 + OAuth2ClientDO oAuth2Client = oAuth2ClientMapper.selectById(oauth2ClientId);
  50 + assertPojoEquals(reqVO, oAuth2Client);
  51 + }
  52 +
  53 + @Test
  54 + public void testUpdateOAuth2Client_success() {
  55 + // mock 数据
  56 + OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class);
  57 + oAuth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据
  58 + // 准备参数
  59 + OAuth2ClientUpdateReqVO reqVO = randomPojo(OAuth2ClientUpdateReqVO.class, o -> {
  60 + o.setId(dbOAuth2Client.getId()); // 设置更新的 ID
  61 + });
  62 +
  63 + // 调用
  64 + oAuth2ClientService.updateOAuth2Client(reqVO);
  65 + // 校验是否更新正确
  66 + OAuth2ClientDO oAuth2Client = oAuth2ClientMapper.selectById(reqVO.getId()); // 获取最新的
  67 + assertPojoEquals(reqVO, oAuth2Client);
  68 + }
  69 +
  70 + @Test
  71 + public void testUpdateOAuth2Client_notExists() {
  72 + // 准备参数
  73 + OAuth2ClientUpdateReqVO reqVO = randomPojo(OAuth2ClientUpdateReqVO.class);
  74 +
  75 + // 调用, 并断言异常
  76 + assertServiceException(() -> oAuth2ClientService.updateOAuth2Client(reqVO), OAUTH2_CLIENT_NOT_EXISTS);
  77 + }
  78 +
  79 + @Test
  80 + public void testDeleteOAuth2Client_success() {
  81 + // mock 数据
  82 + OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class);
  83 + oAuth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据
  84 + // 准备参数
  85 + Long id = dbOAuth2Client.getId();
  86 +
  87 + // 调用
  88 + oAuth2ClientService.deleteOAuth2Client(id);
  89 + // 校验数据不存在了
  90 + assertNull(oAuth2ClientMapper.selectById(id));
  91 + }
  92 +
  93 + @Test
  94 + public void testDeleteOAuth2Client_notExists() {
  95 + // 准备参数
  96 + Long id = randomLongId();
  97 +
  98 + // 调用, 并断言异常
  99 + assertServiceException(() -> oAuth2ClientService.deleteOAuth2Client(id), OAUTH2_CLIENT_NOT_EXISTS);
  100 + }
  101 +
  102 + @Test
  103 + @Disabled
  104 + public void testGetOAuth2ClientPage() {
  105 + // mock 数据
  106 + OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class, o -> { // 等会查询到
  107 + o.setName("潜龙");
  108 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  109 + });
  110 + oAuth2ClientMapper.insert(dbOAuth2Client);
  111 + // 测试 name 不匹配
  112 + oAuth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰")));
  113 + // 测试 status 不匹配
  114 + oAuth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())));
  115 + // 准备参数
  116 + OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO();
  117 + reqVO.setName("long");
  118 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
  119 +
  120 + // 调用
  121 + PageResult<OAuth2ClientDO> pageResult = oAuth2ClientService.getOAuth2ClientPage(reqVO);
  122 + // 断言
  123 + assertEquals(1, pageResult.getTotal());
  124 + assertEquals(1, pageResult.getList().size());
  125 + assertPojoEquals(dbOAuth2Client, pageResult.getList().get(0));
  126 + }
  127 +
  128 +}
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java deleted
1   -package cn.iocoder.yudao.module.system.service.auth;
2   -
3   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -import cn.iocoder.yudao.framework.common.util.date.DateUtils;
7   -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
8   -import cn.iocoder.yudao.framework.security.config.SecurityProperties;
9   -import cn.iocoder.yudao.framework.security.core.LoginUser;
10   -import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
11   -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
12   -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
13   -import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
14   -import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper;
15   -import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO;
16   -import cn.iocoder.yudao.module.system.enums.common.SexEnum;
17   -import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
18   -import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
19   -import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
20   -import cn.iocoder.yudao.module.system.service.user.AdminUserService;
21   -import org.junit.jupiter.api.BeforeEach;
22   -import org.junit.jupiter.api.Test;
23   -import org.springframework.boot.test.mock.mockito.MockBean;
24   -import org.springframework.context.annotation.Import;
25   -
26   -import javax.annotation.Resource;
27   -import java.time.Duration;
28   -import java.util.Calendar;
29   -
30   -import static cn.hutool.core.util.RandomUtil.randomEle;
31   -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
32   -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
33   -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
34   -import static java.util.Collections.singletonList;
35   -import static org.junit.jupiter.api.Assertions.*;
36   -import static org.mockito.ArgumentMatchers.argThat;
37   -import static org.mockito.ArgumentMatchers.eq;
38   -import static org.mockito.Mockito.verify;
39   -import static org.mockito.Mockito.when;
40   -
41   -/**
42   - * {@link UserSessionServiceImpl} 的单元测试
43   - *
44   - * @author Lyon
45   - */
46   -@Import({UserSessionServiceImpl.class, LoginUserRedisDAO.class})
47   -public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
48   -
49   - @Resource
50   - private UserSessionServiceImpl userSessionService;
51   -
52   - @Resource
53   - private UserSessionMapper userSessionMapper;
54   -
55   - @MockBean
56   - private AdminUserService userService;
57   - @MockBean
58   - private LoginLogService loginLogService;
59   - @Resource
60   - private LoginUserRedisDAO loginUserRedisDAO;
61   -
62   - @MockBean
63   - private SecurityProperties securityProperties;
64   -
65   - @BeforeEach
66   - public void setUp() {
67   - when(securityProperties.getSessionTimeout()).thenReturn(Duration.ofDays(1L));
68   - }
69   -
70   - @Test
71   - public void testGetUserSessionPage_success() {
72   - // mock 数据
73   - AdminUserDO dbUser = randomPojo(AdminUserDO.class, o -> {
74   - o.setSex(randomEle(SexEnum.values()).getSex());
75   - o.setStatus(CommonStatusEnum.ENABLE.getStatus());
76   - });
77   - when(userService.getUsersByUsername(eq(dbUser.getUsername()))).thenReturn(singletonList(dbUser));
78   - // 插入可被查询到的数据
79   - String userIp = randomString();
80   - UserSessionDO dbSession = randomPojo(UserSessionDO.class, o -> {
81   - o.setUserId(dbUser.getId());
82   - o.setUserType(randomEle(UserTypeEnum.values()).getValue());
83   - o.setUserIp(userIp);
84   - });
85   - userSessionMapper.insert(dbSession);
86   - // 测试 username 不匹配
87   - userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> o.setUserId(123456L)));
88   - // 测试 userIp 不匹配
89   - userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> o.setUserIp("testUserIp")));
90   - // 准备参数
91   - UserSessionPageReqVO reqVO = new UserSessionPageReqVO();
92   - reqVO.setUsername(dbUser.getUsername());
93   - reqVO.setUserIp(userIp);
94   -
95   - // 调用
96   - PageResult<UserSessionDO> pageResult = userSessionService.getUserSessionPage(reqVO);
97   - // 断言
98   - assertEquals(1, pageResult.getTotal());
99   - assertEquals(1, pageResult.getList().size());
100   - assertPojoEquals(dbSession, pageResult.getList().get(0));
101   - }
102   -
103   - @Test
104   - public void testClearSessionTimeout_none() {
105   - // mock db 数据
106   - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
107   - o.setUserType(randomEle(UserTypeEnum.values()).getValue());
108   - o.setSessionTimeout(addTime(Duration.ofDays(1)));
109   - });
110   - userSessionMapper.insert(userSession);
111   -
112   - // 调用
113   - long count = userSessionService.deleteTimeoutSession();
114   - // 断言
115   - assertEquals(0, count);
116   - assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除
117   - }
118   -
119   - @Test // Redis 还存在的情况
120   - public void testClearSessionTimeout_exists() {
121   - // mock db 数据
122   - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
123   - o.setUserType(randomEle(UserTypeEnum.values()).getValue());
124   - o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1));
125   - });
126   - userSessionMapper.insert(userSession);
127   - // mock redis 数据
128   - loginUserRedisDAO.set(userSession.getToken(), new LoginUser());
129   -
130   - // 调用
131   - long count = userSessionService.deleteTimeoutSession();
132   - // 断言
133   - assertEquals(0, count);
134   - assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除
135   - }
136   -
137   - @Test
138   - public void testClearSessionTimeout_success() {
139   - // mock db 数据
140   - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
141   - o.setUserType(randomEle(UserTypeEnum.values()).getValue());
142   - o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1));
143   - });
144   - userSessionMapper.insert(userSession);
145   -
146   - // 清空超时数据
147   - long count = userSessionService.deleteTimeoutSession();
148   - // 校验
149   - assertEquals(1, count);
150   - assertNull(userSessionMapper.selectById(userSession.getId())); // 已删除
151   - verify(loginLogService).createLoginLog(argThat(loginLog -> {
152   - assertPojoEquals(userSession, loginLog);
153   - assertEquals(LoginLogTypeEnum.LOGOUT_TIMEOUT.getType(), loginLog.getLogType());
154   - assertEquals(LoginResultEnum.SUCCESS.getResult(), loginLog.getResult());
155   - return true;
156   - }));
157   - }
158   -
159   - @Test
160   - public void testCreateUserSession_success() {
161   - // 准备参数
162   - String userIp = randomString();
163   - String userAgent = randomString();
164   - LoginUser loginUser = randomPojo(LoginUser.class, o -> {
165   - o.setUserType(randomEle(UserTypeEnum.values()).getValue());
166   - o.setTenantId(0L); // 租户设置为 0,因为暂未启用多租户组件
167   - });
168   -
169   - // 调用
170   - String token = userSessionService.createUserSession(loginUser, userIp, userAgent);
171   - // 校验 UserSessionDO 记录
172   - UserSessionDO userSessionDO = userSessionMapper.selectOne(UserSessionDO::getToken, token);
173   - assertPojoEquals(loginUser, userSessionDO, "id", "updateTime");
174   - assertEquals(token, userSessionDO.getToken());
175   - assertEquals(userIp, userSessionDO.getUserIp());
176   - assertEquals(userAgent, userSessionDO.getUserAgent());
177   - // 校验 LoginUser 缓存
178   - LoginUser redisLoginUser = loginUserRedisDAO.get(token);
179   - assertPojoEquals(loginUser, redisLoginUser, "username", "password");
180   - }
181   -
182   - @Test
183   - public void testCreateRefreshUserSession() {
184   - // 准备参数
185   - String token = randomString();
186   -
187   - // mock redis 数据
188   - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setUserType(randomEle(UserTypeEnum.values()).getValue()));
189   - loginUserRedisDAO.set(token, loginUser);
190   - // mock db 数据
191   - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
192   - o.setUserType(randomEle(UserTypeEnum.values()).getValue());
193   - o.setToken(token);
194   - });
195   - userSessionMapper.insert(userSession);
196   -
197   - // 调用
198   - userSessionService.refreshUserSession(token, loginUser);
199   - // 校验 LoginUser 缓存
200   - LoginUser redisLoginUser = loginUserRedisDAO.get(token);
201   - assertPojoEquals(redisLoginUser, loginUser, "username", "password");
202   - // 校验 UserSessionDO 记录
203   - UserSessionDO updateDO = userSessionMapper.selectOne(UserSessionDO::getToken, token);
204   - assertEquals(updateDO.getUsername(), loginUser.getUsername());
205   - assertNotNull(userSession.getUpdateTime());
206   - assertNotNull(userSession.getSessionTimeout());
207   - }
208   -
209   - @Test
210   - public void testDeleteUserSession_Token() {
211   - // 准备参数
212   - String token = randomString();
213   -
214   - // mock redis 数据
215   - loginUserRedisDAO.set(token, new LoginUser());
216   - // mock db 数据
217   - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
218   - o.setUserType(randomEle(UserTypeEnum.values()).getValue());
219   - o.setToken(token);
220   - });
221   - userSessionMapper.insert(userSession);
222   -
223   - // 调用
224   - userSessionService.deleteUserSession(token);
225   - // 校验数据不存在了
226   - assertNull(loginUserRedisDAO.get(token));
227   - assertNull(userSessionMapper.selectOne(UserSessionDO::getToken, token));
228   - }
229   -
230   - @Test
231   - public void testDeleteUserSession_Id() {
232   - // mock db 数据
233   - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
234   - o.setUserType(randomEle(UserTypeEnum.values()).getValue());
235   - });
236   - userSessionMapper.insert(userSession);
237   - // mock redis 数据
238   - loginUserRedisDAO.set(userSession.getToken(), new LoginUser());
239   -
240   - // 准备参数
241   - Long id = userSession.getId();
242   -
243   - // 调用
244   - userSessionService.deleteUserSession(id);
245   - // 校验数据不存在了
246   - assertNull(loginUserRedisDAO.get(userSession.getToken()));
247   - assertNull(userSessionMapper.selectById(id));
248   - }
249   -
250   -}
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java
1 1 package cn.iocoder.yudao.module.system.service.permission;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  5 +import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
  6 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
4 7 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
5 8 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
6 9 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
7 10 import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
  11 +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
8 12 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuBatchInsertMapper;
9 13 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
10 14 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleBatchInsertMapper;
11 15 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
  16 +import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
12 17 import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
13 18 import cn.iocoder.yudao.module.system.service.dept.DeptService;
14   -import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
15   -import cn.iocoder.yudao.framework.security.core.LoginUser;
16   -import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
17   -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  19 +import cn.iocoder.yudao.module.system.service.user.AdminUserService;
18 20 import org.junit.jupiter.api.Test;
19 21 import org.springframework.boot.test.mock.mockito.MockBean;
20 22 import org.springframework.context.annotation.Import;
... ... @@ -25,10 +27,10 @@ import java.util.List;
25 27 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
26 28 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
27 29 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  30 +import static java.util.Collections.singleton;
28 31 import static java.util.Collections.singletonList;
29 32 import static org.junit.jupiter.api.Assertions.*;
30 33 import static org.mockito.ArgumentMatchers.eq;
31   -import static org.mockito.ArgumentMatchers.same;
32 34 import static org.mockito.Mockito.verify;
33 35 import static org.mockito.Mockito.when;
34 36  
... ... @@ -55,6 +57,9 @@ public class PermissionServiceTest extends BaseDbUnitTest {
55 57 @MockBean
56 58 private DeptService deptService;
57 59 @MockBean
  60 + private AdminUserService userService;
  61 +
  62 + @MockBean
58 63 private PermissionProducer permissionProducer;
59 64  
60 65 @Test
... ... @@ -124,112 +129,119 @@ public class PermissionServiceTest extends BaseDbUnitTest {
124 129 assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
125 130 }
126 131  
127   - @Test // 测试从 context 获取的场景
128   - public void testGetDeptDataPermission_fromContext() {
129   - // 准备参数
130   - LoginUser loginUser = randomPojo(LoginUser.class);
131   - // mock 方法
132   - DeptDataPermissionRespDTO respDTO = new DeptDataPermissionRespDTO();
133   - loginUser.setContext(PermissionServiceImpl.CONTEXT_KEY, respDTO);
134   -
135   - // 调用
136   - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
137   - // 断言
138   - assertSame(respDTO, result);
139   - }
140   -
141 132 @Test
142 133 public void testGetDeptDataPermission_All() {
143 134 // 准备参数
144   - LoginUser loginUser = randomPojo(LoginUser.class);
145   - // mock 方法
146   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope()));
147   - when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO));
  135 + Long userId = 1L;
  136 + // mock 用户的角色编号
  137 + userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L));
  138 + // mock 获得用户的角色
  139 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())
  140 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  141 + when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
  142 + when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
148 143  
149 144 // 调用
150   - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
  145 + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
151 146 // 断言
152 147 assertTrue(result.getAll());
153 148 assertFalse(result.getSelf());
154 149 assertTrue(CollUtil.isEmpty(result.getDeptIds()));
155   - assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
156 150 }
157 151  
158 152 @Test
159 153 public void testGetDeptDataPermission_DeptCustom() {
160 154 // 准备参数
161   - LoginUser loginUser = randomPojo(LoginUser.class);
162   - // mock 方法
163   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope()));
164   - when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO));
  155 + Long userId = 1L;
  156 + // mock 用户的角色编号
  157 + userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L));
  158 + // mock 获得用户的角色
  159 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
  160 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  161 + when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
  162 + when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
  163 + // mock 部门的返回
  164 + when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
165 165  
166 166 // 调用
167   - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
  167 + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(1L);
168 168 // 断言
169 169 assertFalse(result.getAll());
170 170 assertFalse(result.getSelf());
171 171 assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());
172 172 assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));
173   - assertTrue(CollUtil.contains(result.getDeptIds(), loginUser.getDeptId()));
174   - assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
  173 + assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
175 174 }
176 175  
177 176 @Test
178 177 public void testGetDeptDataPermission_DeptOnly() {
179 178 // 准备参数
180   - LoginUser loginUser = randomPojo(LoginUser.class);
181   - // mock 方法
182   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope()));
183   - when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO));
  179 + Long userId = 1L;
  180 + // mock 用户的角色编号
  181 + userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L));
  182 + // mock 获得用户的角色
  183 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())
  184 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  185 + when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
  186 + when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
  187 + // mock 部门的返回
  188 + when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
184 189  
185 190 // 调用
186   - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
  191 + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(1L);
187 192 // 断言
188 193 assertFalse(result.getAll());
189 194 assertFalse(result.getSelf());
190 195 assertEquals(1, result.getDeptIds().size());
191   - assertTrue(CollUtil.contains(result.getDeptIds(), loginUser.getDeptId()));
192   - assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
  196 + assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
193 197 }
194 198  
195 199 @Test
196 200 public void testGetDeptDataPermission_DeptAndChild() {
197 201 // 准备参数
198   - LoginUser loginUser = randomPojo(LoginUser.class);
199   - // mock 方法(角色)
200   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope()));
201   - when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO));
  202 + Long userId = 1L;
  203 + // mock 用户的角色编号
  204 + userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L));
  205 + // mock 获得用户的角色
  206 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())
  207 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  208 + when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
  209 + when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
  210 + // mock 部门的返回
  211 + when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
202 212 // mock 方法(部门)
203 213 DeptDO deptDO = randomPojo(DeptDO.class);
204   - when(deptService.getDeptsByParentIdFromCache(eq(loginUser.getDeptId()), eq(true)))
  214 + when(deptService.getDeptsByParentIdFromCache(eq(3L), eq(true)))
205 215 .thenReturn(singletonList(deptDO));
206 216  
207 217 // 调用
208   - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
  218 + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
209 219 // 断言
210 220 assertFalse(result.getAll());
211 221 assertFalse(result.getSelf());
212 222 assertEquals(2, result.getDeptIds().size());
213 223 assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId()));
214   - assertTrue(CollUtil.contains(result.getDeptIds(), loginUser.getDeptId()));
215   - assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
  224 + assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
216 225 }
217 226  
218 227 @Test
219 228 public void testGetDeptDataPermission_Self() {
220 229 // 准备参数
221   - LoginUser loginUser = randomPojo(LoginUser.class);
222   - // mock 方法
223   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope()));
224   - when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO));
  230 + Long userId = 1L;
  231 + // mock 用户的角色编号
  232 + userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L));
  233 + // mock 获得用户的角色
  234 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())
  235 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  236 + when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
  237 + when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
225 238  
226 239 // 调用
227   - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser);
  240 + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
228 241 // 断言
229 242 assertFalse(result.getAll());
230 243 assertTrue(result.getSelf());
231 244 assertTrue(CollUtil.isEmpty(result.getDeptIds()));
232   - assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
233 245 }
234 246  
235 247 }
... ...
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql
... ... @@ -20,3 +20,4 @@ DELETE FROM &quot;system_social_user_bind&quot;;
20 20 DELETE FROM "system_tenant";
21 21 DELETE FROM "system_tenant_package";
22 22 DELETE FROM "system_sensitive_word";
  23 +DELETE FROM "system_oauth2_client";
... ...
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
... ... @@ -470,3 +470,21 @@ CREATE TABLE IF NOT EXISTS &quot;system_sensitive_word&quot; (
470 470 "deleted" bit NOT NULL DEFAULT FALSE,
471 471 PRIMARY KEY ("id")
472 472 ) COMMENT '系统敏感词';
  473 +
  474 +CREATE TABLE IF NOT EXISTS "system_oauth2_client" (
  475 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  476 + "secret" varchar NOT NULL,
  477 + "name" varchar NOT NULL,
  478 + "logo" varchar NOT NULL,
  479 + "description" varchar,
  480 + "status" int NOT NULL,
  481 + "access_token_validity_seconds" int NOT NULL,
  482 + "refresh_token_validity_seconds" int NOT NULL,
  483 + "redirect_uris" varchar NOT NULL,
  484 + "creator" varchar DEFAULT '',
  485 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  486 + "updater" varchar DEFAULT '',
  487 + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  488 + "deleted" bit NOT NULL DEFAULT FALSE,
  489 + PRIMARY KEY ("id")
  490 +) COMMENT 'OAuth2 客户端表';
... ...
yudao-server/src/main/resources/application-dev.yaml
... ... @@ -173,8 +173,6 @@ wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-sta
173 173 yudao:
174 174 security:
175 175 token-header: Authorization
176   - token-timeout: 1d
177   - session-timeout: 30m
178 176 mock-enable: true
179 177 mock-secret: test
180 178 xss:
... ...