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
yudao-dependencies/pom.xml
@@ -22,7 +22,6 @@ @@ -22,7 +22,6 @@
22 <swagger-annotations.version>1.5.22</swagger-annotations.version> 22 <swagger-annotations.version>1.5.22</swagger-annotations.version>
23 <servlet.versoin>2.5</servlet.versoin> 23 <servlet.versoin>2.5</servlet.versoin>
24 <!-- DB 相关 --> 24 <!-- DB 相关 -->
25 - <mysql.version>5.1.46</mysql.version>  
26 <druid.version>1.2.8</druid.version> 25 <druid.version>1.2.8</druid.version>
27 <mybatis-plus.version>3.4.3.4</mybatis-plus.version> 26 <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
28 <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version> 27 <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
@@ -76,12 +75,6 @@ @@ -76,12 +75,6 @@
76 <version>${spring.boot.version}</version> 75 <version>${spring.boot.version}</version>
77 <type>pom</type> 76 <type>pom</type>
78 <scope>import</scope> 77 <scope>import</scope>
79 - <exclusions>  
80 - <exclusion>  
81 - <groupId>mysql</groupId>  
82 - <artifactId>mysql-connector-java</artifactId>  
83 - </exclusion>  
84 - </exclusions>  
85 </dependency> 78 </dependency>
86 79
87 <!-- 业务组件 --> 80 <!-- 业务组件 -->
@@ -180,11 +173,6 @@ @@ -180,11 +173,6 @@
180 </dependency> 173 </dependency>
181 174
182 <dependency> 175 <dependency>
183 - <groupId>mysql</groupId>  
184 - <artifactId>mysql-connector-java</artifactId>  
185 - <version>${mysql.version}</version>  
186 - </dependency>  
187 - <dependency>  
188 <groupId>com.alibaba</groupId> 176 <groupId>com.alibaba</groupId>
189 <artifactId>druid-spring-boot-starter</artifactId> 177 <artifactId>druid-spring-boot-starter</artifactId>
190 <version>${druid.version}</version> 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,6 +54,13 @@ public class CollectionUtils {
54 return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList()); 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 public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) { 64 public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
58 if (CollUtil.isEmpty(from)) { 65 if (CollUtil.isEmpty(from)) {
59 return new HashSet<>(); 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 package cn.iocoder.yudao.framework.datapermission.core.dept.rule; 1 package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
4 import cn.hutool.core.util.StrUtil; 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 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 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 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule; 11 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
10 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 12 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
11 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; 13 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
@@ -13,7 +15,6 @@ import cn.iocoder.yudao.framework.security.core.LoginUser; @@ -13,7 +15,6 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
13 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; 15 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
14 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; 16 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
15 import lombok.AllArgsConstructor; 17 import lombok.AllArgsConstructor;
16 -import lombok.Getter;  
17 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
18 import net.sf.jsqlparser.expression.Alias; 19 import net.sf.jsqlparser.expression.Alias;
19 import net.sf.jsqlparser.expression.Expression; 20 import net.sf.jsqlparser.expression.Expression;
@@ -24,10 +25,7 @@ import net.sf.jsqlparser.expression.operators.relational.EqualsTo; @@ -24,10 +25,7 @@ import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
24 import net.sf.jsqlparser.expression.operators.relational.ExpressionList; 25 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
25 import net.sf.jsqlparser.expression.operators.relational.InExpression; 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 * 基于部门的 {@link DataPermissionRule} 数据权限规则实现 31 * 基于部门的 {@link DataPermissionRule} 数据权限规则实现
@@ -50,6 +48,11 @@ import java.util.Set; @@ -50,6 +48,11 @@ import java.util.Set;
50 @Slf4j 48 @Slf4j
51 public class DeptDataPermissionRule implements DataPermissionRule { 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 private static final String DEPT_COLUMN_NAME = "dept_id"; 56 private static final String DEPT_COLUMN_NAME = "dept_id";
54 private static final String USER_COLUMN_NAME = "user_id"; 57 private static final String USER_COLUMN_NAME = "user_id";
55 58
@@ -90,13 +93,23 @@ public class DeptDataPermissionRule implements DataPermissionRule { @@ -90,13 +93,23 @@ public class DeptDataPermissionRule implements DataPermissionRule {
90 if (loginUser == null) { 93 if (loginUser == null) {
91 return null; 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 if (deptDataPermission == null) { 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 // 情况一,如果是 ALL 可查看全部,则无需拼接条件 115 // 情况一,如果是 ALL 可查看全部,则无需拼接条件
@@ -111,8 +124,8 @@ public class DeptDataPermissionRule implements DataPermissionRule { @@ -111,8 +124,8 @@ public class DeptDataPermissionRule implements DataPermissionRule {
111 } 124 }
112 125
113 // 情况三,拼接 Dept 和 User 的条件,最后组合 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 if (deptExpression == null && userExpression == null) { 129 if (deptExpression == null && userExpression == null) {
117 // TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据 130 // TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据
118 log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]", 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 package cn.iocoder.yudao.framework.datapermission.core.dept.service; 1 package cn.iocoder.yudao.framework.datapermission.core.dept.service;
2 2
3 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; 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 * 基于部门的数据权限 Framework Service 接口 6 * 基于部门的数据权限 Framework Service 接口
@@ -14,9 +13,9 @@ public interface DeptDataPermissionFrameworkService { @@ -14,9 +13,9 @@ public interface DeptDataPermissionFrameworkService {
14 /** 13 /**
15 * 获得登陆用户的部门数据权限 14 * 获得登陆用户的部门数据权限
16 * 15 *
17 - * @param loginUser 登陆用户 16 + * @param userId 用户编号
18 * @return 部门数据权限 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,6 +2,7 @@ package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ReflectUtil; 4 import cn.hutool.core.util.ReflectUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 import cn.iocoder.yudao.framework.common.util.collection.SetUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
6 import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService; 7 import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
7 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; 8 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
@@ -69,7 +70,8 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { @@ -69,7 +70,8 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
69 String tableName = "t_user"; 70 String tableName = "t_user";
70 Alias tableAlias = new Alias("u"); 71 Alias tableAlias = new Alias("u");
71 // mock 方法 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 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); 75 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
74 76
75 // 调用 77 // 调用
@@ -88,16 +90,18 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { @@ -88,16 +90,18 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
88 String tableName = "t_user"; 90 String tableName = "t_user";
89 Alias tableAlias = new Alias("u"); 91 Alias tableAlias = new Alias("u");
90 // mock 方法(LoginUser) 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 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); 95 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
93 // mock 方法(DeptDataPermissionRespDTO) 96 // mock 方法(DeptDataPermissionRespDTO)
94 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO().setAll(true); 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 Expression expression = rule.getExpression(tableName, tableAlias); 101 Expression expression = rule.getExpression(tableName, tableAlias);
99 // 断言 102 // 断言
100 assertNull(expression); 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,16 +113,18 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
109 String tableName = "t_user"; 113 String tableName = "t_user";
110 Alias tableAlias = new Alias("u"); 114 Alias tableAlias = new Alias("u");
111 // mock 方法(LoginUser) 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 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); 118 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
114 // mock 方法(DeptDataPermissionRespDTO) 119 // mock 方法(DeptDataPermissionRespDTO)
115 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO(); 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 Expression expression = rule.getExpression(tableName, tableAlias); 124 Expression expression = rule.getExpression(tableName, tableAlias);
120 // 断言 125 // 断言
121 assertEquals("null = null", expression.toString()); 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,17 +136,19 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
130 String tableName = "t_user"; 136 String tableName = "t_user";
131 Alias tableAlias = new Alias("u"); 137 Alias tableAlias = new Alias("u");
132 // mock 方法(LoginUser) 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 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); 141 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
135 // mock 方法(DeptDataPermissionRespDTO) 142 // mock 方法(DeptDataPermissionRespDTO)
136 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO() 143 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
137 .setDeptIds(SetUtils.asSet(10L, 20L)).setSelf(true); 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 Expression expression = rule.getExpression(tableName, tableAlias); 148 Expression expression = rule.getExpression(tableName, tableAlias);
142 // 断言 149 // 断言
143 assertSame(EXPRESSION_NULL, expression); 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,12 +160,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
152 String tableName = "t_user"; 160 String tableName = "t_user";
153 Alias tableAlias = new Alias("u"); 161 Alias tableAlias = new Alias("u");
154 // mock 方法(LoginUser) 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 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); 165 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
157 // mock 方法(DeptDataPermissionRespDTO) 166 // mock 方法(DeptDataPermissionRespDTO)
158 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO() 167 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
159 .setSelf(true); 168 .setSelf(true);
160 - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission); 169 + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
161 // 添加 user 字段配置 170 // 添加 user 字段配置
162 rule.addUserColumn("t_user", "id"); 171 rule.addUserColumn("t_user", "id");
163 172
@@ -165,6 +174,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { @@ -165,6 +174,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
165 Expression expression = rule.getExpression(tableName, tableAlias); 174 Expression expression = rule.getExpression(tableName, tableAlias);
166 // 断言 175 // 断言
167 assertEquals("u.id = 1", expression.toString()); 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,12 +186,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
176 String tableName = "t_user"; 186 String tableName = "t_user";
177 Alias tableAlias = new Alias("u"); 187 Alias tableAlias = new Alias("u");
178 // mock 方法(LoginUser) 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 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); 191 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
181 // mock 方法(DeptDataPermissionRespDTO) 192 // mock 方法(DeptDataPermissionRespDTO)
182 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO() 193 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
183 .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L)); 194 .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L));
184 - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission); 195 + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
185 // 添加 dept 字段配置 196 // 添加 dept 字段配置
186 rule.addDeptColumn("t_user", "dept_id"); 197 rule.addDeptColumn("t_user", "dept_id");
187 198
@@ -189,6 +200,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { @@ -189,6 +200,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
189 Expression expression = rule.getExpression(tableName, tableAlias); 200 Expression expression = rule.getExpression(tableName, tableAlias);
190 // 断言 201 // 断言
191 assertEquals("u.dept_id IN (10, 20)", expression.toString()); 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,12 +212,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
200 String tableName = "t_user"; 212 String tableName = "t_user";
201 Alias tableAlias = new Alias("u"); 213 Alias tableAlias = new Alias("u");
202 // mock 方法(LoginUser) 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 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); 217 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
205 // mock 方法(DeptDataPermissionRespDTO) 218 // mock 方法(DeptDataPermissionRespDTO)
206 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO() 219 DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
207 .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L)).setSelf(true); 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 // 添加 user 字段配置 222 // 添加 user 字段配置
210 rule.addUserColumn("t_user", "id"); 223 rule.addUserColumn("t_user", "id");
211 // 添加 dept 字段配置 224 // 添加 dept 字段配置
@@ -215,6 +228,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { @@ -215,6 +228,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
215 Expression expression = rule.getExpression(tableName, tableAlias); 228 Expression expression = rule.getExpression(tableName, tableAlias);
216 // 断言 229 // 断言
217 assertEquals("u.dept_id IN (10, 20) OR u.id = 1", expression.toString()); 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 package cn.iocoder.yudao.framework.tenant.core.web; 1 package cn.iocoder.yudao.framework.tenant.core.web;
2 2
3 -import cn.hutool.core.util.StrUtil;  
4 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 3 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  4 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
5 import org.springframework.web.filter.OncePerRequestFilter; 5 import org.springframework.web.filter.OncePerRequestFilter;
6 6
7 import javax.servlet.FilterChain; 7 import javax.servlet.FilterChain;
@@ -24,9 +24,9 @@ public class TenantContextWebFilter extends OncePerRequestFilter { @@ -24,9 +24,9 @@ public class TenantContextWebFilter extends OncePerRequestFilter {
24 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) 24 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
25 throws ServletException, IOException { 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 try { 31 try {
32 chain.doFilter(request, response); 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,9 +27,11 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
27 27
28 @Override 28 @Override
29 protected void doInit() { 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 // 初始化 Ftp 对象 36 // 初始化 Ftp 对象
35 this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(), 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,6 +44,7 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
42 String filePath = getFilePath(path); 44 String filePath = getFilePath(path);
43 String fileName = FileUtil.getName(filePath); 45 String fileName = FileUtil.getName(filePath);
44 String dir = StrUtil.removeSuffix(filePath, fileName); 46 String dir = StrUtil.removeSuffix(filePath, fileName);
  47 + ftp.reconnectIfTimeout();
45 boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content)); 48 boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));
46 if (!success) { 49 if (!success) {
47 throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath)); 50 throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
@@ -53,6 +56,7 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; { @@ -53,6 +56,7 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
53 @Override 56 @Override
54 public void delete(String path) { 57 public void delete(String path) {
55 String filePath = getFilePath(path); 58 String filePath = getFilePath(path);
  59 + ftp.reconnectIfTimeout();
56 ftp.delFile(filePath); 60 ftp.delFile(filePath);
57 } 61 }
58 62
@@ -60,8 +64,9 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; { @@ -60,8 +64,9 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
60 public byte[] getContent(String path) { 64 public byte[] getContent(String path) {
61 String filePath = getFilePath(path); 65 String filePath = getFilePath(path);
62 String fileName = FileUtil.getName(filePath); 66 String fileName = FileUtil.getName(filePath);
63 - String dir = StrUtil.removeSuffix(path, fileName); 67 + String dir = StrUtil.removeSuffix(filePath, fileName);
64 ByteArrayOutputStream out = new ByteArrayOutputStream(); 68 ByteArrayOutputStream out = new ByteArrayOutputStream();
  69 + ftp.reconnectIfTimeout();
65 ftp.download(dir, fileName, out); 70 ftp.download(dir, fileName, out);
66 return out.toByteArray(); 71 return out.toByteArray();
67 } 72 }
@@ -70,4 +75,4 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; { @@ -70,4 +75,4 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
70 return config.getBasePath() + path; 75 return config.getBasePath() + path;
71 } 76 }
72 77
73 -} 78 -}
  79 +}
74 \ No newline at end of file 80 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-security/pom.xml
@@ -44,18 +44,11 @@ @@ -44,18 +44,11 @@
44 <artifactId>spring-boot-starter-security</artifactId> 44 <artifactId>spring-boot-starter-security</artifactId>
45 </dependency> 45 </dependency>
46 46
47 - <!-- TODO 芋艿: --> 47 + <!-- 业务组件 -->
48 <dependency> 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 </dependency> 52 </dependency>
60 </dependencies> 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,7 +6,6 @@ import org.springframework.validation.annotation.Validated;
6 6
7 import javax.validation.constraints.NotEmpty; 7 import javax.validation.constraints.NotEmpty;
8 import javax.validation.constraints.NotNull; 8 import javax.validation.constraints.NotNull;
9 -import java.time.Duration;  
10 9
11 @ConfigurationProperties(prefix = "yudao.security") 10 @ConfigurationProperties(prefix = "yudao.security")
12 @Validated 11 @Validated
@@ -18,18 +17,6 @@ public class SecurityProperties { @@ -18,18 +17,6 @@ public class SecurityProperties {
18 */ 17 */
19 @NotEmpty(message = "Token Header 不能为空") 18 @NotEmpty(message = "Token Header 不能为空")
20 private String tokenHeader; 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 * mock 模式的开关 22 * mock 模式的开关
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java
1 package cn.iocoder.yudao.framework.security.config; 1 package cn.iocoder.yudao.framework.security.config;
2 2
3 import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect; 3 import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect;
4 -import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;  
5 import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy; 4 import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
6 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter; 5 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
7 import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl; 6 import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
8 import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl; 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 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; 8 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
  9 +import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
13 import org.springframework.beans.factory.config.MethodInvokingFactoryBean; 10 import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
14 import org.springframework.boot.context.properties.EnableConfigurationProperties; 11 import org.springframework.boot.context.properties.EnableConfigurationProperties;
15 import org.springframework.context.annotation.Bean; 12 import org.springframework.context.annotation.Bean;
@@ -19,10 +16,8 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -19,10 +16,8 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
19 import org.springframework.security.crypto.password.PasswordEncoder; 16 import org.springframework.security.crypto.password.PasswordEncoder;
20 import org.springframework.security.web.AuthenticationEntryPoint; 17 import org.springframework.security.web.AuthenticationEntryPoint;
21 import org.springframework.security.web.access.AccessDeniedHandler; 18 import org.springframework.security.web.access.AccessDeniedHandler;
22 -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;  
23 19
24 import javax.annotation.Resource; 20 import javax.annotation.Resource;
25 -import java.util.List;  
26 21
27 /** 22 /**
28 * Spring Security 自动配置类,主要用于相关组件的配置 23 * Spring Security 自动配置类,主要用于相关组件的配置
@@ -64,14 +59,6 @@ public class YudaoSecurityAutoConfiguration { @@ -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 * Spring Security 加密器 62 * Spring Security 加密器
76 * 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器 63 * 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器
77 * 64 *
@@ -86,19 +73,9 @@ public class YudaoSecurityAutoConfiguration { @@ -86,19 +73,9 @@ public class YudaoSecurityAutoConfiguration {
86 * Token 认证过滤器 Bean 73 * Token 认证过滤器 Bean
87 */ 74 */
88 @Bean 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 package cn.iocoder.yudao.framework.security.config; 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 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter; 3 import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
6 import cn.iocoder.yudao.framework.web.config.WebProperties; 4 import cn.iocoder.yudao.framework.web.config.WebProperties;
7 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 5 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -9,7 +7,6 @@ import org.springframework.context.annotation.Bean; @@ -9,7 +7,6 @@ import org.springframework.context.annotation.Bean;
9 import org.springframework.context.annotation.Configuration; 7 import org.springframework.context.annotation.Configuration;
10 import org.springframework.http.HttpMethod; 8 import org.springframework.http.HttpMethod;
11 import org.springframework.security.authentication.AuthenticationManager; 9 import org.springframework.security.authentication.AuthenticationManager;
12 -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;  
13 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 10 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
14 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 11 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
15 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -17,7 +14,6 @@ import org.springframework.security.config.http.SessionCreationPolicy; @@ -17,7 +14,6 @@ import org.springframework.security.config.http.SessionCreationPolicy;
17 import org.springframework.security.web.AuthenticationEntryPoint; 14 import org.springframework.security.web.AuthenticationEntryPoint;
18 import org.springframework.security.web.access.AccessDeniedHandler; 15 import org.springframework.security.web.access.AccessDeniedHandler;
19 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 16 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
20 -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;  
21 17
22 import javax.annotation.Resource; 18 import javax.annotation.Resource;
23 import java.util.List; 19 import java.util.List;
@@ -34,8 +30,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap @@ -34,8 +30,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
34 @Resource 30 @Resource
35 private WebProperties webProperties; 31 private WebProperties webProperties;
36 32
37 - @Resource  
38 - private MultiUserDetailsAuthenticationProvider authenticationProvider;  
39 /** 33 /**
40 * 认证失败处理类 Bean 34 * 认证失败处理类 Bean
41 */ 35 */
@@ -47,11 +41,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap @@ -47,11 +41,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
47 @Resource 41 @Resource
48 private AccessDeniedHandler accessDeniedHandler; 42 private AccessDeniedHandler accessDeniedHandler;
49 /** 43 /**
50 - * 退出处理类 Bean  
51 - */  
52 - @Resource  
53 - private LogoutSuccessHandler logoutSuccessHandler;  
54 - /**  
55 * Token 认证过滤器 Bean 44 * Token 认证过滤器 Bean
56 */ 45 */
57 @Resource 46 @Resource
@@ -77,14 +66,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap @@ -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 * 配置 URL 的安全配置 69 * 配置 URL 的安全配置
89 * 70 *
90 * anyRequest | 匹配所有请求路径 71 * anyRequest | 匹配所有请求路径
@@ -114,11 +95,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap @@ -114,11 +95,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
114 .headers().frameOptions().disable().and() 95 .headers().frameOptions().disable().and()
115 // 一堆自定义的 Spring Security 处理器 96 // 一堆自定义的 Spring Security 处理器
116 .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) 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 httpSecurity 102 httpSecurity
@@ -140,11 +118,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap @@ -140,11 +118,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
140 // 添加 JWT Filter 118 // 添加 JWT Filter
141 httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); 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 private String buildAppApi(String url) { 122 private String buildAppApi(String url) {
149 return webProperties.getAppApi().getPrefix() + url; 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 package cn.iocoder.yudao.framework.security.core; 1 package cn.iocoder.yudao.framework.security.core;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
4 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
5 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6 import com.fasterxml.jackson.annotation.JsonIgnore; 5 import com.fasterxml.jackson.annotation.JsonIgnore;
7 import lombok.Data; 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,7 +14,7 @@ import java.util.*;
16 * @author 芋道源码 14 * @author 芋道源码
17 */ 15 */
18 @Data 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,38 +27,10 @@ public class LoginUser implements UserDetails {
29 */ 27 */
30 private Integer userType; 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 private Long tenantId; 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,49 +40,6 @@ public class LoginUser implements UserDetails {
70 @JsonIgnore 40 @JsonIgnore
71 private Map<String, Object> context; 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 public void setContext(String key, Object value) { 43 public void setContext(String key, Object value) {
117 if (context == null) { 44 if (context == null) {
118 context = new HashMap<>(); 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 package cn.iocoder.yudao.framework.security.core.filter; 1 package cn.iocoder.yudao.framework.security.core.filter;
2 2
  3 +import cn.hutool.core.util.ObjectUtil;
3 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 7 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
6 import cn.iocoder.yudao.framework.security.config.SecurityProperties; 8 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
7 import cn.iocoder.yudao.framework.security.core.LoginUser; 9 import cn.iocoder.yudao.framework.security.core.LoginUser;
8 -import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;  
9 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; 10 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
10 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; 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 import lombok.RequiredArgsConstructor; 15 import lombok.RequiredArgsConstructor;
  16 +import org.springframework.security.access.AccessDeniedException;
12 import org.springframework.web.filter.OncePerRequestFilter; 17 import org.springframework.web.filter.OncePerRequestFilter;
13 18
14 import javax.servlet.FilterChain; 19 import javax.servlet.FilterChain;
@@ -28,24 +33,26 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { @@ -28,24 +33,26 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
28 33
29 private final SecurityProperties securityProperties; 34 private final SecurityProperties securityProperties;
30 35
31 - private final MultiUserDetailsAuthenticationProvider authenticationProvider;  
32 -  
33 private final GlobalExceptionHandler globalExceptionHandler; 36 private final GlobalExceptionHandler globalExceptionHandler;
34 37
  38 + private final OAuth2TokenApi oauth2TokenApi;
  39 +
35 @Override 40 @Override
36 @SuppressWarnings("NullableProblems") 41 @SuppressWarnings("NullableProblems")
37 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) 42 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
38 throws ServletException, IOException { 43 throws ServletException, IOException {
39 String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); 44 String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
40 if (StrUtil.isNotEmpty(token)) { 45 if (StrUtil.isNotEmpty(token)) {
  46 + Integer userType = WebFrameworkUtils.getLoginUserType(request);
41 try { 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 if (loginUser == null) { 51 if (loginUser == null) {
46 - loginUser = mockLoginUser(request, token); 52 + loginUser = mockLoginUser(request, token, userType);
47 } 53 }
48 - // 设置当前用户 54 +
  55 + // 2. 设置当前用户
49 if (loginUser != null) { 56 if (loginUser != null) {
50 SecurityFrameworkUtils.setLoginUser(loginUser, request); 57 SecurityFrameworkUtils.setLoginUser(loginUser, request);
51 } 58 }
@@ -60,6 +67,25 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { @@ -60,6 +67,25 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
60 chain.doFilter(request, response); 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,9 +93,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
67 * 93 *
68 * @param request 请求 94 * @param request 请求
69 * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号 95 * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
  96 + * @param userType 用户类型
70 * @return 模拟的 LoginUser 97 * @return 模拟的 LoginUser
71 */ 98 */
72 - private LoginUser mockLoginUser(HttpServletRequest request, String token) { 99 + private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) {
73 if (!securityProperties.getMockEnable()) { 100 if (!securityProperties.getMockEnable()) {
74 return null; 101 return null;
75 } 102 }
@@ -77,8 +104,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { @@ -77,8 +104,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
77 if (!token.startsWith(securityProperties.getMockSecret())) { 104 if (!token.startsWith(securityProperties.getMockSecret())) {
78 return null; 105 return null;
79 } 106 }
  107 + // 构建模拟用户
80 Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length())); 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,7 +11,7 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS
11 import org.springframework.util.StringUtils; 11 import org.springframework.util.StringUtils;
12 12
13 import javax.servlet.http.HttpServletRequest; 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,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 * @param loginUser 登录用户 85 * @param loginUser 登录用户
@@ -110,7 +99,7 @@ public class SecurityFrameworkUtils { @@ -110,7 +99,7 @@ public class SecurityFrameworkUtils {
110 private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { 99 private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
111 // 创建 UsernamePasswordAuthenticationToken 对象 100 // 创建 UsernamePasswordAuthenticationToken 对象
112 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( 101 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
113 - loginUser, null, loginUser.getAuthorities()); 102 + loginUser, null, Collections.emptyList());
114 authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 103 authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
115 return authenticationToken; 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,6 +7,7 @@ import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
7 import cn.iocoder.yudao.framework.web.core.filter.XssFilter; 7 import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
8 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; 8 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
9 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler; 9 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
  10 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
10 import org.springframework.beans.factory.annotation.Value; 11 import org.springframework.beans.factory.annotation.Value;
11 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 12 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
12 import org.springframework.boot.context.properties.EnableConfigurationProperties; 13 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -65,6 +66,13 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer { @@ -65,6 +66,13 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
65 return new GlobalResponseBodyHandler(); 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 // ========== Filter 相关 ========== 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 package cn.iocoder.yudao.framework.web.core.util; 1 package cn.iocoder.yudao.framework.web.core.util;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.web.config.WebProperties;
5 import org.springframework.web.context.request.RequestAttributes; 7 import org.springframework.web.context.request.RequestAttributes;
6 import org.springframework.web.context.request.RequestContextHolder; 8 import org.springframework.web.context.request.RequestContextHolder;
7 import org.springframework.web.context.request.ServletRequestAttributes; 9 import org.springframework.web.context.request.ServletRequestAttributes;
@@ -21,16 +23,43 @@ public class WebFrameworkUtils { @@ -21,16 +23,43 @@ public class WebFrameworkUtils {
21 23
22 private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result"; 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 public static void setLoginUserId(ServletRequest request, Long userId) { 46 public static void setLoginUserId(ServletRequest request, Long userId) {
25 request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId); 47 request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId);
26 } 48 }
27 49
  50 + /**
  51 + * 设置用户类型
  52 + *
  53 + * @param request 请求
  54 + * @param userType 用户类型
  55 + */
28 public static void setLoginUserType(ServletRequest request, Integer userType) { 56 public static void setLoginUserType(ServletRequest request, Integer userType) {
29 request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType); 57 request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType);
30 } 58 }
31 59
32 /** 60 /**
33 * 获得当前用户的编号,从请求中 61 * 获得当前用户的编号,从请求中
  62 + * 注意:该方法仅限于 framework 框架使用!!!
34 * 63 *
35 * @param request 请求 64 * @param request 请求
36 * @return 用户编号 65 * @return 用户编号
@@ -43,7 +72,8 @@ public class WebFrameworkUtils { @@ -43,7 +72,8 @@ public class WebFrameworkUtils {
43 } 72 }
44 73
45 /** 74 /**
46 - * 获得当前用户的类型,从请求中 75 + * 获得当前用户的类型
  76 + * 注意:该方法仅限于 web 相关的 framework 组件使用!!!
47 * 77 *
48 * @param request 请求 78 * @param request 请求
49 * @return 用户编号 79 * @return 用户编号
@@ -52,7 +82,19 @@ public class WebFrameworkUtils { @@ -52,7 +82,19 @@ public class WebFrameworkUtils {
52 if (request == null) { 82 if (request == null) {
53 return null; 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 public static Integer getLoginUserType() { 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,6 +4,7 @@ import cn.hutool.core.io.IoUtil;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 6 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
  7 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
7 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; 8 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
8 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO; 9 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO;
9 import cn.iocoder.yudao.module.infra.convert.file.FileConvert; 10 import cn.iocoder.yudao.module.infra.convert.file.FileConvert;
@@ -42,8 +43,9 @@ public class FileController { @@ -42,8 +43,9 @@ public class FileController {
42 @ApiImplicitParam(name = "file", value = "文件附件", required = true, dataTypeClass = MultipartFile.class), 43 @ApiImplicitParam(name = "file", value = "文件附件", required = true, dataTypeClass = MultipartFile.class),
43 @ApiImplicitParam(name = "path", value = "文件路径", example = "yudaoyuanma.png", dataTypeClass = String.class) 44 @ApiImplicitParam(name = "path", value = "文件路径", example = "yudaoyuanma.png", dataTypeClass = String.class)
44 }) 45 })
  46 + @OperateLog(logArgs = false) // 上传文件,没有记录操作日志的必要
45 public CommonResult<String> uploadFile(@RequestParam("file") MultipartFile file, 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 return success(fileService.createFile(path, IoUtil.readBytes(file.getInputStream()))); 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,6 +2,8 @@ package cn.iocoder.yudao.module.infra.service.file;
2 2
3 import cn.hutool.core.io.FileTypeUtil; 3 import cn.hutool.core.io.FileTypeUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.crypto.digest.DigestUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.file.core.client.FileClient; 8 import cn.iocoder.yudao.framework.file.core.client.FileClient;
7 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; 9 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
@@ -36,6 +38,12 @@ public class FileServiceImpl implements FileService { @@ -36,6 +38,12 @@ public class FileServiceImpl implements FileService {
36 38
37 @Override 39 @Override
38 public String createFile(String path, byte[] content) throws Exception { 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 FileClient client = fileConfigService.getMasterFileClient(); 48 FileClient client = fileConfigService.getMasterFileClient();
41 Assert.notNull(client, "客户端(master) 不能为空"); 49 Assert.notNull(client, "客户端(master) 不能为空");
@@ -46,7 +54,7 @@ public class FileServiceImpl implements FileService { @@ -46,7 +54,7 @@ public class FileServiceImpl implements FileService {
46 file.setConfigId(client.getId()); 54 file.setConfigId(client.getId());
47 file.setPath(path); 55 file.setPath(path);
48 file.setUrl(url); 56 file.setUrl(url);
49 - file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content))); 57 + file.setType(type);
50 file.setSize(content.length); 58 file.setSize(content.length);
51 fileMapper.insert(file); 59 fileMapper.insert(file);
52 return url; 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,7 +17,6 @@ public interface ErrorCodeConstants {
17 // ========== AUTH 模块 1004003000 ========== 17 // ========== AUTH 模块 1004003000 ==========
18 ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确"); 18 ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");
19 ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用"); 19 ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用");
20 - ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1004003002, "登录失败"); // 登录失败的兜底,未知原因  
21 ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期"); 20 ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期");
22 ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定"); 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 ### 请求 /login 接口 => 成功 1 ### 请求 /login 接口 => 成功
2 -POST {{appApi}}/member/login 2 +POST {{appApi}}/member/auth/login
3 Content-Type: application/json 3 Content-Type: application/json
4 tenant-id: {{appTenentId}} 4 tenant-id: {{appTenentId}}
5 5
@@ -19,7 +19,7 @@ tenant-id: {{appTenentId}} @@ -19,7 +19,7 @@ tenant-id: {{appTenentId}}
19 } 19 }
20 20
21 ### 请求 /sms-login 接口 => 成功 21 ### 请求 /sms-login 接口 => 成功
22 -POST {{appApi}}/member/sms-login 22 +POST {{appApi}}/member/auth/sms-login
23 Content-Type: application/json 23 Content-Type: application/json
24 tenant-id: {{appTenentId}} 24 tenant-id: {{appTenentId}}
25 25
@@ -29,7 +29,12 @@ tenant-id: {{appTenentId}} @@ -29,7 +29,12 @@ tenant-id: {{appTenentId}}
29 } 29 }
30 30
31 ### 请求 /logout 接口 => 成功 31 ### 请求 /logout 接口 => 成功
32 -POST {{appApi}}/member/logout 32 +POST {{appApi}}/member/auth/logout
33 Content-Type: application/json 33 Content-Type: application/json
34 Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66 34 Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66
35 tenant-id: {{appTenentId}} 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 package cn.iocoder.yudao.module.member.controller.app.auth; 1 package cn.iocoder.yudao.module.member.controller.app.auth;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 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 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; 7 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
  8 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
5 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; 9 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
6 import cn.iocoder.yudao.module.member.service.auth.MemberAuthService; 10 import cn.iocoder.yudao.module.member.service.auth.MemberAuthService;
7 import io.swagger.annotations.Api; 11 import io.swagger.annotations.Api;
@@ -13,11 +17,10 @@ import org.springframework.validation.annotation.Validated; @@ -13,11 +17,10 @@ import org.springframework.validation.annotation.Validated;
13 import org.springframework.web.bind.annotation.*; 17 import org.springframework.web.bind.annotation.*;
14 18
15 import javax.annotation.Resource; 19 import javax.annotation.Resource;
  20 +import javax.servlet.http.HttpServletRequest;
16 import javax.validation.Valid; 21 import javax.validation.Valid;
17 22
18 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 24 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
22 25
23 @Api(tags = "用户 APP - 认证") 26 @Api(tags = "用户 APP - 认证")
@@ -30,19 +33,39 @@ public class AppAuthController { @@ -30,19 +33,39 @@ public class AppAuthController {
30 @Resource 33 @Resource
31 private MemberAuthService authService; 34 private MemberAuthService authService;
32 35
  36 + @Resource
  37 + private SecurityProperties securityProperties;
  38 +
33 @PostMapping("/login") 39 @PostMapping("/login")
34 @ApiOperation("使用手机 + 密码登录") 40 @ApiOperation("使用手机 + 密码登录")
35 public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) { 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 @PostMapping("/sms-login") 65 @PostMapping("/sms-login")
41 @ApiOperation("使用手机 + 验证码登录") 66 @ApiOperation("使用手机 + 验证码登录")
42 public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) { 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 @PostMapping("/send-sms-code") 71 @PostMapping("/send-sms-code")
@@ -83,16 +106,14 @@ public class AppAuthController { @@ -83,16 +106,14 @@ public class AppAuthController {
83 106
84 @PostMapping("/social-quick-login") 107 @PostMapping("/social-quick-login")
85 @ApiOperation(value = "社交快捷登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户") 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 @PostMapping("/social-bind-login") 113 @PostMapping("/social-bind-login")
92 @ApiOperation(value = "社交绑定登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定") 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,14 +7,25 @@ import lombok.Builder;
7 import lombok.Data; 7 import lombok.Data;
8 import lombok.NoArgsConstructor; 8 import lombok.NoArgsConstructor;
9 9
10 -@ApiModel("用户 APP - 手机密码登录 Response VO") 10 +import java.util.Date;
  11 +
  12 +@ApiModel("用户 APP - 登录 Response VO")
11 @Data 13 @Data
12 @NoArgsConstructor 14 @NoArgsConstructor
13 @AllArgsConstructor 15 @AllArgsConstructor
14 @Builder 16 @Builder
15 public class AppAuthLoginRespVO { 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 package cn.iocoder.yudao.module.member.convert.auth; 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 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; 3 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
6 import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO; 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 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; 6 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
9 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; 7 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
10 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 8 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
11 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO; 9 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
12 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; 10 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
13 import org.mapstruct.Mapper; 11 import org.mapstruct.Mapper;
14 -import org.mapstruct.Mapping;  
15 import org.mapstruct.factory.Mappers; 12 import org.mapstruct.factory.Mappers;
16 13
17 @Mapper 14 @Mapper
@@ -19,14 +16,6 @@ public interface AuthConvert { @@ -19,14 +16,6 @@ public interface AuthConvert {
19 16
20 AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class); 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 SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO); 19 SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO);
31 SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO); 20 SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO);
32 SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO); 21 SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
@@ -35,4 +24,6 @@ public interface AuthConvert { @@ -35,4 +24,6 @@ public interface AuthConvert {
35 SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp); 24 SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
36 SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp); 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 package cn.iocoder.yudao.module.member.service.auth; 1 package cn.iocoder.yudao.module.member.service.auth;
2 2
3 -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;  
4 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; 3 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
5 4
6 import javax.validation.Valid; 5 import javax.validation.Valid;
@@ -12,47 +11,46 @@ import javax.validation.Valid; @@ -12,47 +11,46 @@ import javax.validation.Valid;
12 * 11 *
13 * @author 芋道源码 12 * @author 芋道源码
14 */ 13 */
15 -public interface MemberAuthService extends SecurityAuthFrameworkService { 14 +public interface MemberAuthService {
16 15
17 /** 16 /**
18 * 手机 + 密码登录 17 * 手机 + 密码登录
19 * 18 *
20 * @param reqVO 登录信息 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 * @param reqVO 登陆信息 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 * 社交登录,使用 code 授权码 40 * 社交登录,使用 code 授权码
39 * 41 *
40 * @param reqVO 登录信息 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 * @param reqVO 登录信息 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 * 获得社交认证 URL 56 * 获得社交认证 URL
@@ -84,4 +82,11 @@ public interface MemberAuthService extends SecurityAuthFrameworkService { @@ -84,4 +82,11 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
84 */ 82 */
85 void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO); 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 package cn.iocoder.yudao.module.member.service.auth; 1 package cn.iocoder.yudao.module.member.service.auth;
2 2
3 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.ObjectUtil;
4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 6 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; 7 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
7 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 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 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; 9 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
11 import cn.iocoder.yudao.module.member.convert.auth.AuthConvert; 10 import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
12 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; 11 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
13 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper; 12 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
14 import cn.iocoder.yudao.module.member.service.user.MemberUserService; 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 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi; 17 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
17 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; 18 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
18 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; 19 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
19 import cn.iocoder.yudao.module.system.api.social.SocialUserApi; 20 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
  21 +import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientIdEnum;
20 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; 22 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
21 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; 23 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
22 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; 24 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
23 import com.google.common.annotations.VisibleForTesting; 25 import com.google.common.annotations.VisibleForTesting;
24 import lombok.extern.slf4j.Slf4j; 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 import org.springframework.security.crypto.password.PasswordEncoder; 27 import org.springframework.security.crypto.password.PasswordEncoder;
34 import org.springframework.stereotype.Service; 28 import org.springframework.stereotype.Service;
35 import org.springframework.transaction.annotation.Transactional; 29 import org.springframework.transaction.annotation.Transactional;
@@ -51,19 +45,15 @@ import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*; @@ -51,19 +45,15 @@ import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
51 public class MemberAuthServiceImpl implements MemberAuthService { 45 public class MemberAuthServiceImpl implements MemberAuthService {
52 46
53 @Resource 47 @Resource
54 - @Lazy // 延迟加载,因为存在相互依赖的问题  
55 - private AuthenticationManager authenticationManager;  
56 -  
57 - @Resource  
58 private MemberUserService userService; 48 private MemberUserService userService;
59 @Resource 49 @Resource
60 private SmsCodeApi smsCodeApi; 50 private SmsCodeApi smsCodeApi;
61 @Resource 51 @Resource
62 private LoginLogApi loginLogApi; 52 private LoginLogApi loginLogApi;
63 @Resource 53 @Resource
64 - private UserSessionApi userSessionApi;  
65 - @Resource  
66 private SocialUserApi socialUserApi; 54 private SocialUserApi socialUserApi;
  55 + @Resource
  56 + private OAuth2TokenApi oauth2TokenApi;
67 57
68 @Resource 58 @Resource
69 private PasswordEncoder passwordEncoder; 59 private PasswordEncoder passwordEncoder;
@@ -71,44 +61,31 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -71,44 +61,31 @@ public class MemberAuthServiceImpl implements MemberAuthService {
71 private MemberUserMapper userMapper; 61 private MemberUserMapper userMapper;
72 62
73 @Override 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 @Override 72 @Override
94 @Transactional 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 smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp)); 77 smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));
98 78
99 // 获得获得注册用户 79 // 获得获得注册用户
100 MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp); 80 MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp);
101 Assert.notNull(user, "获取用户失败,结果为空"); 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 @Override 87 @Override
111 - public String socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) { 88 + public AppAuthLoginRespVO socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO) {
112 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 89 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
113 Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(), 90 Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
114 reqVO.getCode(), reqVO.getState()); 91 reqVO.getCode(), reqVO.getState());
@@ -122,31 +99,30 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -122,31 +99,30 @@ public class MemberAuthServiceImpl implements MemberAuthService {
122 throw exception(USER_NOT_EXISTS); 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 @Override 106 @Override
133 - public String socialBindLogin(AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) { 107 + public AppAuthLoginRespVO socialBindLogin(AppAuthSocialBindLoginReqVO reqVO) {
134 // 使用手机号、手机验证码登录 108 // 使用手机号、手机验证码登录
135 AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder() 109 AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
136 .mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build(); 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 return token; 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 @Override 128 @Override
@@ -154,40 +130,32 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -154,40 +130,32 @@ public class MemberAuthServiceImpl implements MemberAuthService {
154 return socialUserApi.getAuthorizeUrl(type, redirectUri); 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 throw exception(AUTH_LOGIN_BAD_CREDENTIALS); 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 throw exception(AUTH_LOGIN_USER_DISABLED); 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 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); 155 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
186 reqDTO.setLogType(logType.getType()); 156 reqDTO.setLogType(logType.getType());
187 reqDTO.setTraceId(TracerUtils.getTraceId()); 157 reqDTO.setTraceId(TracerUtils.getTraceId());
188 - if (user != null) {  
189 - reqDTO.setUserId(user.getId());  
190 - } 158 + reqDTO.setUserId(userId);
191 reqDTO.setUserType(getUserType().getValue()); 159 reqDTO.setUserType(getUserType().getValue());
192 reqDTO.setUsername(mobile); 160 reqDTO.setUsername(mobile);
193 reqDTO.setUserAgent(ServletUtils.getUserAgent()); 161 reqDTO.setUserAgent(ServletUtils.getUserAgent());
@@ -195,72 +163,20 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -195,72 +163,20 @@ public class MemberAuthServiceImpl implements MemberAuthService {
195 reqDTO.setResult(loginResult.getResult()); 163 reqDTO.setResult(loginResult.getResult());
196 loginLogApi.createLoginLog(reqDTO); 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 @Override 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 public void logout(String token) { 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 return; 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 @Override 182 @Override
@@ -269,6 +185,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -269,6 +185,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
269 MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword()); 185 MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
270 186
271 // 更新用户密码 187 // 更新用户密码
  188 + // TODO 芋艿:需要重构到用户模块
272 userMapper.updateById(MemberUserDO.builder().id(userDO.getId()) 189 userMapper.updateById(MemberUserDO.builder().id(userDO.getId())
273 .password(passwordEncoder.encode(reqVO.getPassword())).build()); 190 .password(passwordEncoder.encode(reqVO.getPassword())).build());
274 } 191 }
@@ -293,6 +210,12 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -293,6 +210,12 @@ public class MemberAuthServiceImpl implements MemberAuthService {
293 smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP())); 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,17 +244,29 @@ public class MemberAuthServiceImpl implements MemberAuthService {
321 return user; 244 return user;
322 } 245 }
323 246
324 - private void createLogoutLog(Long userId, String username) { 247 + private void createLogoutLog(Long userId) {
325 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); 248 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
326 reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType()); 249 reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
327 reqDTO.setTraceId(TracerUtils.getTraceId()); 250 reqDTO.setTraceId(TracerUtils.getTraceId());
328 reqDTO.setUserId(userId); 251 reqDTO.setUserId(userId);
329 reqDTO.setUserType(getUserType().getValue()); 252 reqDTO.setUserType(getUserType().getValue());
330 - reqDTO.setUsername(username); 253 + reqDTO.setUsername(getMobile(userId));
331 reqDTO.setUserAgent(ServletUtils.getUserAgent()); 254 reqDTO.setUserAgent(ServletUtils.getUserAgent());
332 reqDTO.setUserIp(getClientIP()); 255 reqDTO.setUserIp(getClientIP());
333 reqDTO.setResult(LoginResultEnum.SUCCESS.getResult()); 256 reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
334 loginLogApi.createLoginLog(reqDTO); 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,4 +69,13 @@ public interface MemberUserService {
69 */ 69 */
70 void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO); 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,7 +69,7 @@ public class MemberUserServiceImpl implements MemberUserService {
69 MemberUserDO user = new MemberUserDO(); 69 MemberUserDO user = new MemberUserDO();
70 user.setMobile(mobile); 70 user.setMobile(mobile);
71 user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启 71 user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
72 - user.setPassword(passwordEncoder.encode(password)); // 加密密码 72 + user.setPassword(encodePassword(password)); // 加密密码
73 user.setRegisterIp(registerIp); 73 user.setRegisterIp(registerIp);
74 memberUserMapper.insert(user); 74 memberUserMapper.insert(user);
75 return user; 75 return user;
@@ -127,6 +127,21 @@ public class MemberUserServiceImpl implements MemberUserService { @@ -127,6 +127,21 @@ public class MemberUserServiceImpl implements MemberUserService {
127 memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build()); 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 @VisibleForTesting 145 @VisibleForTesting
131 public MemberUserDO checkUserExists(Long id) { 146 public MemberUserDO checkUserExists(Long id) {
132 if (id == null) { 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,14 +9,13 @@ import cn.iocoder.yudao.module.member.controller.app.auth.vo.AppAuthUpdatePasswo
9 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; 9 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
10 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper; 10 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
11 import cn.iocoder.yudao.module.member.service.user.MemberUserService; 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 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi; 13 import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
14 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; 14 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
15 import cn.iocoder.yudao.module.system.api.social.SocialUserApi; 15 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
16 import org.junit.jupiter.api.Test; 16 import org.junit.jupiter.api.Test;
17 import org.springframework.boot.test.mock.mockito.MockBean; 17 import org.springframework.boot.test.mock.mockito.MockBean;
18 import org.springframework.context.annotation.Import; 18 import org.springframework.context.annotation.Import;
19 -import org.springframework.security.authentication.AuthenticationManager;  
20 import org.springframework.security.crypto.password.PasswordEncoder; 19 import org.springframework.security.crypto.password.PasswordEncoder;
21 20
22 import javax.annotation.Resource; 21 import javax.annotation.Resource;
@@ -38,8 +37,8 @@ import static org.mockito.Mockito.when; @@ -38,8 +37,8 @@ import static org.mockito.Mockito.when;
38 @Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class}) 37 @Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class})
39 public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest { 38 public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
40 39
41 - @MockBean  
42 - private AuthenticationManager authenticationManager; 40 + // TODO @芋艿:登录相关的单测,待补全
  41 +
43 @MockBean 42 @MockBean
44 private MemberUserService userService; 43 private MemberUserService userService;
45 @MockBean 44 @MockBean
@@ -47,7 +46,7 @@ public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest { @@ -47,7 +46,7 @@ public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
47 @MockBean 46 @MockBean
48 private LoginLogApi loginLogApi; 47 private LoginLogApi loginLogApi;
49 @MockBean 48 @MockBean
50 - private UserSessionApi userSessionApi; 49 + private OAuth2TokenApi oauth2TokenApi;
51 @MockBean 50 @MockBean
52 private SocialUserApi socialUserApi; 51 private SocialUserApi socialUserApi;
53 @MockBean 52 @MockBean
yudao-module-system/yudao-module-system-api/pom.xml
@@ -29,13 +29,6 @@ @@ -29,13 +29,6 @@
29 <optional>true</optional> 29 <optional>true</optional>
30 </dependency> 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 </dependencies> 32 </dependencies>
40 33
41 </project> 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,7 +12,6 @@ public interface ErrorCodeConstants {
12 // ========== AUTH 模块 1002000000 ========== 12 // ========== AUTH 模块 1002000000 ==========
13 ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确"); 13 ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确");
14 ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用"); 14 ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用");
15 - ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登录失败的兜底,未知原因  
16 ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在"); 15 ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
17 ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确"); 16 ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
18 ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定"); 17 ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
@@ -120,8 +119,11 @@ public interface ErrorCodeConstants { @@ -120,8 +119,11 @@ public interface ErrorCodeConstants {
120 ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定"); 119 ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定");
121 ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户"); 120 ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户");
122 121
123 - // ========== 系统铭感词 1002019000 ========= 122 + // ========== 系统敏感词 1002019000 =========
124 ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在"); 123 ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在");
125 ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在"); 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 +12,10 @@ public enum LoginLogTypeEnum {
12 12
13 LOGIN_USERNAME(100), // 使用账号登录 13 LOGIN_USERNAME(100), // 使用账号登录
14 LOGIN_SOCIAL(101), // 使用社交登录 14 LOGIN_SOCIAL(101), // 使用社交登录
15 - LOGIN_MOCK(102), // 使用 Mock 登录  
16 LOGIN_MOBILE(103), // 使用手机登陆 15 LOGIN_MOBILE(103), // 使用手机登陆
17 LOGIN_SMS(104), // 使用短信登陆 16 LOGIN_SMS(104), // 使用短信登陆
18 17
19 LOGOUT_SELF(200), // 自己主动登出 18 LOGOUT_SELF(200), // 自己主动登出
20 - LOGOUT_TIMEOUT(201), // 超时登出  
21 LOGOUT_DELETE(202), // 强制退出 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 ### 请求 /login 接口 => 成功 1 ### 请求 /login 接口 => 成功
2 -POST {{baseUrl}}/system/login 2 +POST {{baseUrl}}/system/auth/login
3 Content-Type: application/json 3 Content-Type: application/json
4 tenant-id: {{adminTenentId}} 4 tenant-id: {{adminTenentId}}
5 5
@@ -11,7 +11,7 @@ tenant-id: {{adminTenentId}} @@ -11,7 +11,7 @@ tenant-id: {{adminTenentId}}
11 } 11 }
12 12
13 ### 请求 /login 接口 => 成功(无验证码) 13 ### 请求 /login 接口 => 成功(无验证码)
14 -POST {{baseUrl}}/system/login 14 +POST {{baseUrl}}/system/auth/login
15 Content-Type: application/json 15 Content-Type: application/json
16 tenant-id: {{adminTenentId}} 16 tenant-id: {{adminTenentId}}
17 17
@@ -21,7 +21,7 @@ tenant-id: {{adminTenentId}} @@ -21,7 +21,7 @@ tenant-id: {{adminTenentId}}
21 } 21 }
22 22
23 ### 请求 /get-permission-info 接口 => 成功 23 ### 请求 /get-permission-info 接口 => 成功
24 -GET {{baseUrl}}/system/get-permission-info 24 +GET {{baseUrl}}/system/auth/get-permission-info
25 Authorization: Bearer {{token}} 25 Authorization: Bearer {{token}}
26 tenant-id: {{adminTenentId}} 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 package cn.iocoder.yudao.module.system.controller.admin.auth; 1 package cn.iocoder.yudao.module.system.controller.admin.auth;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.util.collection.SetUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
6 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 7 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  8 +import cn.iocoder.yudao.framework.security.config.SecurityProperties;
7 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; 9 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
8 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; 10 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
9 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; 11 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
10 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; 12 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
11 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 13 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
  14 +import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
12 import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum; 15 import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
13 import cn.iocoder.yudao.module.system.service.auth.AdminAuthService; 16 import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
14 import cn.iocoder.yudao.module.system.service.permission.PermissionService; 17 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
@@ -24,18 +27,19 @@ import org.springframework.validation.annotation.Validated; @@ -24,18 +27,19 @@ import org.springframework.validation.annotation.Validated;
24 import org.springframework.web.bind.annotation.*; 27 import org.springframework.web.bind.annotation.*;
25 28
26 import javax.annotation.Resource; 29 import javax.annotation.Resource;
  30 +import javax.servlet.http.HttpServletRequest;
27 import javax.validation.Valid; 31 import javax.validation.Valid;
28 import java.util.List; 32 import java.util.List;
  33 +import java.util.Set;
29 34
30 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 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 @Api(tags = "管理后台 - 认证") 40 @Api(tags = "管理后台 - 认证")
37 @RestController 41 @RestController
38 -@RequestMapping("/system/auth") // 暂时不跟 /auth 结尾 42 +@RequestMapping("/system/auth")
39 @Validated 43 @Validated
40 @Slf4j 44 @Slf4j
41 public class AuthController { 45 public class AuthController {
@@ -51,13 +55,33 @@ public class AuthController { @@ -51,13 +55,33 @@ public class AuthController {
51 @Resource 55 @Resource
52 private SocialUserService socialUserService; 56 private SocialUserService socialUserService;
53 57
  58 + @Resource
  59 + private SecurityProperties securityProperties;
  60 +
54 @PostMapping("/login") 61 @PostMapping("/login")
55 @ApiOperation("使用账号密码登录") 62 @ApiOperation("使用账号密码登录")
56 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 63 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
57 public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) { 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 @GetMapping("/get-permission-info") 87 @GetMapping("/get-permission-info")
@@ -69,12 +93,12 @@ public class AuthController { @@ -69,12 +93,12 @@ public class AuthController {
69 return null; 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 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()), 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 return success(AuthConvert.INSTANCE.convert(user, roleList, menuList)); 103 return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
80 } 104 }
@@ -82,11 +106,12 @@ public class AuthController { @@ -82,11 +106,12 @@ public class AuthController {
82 @GetMapping("/list-menus") 106 @GetMapping("/list-menus")
83 @ApiOperation("获得登录用户的菜单列表") 107 @ApiOperation("获得登录用户的菜单列表")
84 public CommonResult<List<AuthMenuRespVO>> getMenus() { 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 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型 113 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
89 - SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的 114 + singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
90 // 转换成 Tree 结构返回 115 // 转换成 Tree 结构返回
91 return success(AuthConvert.INSTANCE.buildMenuTree(menuList)); 116 return success(AuthConvert.INSTANCE.buildMenuTree(menuList));
92 } 117 }
@@ -97,9 +122,7 @@ public class AuthController { @@ -97,9 +122,7 @@ public class AuthController {
97 @ApiOperation("使用短信验证码登录") 122 @ApiOperation("使用短信验证码登录")
98 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 123 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
99 public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) { 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 @PostMapping("/send-sms-code") 128 @PostMapping("/send-sms-code")
@@ -127,18 +150,14 @@ public class AuthController { @@ -127,18 +150,14 @@ public class AuthController {
127 @ApiOperation("社交快捷登录,使用 code 授权码") 150 @ApiOperation("社交快捷登录,使用 code 授权码")
128 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 151 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
129 public CommonResult<AuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) { 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 @PostMapping("/social-bind-login") 156 @PostMapping("/social-bind-login")
136 @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码") 157 @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码")
137 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 158 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
138 public CommonResult<AuthLoginRespVO> socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) { 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,14 +7,25 @@ import lombok.Builder;
7 import lombok.Data; 7 import lombok.Data;
8 import lombok.NoArgsConstructor; 8 import lombok.NoArgsConstructor;
9 9
10 -@ApiModel("管理后台 - 账号密码登录 Response VO") 10 +import java.util.Date;
  11 +
  12 +@ApiModel("管理后台 - 登录 Response VO")
11 @Data 13 @Data
12 @NoArgsConstructor 14 @NoArgsConstructor
13 @AllArgsConstructor 15 @AllArgsConstructor
14 @Builder 16 @Builder
15 public class AuthLoginRespVO { 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 package cn.iocoder.yudao.module.system.convert.auth; 1 package cn.iocoder.yudao.module.system.convert.auth;
2 2
3 -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;  
4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 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 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; 4 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
8 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; 5 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
9 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 6 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
10 -import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;  
11 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; 7 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
  8 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
12 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; 9 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
13 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; 10 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
14 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 11 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
15 import cn.iocoder.yudao.module.system.enums.permission.MenuIdEnum; 12 import cn.iocoder.yudao.module.system.enums.permission.MenuIdEnum;
16 import org.mapstruct.Mapper; 13 import org.mapstruct.Mapper;
17 -import org.mapstruct.Mapping;  
18 import org.mapstruct.factory.Mappers; 14 import org.mapstruct.factory.Mappers;
19 import org.slf4j.LoggerFactory; 15 import org.slf4j.LoggerFactory;
20 16
@@ -25,13 +21,7 @@ public interface AuthConvert { @@ -25,13 +21,7 @@ public interface AuthConvert {
25 21
26 AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class); 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 default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) { 26 default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
37 return AuthPermissionInfoRespVO.builder() 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,43 +2,34 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth;
2 2
3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 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 import lombok.Data; 6 import lombok.Data;
9 import lombok.EqualsAndHashCode; 7 import lombok.EqualsAndHashCode;
  8 +import lombok.experimental.Accessors;
10 9
11 import java.util.Date; 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 * @author 芋道源码 15 * @author 芋道源码
21 */ 16 */
22 -@TableName(value = "system_user_session")  
23 -@KeySequence(value = "system_user_session_seq") 17 +@TableName("system_oauth2_code")
24 @Data 18 @Data
25 -@Builder  
26 @EqualsAndHashCode(callSuper = true) 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 private Long id; 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 private Long userId; 34 private Long userId;
44 /** 35 /**
@@ -47,25 +38,25 @@ public class UserSessionDO extends BaseDO { @@ -47,25 +38,25 @@ public class UserSessionDO extends BaseDO {
47 * 枚举 {@link UserTypeEnum} 38 * 枚举 {@link UserTypeEnum}
48 */ 39 */
49 private Integer userType; 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 package cn.iocoder.yudao.module.system.dal.redis; 1 package cn.iocoder.yudao.module.system.dal.redis;
2 2
3 import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; 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 import java.time.Duration; 6 import java.time.Duration;
7 7
@@ -18,9 +18,9 @@ public interface RedisKeyConstants { @@ -18,9 +18,9 @@ public interface RedisKeyConstants {
18 "captcha_code:%s", // 参数为 uuid 18 "captcha_code:%s", // 参数为 uuid
19 STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); 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 RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到 25 RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
26 "social_auth_state:%s", // 参数为 state 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,6 +20,8 @@ public class SecurityConfiguration {
20 public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) { 20 public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
21 // 登录的接口 21 // 登录的接口
22 registry.antMatchers(buildAdminApi("/system/auth/login")).permitAll(); 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 registry.antMatchers(buildAdminApi("/system/auth/social-auth-redirect")).permitAll(); 26 registry.antMatchers(buildAdminApi("/system/auth/social-auth-redirect")).permitAll();
25 registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll(); 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 package cn.iocoder.yudao.module.system.service.auth; 1 package cn.iocoder.yudao.module.system.service.auth;
2 2
3 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; 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 import javax.validation.Valid; 5 import javax.validation.Valid;
7 6
8 /** 7 /**
9 * 管理后台的认证 Service 接口 8 * 管理后台的认证 Service 接口
10 * 9 *
11 - * 提供用户的账号密码登录、token 的校验等认证相关的功能 10 + * 提供用户的登录、登出的能力
12 * 11 *
13 * @author 芋道源码 12 * @author 芋道源码
14 */ 13 */
15 -public interface AdminAuthService extends SecurityAuthFrameworkService { 14 +public interface AdminAuthService {
16 15
17 /** 16 /**
18 * 账号登录 17 * 账号登录
19 * 18 *
20 * @param reqVO 登录信息 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,30 +40,32 @@ public interface AdminAuthService extends SecurityAuthFrameworkService {
35 * 短信登录 40 * 短信登录
36 * 41 *
37 * @param reqVO 登录信息 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 * 社交快捷登录,使用 code 授权码 48 * 社交快捷登录,使用 code 授权码
46 * 49 *
47 * @param reqVO 登录信息 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 * 社交绑定登录,使用 code 授权码 + 账号密码 56 * 社交绑定登录,使用 code 授权码 + 账号密码
56 * 57 *
57 * @param reqVO 登录信息 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 package cn.iocoder.yudao.module.system.service.auth; 1 package cn.iocoder.yudao.module.system.service.auth;
2 2
  3 +import cn.hutool.core.util.ObjectUtil;
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 5 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; 6 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
6 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 7 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
7 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; 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 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; 9 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
11 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; 10 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
12 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; 11 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
13 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; 12 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
  13 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
14 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 14 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
  15 +import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientIdEnum;
15 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; 16 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
16 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; 17 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
17 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; 18 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
18 import cn.iocoder.yudao.module.system.service.common.CaptchaService; 19 import cn.iocoder.yudao.module.system.service.common.CaptchaService;
19 import cn.iocoder.yudao.module.system.service.logger.LoginLogService; 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 import cn.iocoder.yudao.module.system.service.social.SocialUserService; 22 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
22 import cn.iocoder.yudao.module.system.service.user.AdminUserService; 23 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
  24 +import com.google.common.annotations.VisibleForTesting;
23 import lombok.extern.slf4j.Slf4j; 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 import org.springframework.stereotype.Service; 26 import org.springframework.stereotype.Service;
34 -import org.springframework.util.Assert;  
35 27
36 import javax.annotation.Resource; 28 import javax.annotation.Resource;
37 import javax.validation.Validator; 29 import javax.validation.Validator;
38 import java.util.Objects; 30 import java.util.Objects;
39 -import java.util.Set;  
40 31
41 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
42 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; 33 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
43 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; 34 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
44 -import static java.util.Collections.singleton;  
45 35
46 /** 36 /**
47 * Auth Service 实现类 37 * Auth Service 实现类
@@ -53,22 +43,17 @@ import static java.util.Collections.singleton; @@ -53,22 +43,17 @@ import static java.util.Collections.singleton;
53 public class AdminAuthServiceImpl implements AdminAuthService { 43 public class AdminAuthServiceImpl implements AdminAuthService {
54 44
55 @Resource 45 @Resource
56 - @Lazy // 延迟加载,因为存在相互依赖的问题  
57 - private AuthenticationManager authenticationManager;  
58 -  
59 - @Autowired  
60 - @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") // UserService 存在重名  
61 private AdminUserService userService; 46 private AdminUserService userService;
62 @Resource 47 @Resource
63 - private PermissionService permissionService;  
64 - @Resource  
65 private CaptchaService captchaService; 48 private CaptchaService captchaService;
66 @Resource 49 @Resource
67 private LoginLogService loginLogService; 50 private LoginLogService loginLogService;
68 @Resource 51 @Resource
69 - private UserSessionService userSessionService; 52 + private OAuth2TokenService oauth2TokenService;
70 @Resource 53 @Resource
71 private SocialUserService socialUserService; 54 private SocialUserService socialUserService;
  55 + @Resource
  56 + private MemberService memberService;
72 57
73 @Resource 58 @Resource
74 private Validator validator; 59 private Validator validator;
@@ -77,39 +62,15 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -77,39 +62,15 @@ public class AdminAuthServiceImpl implements AdminAuthService {
77 private SmsCodeApi smsCodeApi; 62 private SmsCodeApi smsCodeApi;
78 63
79 @Override 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 verifyCaptcha(reqVO); 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 @Override 76 @Override
@@ -123,9 +84,9 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -123,9 +84,9 @@ public class AdminAuthServiceImpl implements AdminAuthService {
123 } 84 }
124 85
125 @Override 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 AdminUserDO user = userService.getUserByMobile(reqVO.getMobile()); 92 AdminUserDO user = userService.getUserByMobile(reqVO.getMobile());
@@ -133,14 +94,12 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -133,14 +94,12 @@ public class AdminAuthServiceImpl implements AdminAuthService {
133 throw exception(USER_NOT_EXISTS); 94 throw exception(USER_NOT_EXISTS);
134 } 95 }
135 96
136 - // 创建 LoginUser 对象  
137 - LoginUser loginUser = buildLoginUser(user);  
138 -  
139 // 缓存登陆用户到 Redis 中,返回 sessionId 编号 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 if (!captchaService.isCaptchaEnable()) { 104 if (!captchaService.isCaptchaEnable()) {
146 return; 105 return;
@@ -152,53 +111,47 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -152,53 +111,47 @@ public class AdminAuthServiceImpl implements AdminAuthService {
152 String code = captchaService.getCaptchaCode(reqVO.getUuid()); 111 String code = captchaService.getCaptchaCode(reqVO.getUuid());
153 if (code == null) { 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 throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND); 115 throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND);
157 } 116 }
158 // 验证码不正确 117 // 验证码不正确
159 if (!code.equals(reqVO.getCode())) { 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 throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR); 121 throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR);
163 } 122 }
164 // 正确,所以要删除下验证码 123 // 正确,所以要删除下验证码
165 captchaService.deleteCaptchaCode(reqVO.getUuid()); 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 final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; 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 throw exception(AUTH_LOGIN_BAD_CREDENTIALS); 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 throw exception(AUTH_LOGIN_USER_DISABLED); 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 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); 151 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
197 reqDTO.setLogType(logTypeEnum.getType()); 152 reqDTO.setLogType(logTypeEnum.getType());
198 reqDTO.setTraceId(TracerUtils.getTraceId()); 153 reqDTO.setTraceId(TracerUtils.getTraceId());
199 - if (user != null) {  
200 - reqDTO.setUserId(user.getId());  
201 - } 154 + reqDTO.setUserId(userId);
202 reqDTO.setUserType(getUserType().getValue()); 155 reqDTO.setUserType(getUserType().getValue());
203 reqDTO.setUsername(username); 156 reqDTO.setUsername(username);
204 reqDTO.setUserAgent(ServletUtils.getUserAgent()); 157 reqDTO.setUserAgent(ServletUtils.getUserAgent());
@@ -206,23 +159,13 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -206,23 +159,13 @@ public class AdminAuthServiceImpl implements AdminAuthService {
206 reqDTO.setResult(loginResult.getResult()); 159 reqDTO.setResult(loginResult.getResult());
207 loginLogService.createLoginLog(reqDTO); 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 @Override 167 @Override
225 - public String socialQuickLogin(AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) { 168 + public AuthLoginRespVO socialQuickLogin(AuthSocialQuickLoginReqVO reqVO) {
226 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 169 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
227 Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), 170 Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
228 reqVO.getCode(), reqVO.getState()); 171 reqVO.getCode(), reqVO.getState());
@@ -230,105 +173,82 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -230,105 +173,82 @@ public class AdminAuthServiceImpl implements AdminAuthService {
230 throw exception(AUTH_THIRD_LOGIN_NOT_BIND); 173 throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
231 } 174 }
232 175
233 - // 自动登录 176 + // 获得用户
234 AdminUserDO user = userService.getUser(userId); 177 AdminUserDO user = userService.getUser(userId);
235 if (user == null) { 178 if (user == null) {
236 throw exception(USER_NOT_EXISTS); 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 @Override 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 @Override 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 return; 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 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); 226 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
285 - reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType()); 227 + reqDTO.setLogType(logType);
286 reqDTO.setTraceId(TracerUtils.getTraceId()); 228 reqDTO.setTraceId(TracerUtils.getTraceId());
287 reqDTO.setUserId(userId); 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 reqDTO.setUserAgent(ServletUtils.getUserAgent()); 236 reqDTO.setUserAgent(ServletUtils.getUserAgent());
291 reqDTO.setUserIp(ServletUtils.getClientIP()); 237 reqDTO.setUserIp(ServletUtils.getClientIP());
292 reqDTO.setResult(LoginResultEnum.SUCCESS.getResult()); 238 reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
293 loginLogService.createLoginLog(reqDTO); 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 return null; 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,12 +3,12 @@ package cn.iocoder.yudao.module.system.service.permission;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.collection.CollectionUtil; 4 import cn.hutool.core.collection.CollectionUtil;
5 import cn.hutool.core.util.ArrayUtil; 5 import cn.hutool.core.util.ArrayUtil;
  6 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 8 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 9 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  10 +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
9 import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; 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 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; 12 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
13 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; 13 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
14 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; 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,6 +22,8 @@ import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
22 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum; 22 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
23 import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer; 23 import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
24 import cn.iocoder.yudao.module.system.service.dept.DeptService; 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 import com.google.common.collect.ImmutableMultimap; 27 import com.google.common.collect.ImmutableMultimap;
26 import com.google.common.collect.Multimap; 28 import com.google.common.collect.Multimap;
27 import com.google.common.collect.Sets; 29 import com.google.common.collect.Sets;
@@ -36,6 +38,10 @@ import org.springframework.transaction.support.TransactionSynchronizationManager @@ -36,6 +38,10 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
36 import javax.annotation.PostConstruct; 38 import javax.annotation.PostConstruct;
37 import javax.annotation.Resource; 39 import javax.annotation.Resource;
38 import java.util.*; 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 * 权限 Service 实现类 47 * 权限 Service 实现类
@@ -47,11 +53,6 @@ import java.util.*; @@ -47,11 +53,6 @@ import java.util.*;
47 public class PermissionServiceImpl implements PermissionService { 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 * 定时执行 {@link #schedulePeriodicRefresh()} 的周期 56 * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
56 * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高 57 * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
57 */ 58 */
@@ -93,6 +94,8 @@ public class PermissionServiceImpl implements PermissionService { @@ -93,6 +94,8 @@ public class PermissionServiceImpl implements PermissionService {
93 private MenuService menuService; 94 private MenuService menuService;
94 @Resource 95 @Resource
95 private DeptService deptService; 96 private DeptService deptService;
  97 + @Resource
  98 + private AdminUserService userService;
96 99
97 @Resource 100 @Resource
98 private PermissionProducer permissionProducer; 101 private PermissionProducer permissionProducer;
@@ -319,7 +322,7 @@ public class PermissionServiceImpl implements PermissionService { @@ -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 if (CollUtil.isEmpty(roleIds)) { 326 if (CollUtil.isEmpty(roleIds)) {
324 return false; 327 return false;
325 } 328 }
@@ -354,7 +357,7 @@ public class PermissionServiceImpl implements PermissionService { @@ -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 if (CollUtil.isEmpty(roleIds)) { 361 if (CollUtil.isEmpty(roleIds)) {
359 return false; 362 return false;
360 } 363 }
@@ -368,16 +371,18 @@ public class PermissionServiceImpl implements PermissionService { @@ -368,16 +371,18 @@ public class PermissionServiceImpl implements PermissionService {
368 } 371 }
369 372
370 @Override 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 return result; 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 for (RoleDO role : roles) { 386 for (RoleDO role : roles) {
382 // 为空时,跳过 387 // 为空时,跳过
383 if (role.getDataScope() == null) { 388 if (role.getDataScope() == null) {
@@ -393,20 +398,20 @@ public class PermissionServiceImpl implements PermissionService { @@ -393,20 +398,20 @@ public class PermissionServiceImpl implements PermissionService {
393 CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds()); 398 CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
394 // 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。 399 // 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
395 // 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉 400 // 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
396 - CollUtil.addAll(result.getDeptIds(), loginUser.getDeptId()); 401 + CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
397 continue; 402 continue;
398 } 403 }
399 // 情况三,DEPT_ONLY 404 // 情况三,DEPT_ONLY
400 if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) { 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 continue; 407 continue;
403 } 408 }
404 // 情况四,DEPT_DEPT_AND_CHILD 409 // 情况四,DEPT_DEPT_AND_CHILD
405 if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) { 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 CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, DeptDO::getId)); 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 continue; 415 continue;
411 } 416 }
412 // 情况五,SELF 417 // 情况五,SELF
@@ -415,11 +420,8 @@ public class PermissionServiceImpl implements PermissionService { @@ -415,11 +420,8 @@ public class PermissionServiceImpl implements PermissionService {
415 continue; 420 continue;
416 } 421 }
417 // 未知情况,error log 即可 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 return result; 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,6 +81,7 @@ public class TenantServiceImpl implements TenantService {
81 @Getter 81 @Getter
82 private volatile Date maxUpdateTime; 82 private volatile Date maxUpdateTime;
83 83
  84 + @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
84 @Autowired(required = false) // 由于 yudao.tenant.enable 配置项,可以关闭多租户的功能,所以这里只能不强制注入 85 @Autowired(required = false) // 由于 yudao.tenant.enable 配置项,可以关闭多租户的功能,所以这里只能不强制注入
85 private TenantProperties tenantProperties; 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,7 +105,6 @@ public interface AdminUserService {
105 */ 105 */
106 AdminUserDO getUserByMobile(String mobile); 106 AdminUserDO getUserByMobile(String mobile);
107 107
108 -  
109 /** 108 /**
110 * 获得用户分页列表 109 * 获得用户分页列表
111 * 110 *
@@ -209,4 +208,13 @@ public interface AdminUserService { @@ -209,4 +208,13 @@ public interface AdminUserService {
209 */ 208 */
210 List<AdminUserDO> getUsersByStatus(Integer status); 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,6 +25,7 @@ import cn.iocoder.yudao.module.system.service.tenant.TenantService;
25 import com.google.common.annotations.VisibleForTesting; 25 import com.google.common.annotations.VisibleForTesting;
26 import lombok.extern.slf4j.Slf4j; 26 import lombok.extern.slf4j.Slf4j;
27 import org.springframework.beans.factory.annotation.Value; 27 import org.springframework.beans.factory.annotation.Value;
  28 +import org.springframework.context.annotation.Lazy;
28 import org.springframework.security.crypto.password.PasswordEncoder; 29 import org.springframework.security.crypto.password.PasswordEncoder;
29 import org.springframework.stereotype.Service; 30 import org.springframework.stereotype.Service;
30 import org.springframework.transaction.annotation.Transactional; 31 import org.springframework.transaction.annotation.Transactional;
@@ -61,6 +62,7 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -61,6 +62,7 @@ public class AdminUserServiceImpl implements AdminUserService {
61 @Resource 62 @Resource
62 private PasswordEncoder passwordEncoder; 63 private PasswordEncoder passwordEncoder;
63 @Resource 64 @Resource
  65 + @Lazy // 延迟,避免循环依赖报错
64 private TenantService tenantService; 66 private TenantService tenantService;
65 67
66 @Resource 68 @Resource
@@ -146,7 +148,7 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -146,7 +148,7 @@ public class AdminUserServiceImpl implements AdminUserService {
146 checkOldPassword(id, reqVO.getOldPassword()); 148 checkOldPassword(id, reqVO.getOldPassword());
147 // 执行更新 149 // 执行更新
148 AdminUserDO updateObj = new AdminUserDO().setId(id); 150 AdminUserDO updateObj = new AdminUserDO().setId(id);
149 - updateObj.setPassword(passwordEncoder.encode(reqVO.getNewPassword())); // 加密密码 151 + updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码
150 userMapper.updateById(updateObj); 152 userMapper.updateById(updateObj);
151 } 153 }
152 154
@@ -170,7 +172,7 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -170,7 +172,7 @@ public class AdminUserServiceImpl implements AdminUserService {
170 // 更新密码 172 // 更新密码
171 AdminUserDO updateObj = new AdminUserDO(); 173 AdminUserDO updateObj = new AdminUserDO();
172 updateObj.setId(id); 174 updateObj.setId(id);
173 - updateObj.setPassword(passwordEncoder.encode(password)); // 加密密码 175 + updateObj.setPassword(encodePassword(password)); // 加密密码
174 userMapper.updateById(updateObj); 176 userMapper.updateById(updateObj);
175 } 177 }
176 178
@@ -203,11 +205,6 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -203,11 +205,6 @@ public class AdminUserServiceImpl implements AdminUserService {
203 return userMapper.selectByUsername(username); 205 return userMapper.selectByUsername(username);
204 } 206 }
205 207
206 - /**  
207 - * 通过手机号获取用户  
208 - * @param mobile  
209 - * @return  
210 - */  
211 @Override 208 @Override
212 public AdminUserDO getUserByMobile(String mobile) { 209 public AdminUserDO getUserByMobile(String mobile) {
213 return userMapper.selectByMobile(mobile); 210 return userMapper.selectByMobile(mobile);
@@ -393,7 +390,7 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -393,7 +390,7 @@ public class AdminUserServiceImpl implements AdminUserService {
393 if (user == null) { 390 if (user == null) {
394 throw exception(USER_NOT_EXISTS); 391 throw exception(USER_NOT_EXISTS);
395 } 392 }
396 - if (!passwordEncoder.matches(oldPassword, user.getPassword())) { 393 + if (!isPasswordMatch(oldPassword, user.getPassword())) {
397 throw exception(USER_PASSWORD_FAILED); 394 throw exception(USER_PASSWORD_FAILED);
398 } 395 }
399 } 396 }
@@ -419,7 +416,7 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -419,7 +416,7 @@ public class AdminUserServiceImpl implements AdminUserService {
419 AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername()); 416 AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
420 if (existUser == null) { 417 if (existUser == null) {
421 userMapper.insert(UserConvert.INSTANCE.convert(importUser) 418 userMapper.insert(UserConvert.INSTANCE.convert(importUser)
422 - .setPassword(passwordEncoder.encode(userInitPassword))); // 设置默认密码 419 + .setPassword(encodePassword(userInitPassword))); // 设置默认密码
423 respVO.getCreateUsernames().add(importUser.getUsername()); 420 respVO.getCreateUsernames().add(importUser.getUsername());
424 return; 421 return;
425 } 422 }
@@ -441,4 +438,19 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -441,4 +438,19 @@ public class AdminUserServiceImpl implements AdminUserService {
441 return userMapper.selectListByStatus(status); 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 package cn.iocoder.yudao.module.system.service.auth; 1 package cn.iocoder.yudao.module.system.service.auth;
2 2
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 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 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 5 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
6 import cn.iocoder.yudao.framework.test.core.util.AssertUtils; 6 import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
7 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; 7 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
8 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO; 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 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 11 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
10 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; 12 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
11 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; 13 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
12 import cn.iocoder.yudao.module.system.service.common.CaptchaService; 14 import cn.iocoder.yudao.module.system.service.common.CaptchaService;
13 -import cn.iocoder.yudao.module.system.service.dept.PostService;  
14 import cn.iocoder.yudao.module.system.service.logger.LoginLogService; 15 import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
15 -import cn.iocoder.yudao.module.system.service.permission.PermissionService;  
16 import cn.iocoder.yudao.module.system.service.social.SocialUserService; 16 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
17 import cn.iocoder.yudao.module.system.service.user.AdminUserService; 17 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
18 import org.junit.jupiter.api.BeforeEach; 18 import org.junit.jupiter.api.BeforeEach;
19 import org.junit.jupiter.api.Test; 19 import org.junit.jupiter.api.Test;
20 import org.springframework.boot.test.mock.mockito.MockBean; 20 import org.springframework.boot.test.mock.mockito.MockBean;
21 import org.springframework.context.annotation.Import; 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 import javax.annotation.Resource; 23 import javax.annotation.Resource;
31 import javax.validation.Validator; 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 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; 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 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; 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 import static org.mockito.ArgumentMatchers.eq; 31 import static org.mockito.ArgumentMatchers.eq;
41 import static org.mockito.Mockito.*; 32 import static org.mockito.Mockito.*;
42 33
@@ -49,23 +40,15 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @@ -49,23 +40,15 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
49 @MockBean 40 @MockBean
50 private AdminUserService userService; 41 private AdminUserService userService;
51 @MockBean 42 @MockBean
52 - private PermissionService permissionService;  
53 - @MockBean  
54 - private AuthenticationManager authenticationManager;  
55 - @MockBean  
56 - private Authentication authentication;  
57 - @MockBean  
58 private CaptchaService captchaService; 43 private CaptchaService captchaService;
59 @MockBean 44 @MockBean
60 private LoginLogService loginLogService; 45 private LoginLogService loginLogService;
61 @MockBean 46 @MockBean
62 - private UserSessionService userSessionService;  
63 - @MockBean  
64 private SocialUserService socialService; 47 private SocialUserService socialService;
65 @MockBean 48 @MockBean
66 - private PostService postService;  
67 - @MockBean  
68 private SmsCodeApi smsCodeApi; 49 private SmsCodeApi smsCodeApi;
  50 + @MockBean
  51 + private OAuth2TokenService oauth2TokenService;
69 52
70 @MockBean 53 @MockBean
71 private Validator validator; 54 private Validator validator;
@@ -76,216 +59,184 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @@ -76,216 +59,184 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
76 } 59 }
77 60
78 @Test 61 @Test
79 - public void testLoadUserByUsername_success() { 62 + public void testLogin0_success() {
80 // 准备参数 63 // 准备参数
81 String username = randomString(); 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 when(userService.getUserByUsername(eq(username))).thenReturn(user); 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 @Test 79 @Test
93 - public void testLoadUserByUsername_userNotFound() { 80 + public void testLogin0_userNotFound() {
94 // 准备参数 81 // 准备参数
95 String username = randomString(); 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 @Test 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 @Test 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 @Test 137 @Test
152 - public void testLogin_captchaCodeError() { 138 + public void testCaptcha_success() {
153 // 准备参数 139 // 准备参数
154 - String userIp = randomString();  
155 - String userAgent = randomString();  
156 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); 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 @Test 151 @Test
172 - public void testLogin_badCredentials() { 152 + public void testCaptcha_notFound() {
173 // 准备参数 153 // 准备参数
174 - String userIp = randomString();  
175 - String userAgent = randomString();  
176 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); 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 verify(loginLogService, times(1)).createLoginLog( 159 verify(loginLogService, times(1)).createLoginLog(
188 argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) 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 @Test 165 @Test
194 - public void testLogin_userDisabled() { 166 + public void testCaptcha_codeError() {
195 // 准备参数 167 // 准备参数
196 - String userIp = randomString();  
197 - String userAgent = randomString();  
198 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); 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 argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) 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 @Test 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 // mock 验证码正确 189 // mock 验证码正确
223 when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); 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 argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) 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 @Test 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 String token = randomString(); 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 && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())) 227 && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))
271 ); 228 );
272 } 229 }
273 230
274 @Test 231 @Test
275 - public void testLogout_success() { 232 + public void testLogout_fail() {
276 // 准备参数 233 // 准备参数
277 String token = randomString(); 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 package cn.iocoder.yudao.module.system.service.permission; 1 package cn.iocoder.yudao.module.system.service.permission;
2 2
3 import cn.hutool.core.collection.CollUtil; 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 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; 7 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
5 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; 8 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
6 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO; 9 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
7 import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO; 10 import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
  11 +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
8 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuBatchInsertMapper; 12 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuBatchInsertMapper;
9 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper; 13 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
10 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleBatchInsertMapper; 14 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleBatchInsertMapper;
11 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper; 15 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
  16 +import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
12 import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer; 17 import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
13 import cn.iocoder.yudao.module.system.service.dept.DeptService; 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 import org.junit.jupiter.api.Test; 20 import org.junit.jupiter.api.Test;
19 import org.springframework.boot.test.mock.mockito.MockBean; 21 import org.springframework.boot.test.mock.mockito.MockBean;
20 import org.springframework.context.annotation.Import; 22 import org.springframework.context.annotation.Import;
@@ -25,10 +27,10 @@ import java.util.List; @@ -25,10 +27,10 @@ import java.util.List;
25 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 27 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
26 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; 28 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
27 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 29 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  30 +import static java.util.Collections.singleton;
28 import static java.util.Collections.singletonList; 31 import static java.util.Collections.singletonList;
29 import static org.junit.jupiter.api.Assertions.*; 32 import static org.junit.jupiter.api.Assertions.*;
30 import static org.mockito.ArgumentMatchers.eq; 33 import static org.mockito.ArgumentMatchers.eq;
31 -import static org.mockito.ArgumentMatchers.same;  
32 import static org.mockito.Mockito.verify; 34 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.when; 35 import static org.mockito.Mockito.when;
34 36
@@ -55,6 +57,9 @@ public class PermissionServiceTest extends BaseDbUnitTest { @@ -55,6 +57,9 @@ public class PermissionServiceTest extends BaseDbUnitTest {
55 @MockBean 57 @MockBean
56 private DeptService deptService; 58 private DeptService deptService;
57 @MockBean 59 @MockBean
  60 + private AdminUserService userService;
  61 +
  62 + @MockBean
58 private PermissionProducer permissionProducer; 63 private PermissionProducer permissionProducer;
59 64
60 @Test 65 @Test
@@ -124,112 +129,119 @@ public class PermissionServiceTest extends BaseDbUnitTest { @@ -124,112 +129,119 @@ public class PermissionServiceTest extends BaseDbUnitTest {
124 assertPojoEquals(dbUserRoles.get(0), userRoleDO02); 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 @Test 132 @Test
142 public void testGetDeptDataPermission_All() { 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 assertTrue(result.getAll()); 147 assertTrue(result.getAll());
153 assertFalse(result.getSelf()); 148 assertFalse(result.getSelf());
154 assertTrue(CollUtil.isEmpty(result.getDeptIds())); 149 assertTrue(CollUtil.isEmpty(result.getDeptIds()));
155 - assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class));  
156 } 150 }
157 151
158 @Test 152 @Test
159 public void testGetDeptDataPermission_DeptCustom() { 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 assertFalse(result.getAll()); 169 assertFalse(result.getAll());
170 assertFalse(result.getSelf()); 170 assertFalse(result.getSelf());
171 assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size()); 171 assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());
172 assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds())); 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 @Test 176 @Test
178 public void testGetDeptDataPermission_DeptOnly() { 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 assertFalse(result.getAll()); 193 assertFalse(result.getAll());
189 assertFalse(result.getSelf()); 194 assertFalse(result.getSelf());
190 assertEquals(1, result.getDeptIds().size()); 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 @Test 199 @Test
196 public void testGetDeptDataPermission_DeptAndChild() { 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 // mock 方法(部门) 212 // mock 方法(部门)
203 DeptDO deptDO = randomPojo(DeptDO.class); 213 DeptDO deptDO = randomPojo(DeptDO.class);
204 - when(deptService.getDeptsByParentIdFromCache(eq(loginUser.getDeptId()), eq(true))) 214 + when(deptService.getDeptsByParentIdFromCache(eq(3L), eq(true)))
205 .thenReturn(singletonList(deptDO)); 215 .thenReturn(singletonList(deptDO));
206 216
207 // 调用 217 // 调用
208 - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser); 218 + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
209 // 断言 219 // 断言
210 assertFalse(result.getAll()); 220 assertFalse(result.getAll());
211 assertFalse(result.getSelf()); 221 assertFalse(result.getSelf());
212 assertEquals(2, result.getDeptIds().size()); 222 assertEquals(2, result.getDeptIds().size());
213 assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId())); 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 @Test 227 @Test
219 public void testGetDeptDataPermission_Self() { 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 assertFalse(result.getAll()); 242 assertFalse(result.getAll());
230 assertTrue(result.getSelf()); 243 assertTrue(result.getSelf());
231 assertTrue(CollUtil.isEmpty(result.getDeptIds())); 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,3 +20,4 @@ DELETE FROM &quot;system_social_user_bind&quot;;
20 DELETE FROM "system_tenant"; 20 DELETE FROM "system_tenant";
21 DELETE FROM "system_tenant_package"; 21 DELETE FROM "system_tenant_package";
22 DELETE FROM "system_sensitive_word"; 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,3 +470,21 @@ CREATE TABLE IF NOT EXISTS &quot;system_sensitive_word&quot; (
470 "deleted" bit NOT NULL DEFAULT FALSE, 470 "deleted" bit NOT NULL DEFAULT FALSE,
471 PRIMARY KEY ("id") 471 PRIMARY KEY ("id")
472 ) COMMENT '系统敏感词'; 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,8 +173,6 @@ wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-sta
173 yudao: 173 yudao:
174 security: 174 security:
175 token-header: Authorization 175 token-header: Authorization
176 - token-timeout: 1d  
177 - session-timeout: 30m  
178 mock-enable: true 176 mock-enable: true
179 mock-secret: test 177 mock-secret: test
180 xss: 178 xss:
yudao-server/src/main/resources/application-local.yaml
@@ -193,8 +193,6 @@ yudao: @@ -193,8 +193,6 @@ yudao:
193 enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试 193 enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试
194 security: 194 security:
195 token-header: Authorization 195 token-header: Authorization
196 - token-timeout: 1d  
197 - session-timeout: 1d  
198 mock-enable: true 196 mock-enable: true
199 mock-secret: test 197 mock-secret: test
200 xss: 198 xss:
yudao-server/src/main/resources/application.yaml
@@ -114,6 +114,7 @@ yudao: @@ -114,6 +114,7 @@ yudao:
114 - system_sms_template 114 - system_sms_template
115 - system_sms_log 115 - system_sms_log
116 - system_sensitive_word 116 - system_sensitive_word
  117 + - system_oauth2_client
117 - infra_codegen_column 118 - infra_codegen_column
118 - infra_codegen_table 119 - infra_codegen_table
119 - infra_test_demo 120 - infra_test_demo
yudao-ui-admin/src/api/login.js
1 import request from '@/utils/request' 1 import request from '@/utils/request'
  2 +import {getRefreshToken} from "@/utils/auth";
  3 +import service from "@/utils/request";
2 4
3 // 登录方法 5 // 登录方法
4 export function login(username, password, code, uuid) { 6 export function login(username, password, code, uuid) {
@@ -26,7 +28,7 @@ export function getInfo() { @@ -26,7 +28,7 @@ export function getInfo() {
26 // 退出方法 28 // 退出方法
27 export function logout() { 29 export function logout() {
28 return request({ 30 return request({
29 - url: '/system/logout', 31 + url: '/system/auth/logout',
30 method: 'post' 32 method: 'post'
31 }) 33 })
32 } 34 }
@@ -99,3 +101,11 @@ export function smsLogin(mobile, code) { @@ -99,3 +101,11 @@ export function smsLogin(mobile, code) {
99 } 101 }
100 }) 102 })
101 } 103 }
  104 +
  105 +// 刷新访问令牌
  106 +export function refreshToken() {
  107 + return service({
  108 + url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken(),
  109 + method: 'post'
  110 + })
  111 +}
yudao-ui-admin/src/api/system/oauth2/oauth2Client.js 0 → 100755
  1 +import request from '@/utils/request'
  2 +
  3 +// 创建 OAuth2 客户端
  4 +export function createOAuth2Client(data) {
  5 + return request({
  6 + url: '/system/oauth2-client/create',
  7 + method: 'post',
  8 + data: data
  9 + })
  10 +}
  11 +
  12 +// 更新 OAuth2 客户端
  13 +export function updateOAuth2Client(data) {
  14 + return request({
  15 + url: '/system/oauth2-client/update',
  16 + method: 'put',
  17 + data: data
  18 + })
  19 +}
  20 +
  21 +// 删除 OAuth2 客户端
  22 +export function deleteOAuth2Client(id) {
  23 + return request({
  24 + url: '/system/oauth2-client/delete?id=' + id,
  25 + method: 'delete'
  26 + })
  27 +}
  28 +
  29 +// 获得 OAuth2 客户端
  30 +export function getOAuth2Client(id) {
  31 + return request({
  32 + url: '/system/oauth2-client/get?id=' + id,
  33 + method: 'get'
  34 + })
  35 +}
  36 +
  37 +// 获得 OAuth2 客户端分页
  38 +export function getOAuth2ClientPage(query) {
  39 + return request({
  40 + url: '/system/oauth2-client/page',
  41 + method: 'get',
  42 + params: query
  43 + })
  44 +}
yudao-ui-admin/src/api/system/session.js renamed to yudao-ui-admin/src/api/system/oauth2/oauth2Token.js
1 import request from '@/utils/request' 1 import request from '@/utils/request'
2 2
3 -// 查询在线用户列表  
4 -export function list(query) { 3 +// 获得访问令牌分页
  4 +export function getAccessTokenPage(query) {
5 return request({ 5 return request({
6 - url: '/system/user-session/page', 6 + url: '/system/oauth2-token/page',
7 method: 'get', 7 method: 'get',
8 params: query 8 params: query
9 }) 9 })
10 } 10 }
11 11
12 -// 强退用户  
13 -export function forceLogout(tokenId) { 12 +// 删除访问令牌
  13 +export function deleteAccessToken(accessToken) {
14 return request({ 14 return request({
15 - url: '/system/user-session/delete?id=' + tokenId, 15 + url: '/system/oauth2-token/delete?accessToken=' + accessToken,
16 method: 'delete' 16 method: 'delete'
17 }) 17 })
18 } 18 }
yudao-ui-admin/src/components/Editor/index.vue
1 <template> 1 <template>
2 <div> 2 <div>
3 <el-upload 3 <el-upload
4 - :action="uploadUrl" 4 + :action="uploadFileUrl"
5 :before-upload="handleBeforeUpload" 5 :before-upload="handleBeforeUpload"
6 :on-success="handleUploadSuccess" 6 :on-success="handleUploadSuccess"
7 :on-error="handleUploadError" 7 :on-error="handleUploadError"
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 :headers="headers" 10 :headers="headers"
11 style="display: none" 11 style="display: none"
12 ref="upload" 12 ref="upload"
13 - v-if="this.type == 'url'" 13 + v-if="this.type === 'url'"
14 > 14 >
15 </el-upload> 15 </el-upload>
16 <div class="editor" ref="editor" :style="styles"></div> 16 <div class="editor" ref="editor" :style="styles"></div>
@@ -22,7 +22,7 @@ import Quill from &quot;quill&quot;; @@ -22,7 +22,7 @@ import Quill from &quot;quill&quot;;
22 import "quill/dist/quill.core.css"; 22 import "quill/dist/quill.core.css";
23 import "quill/dist/quill.snow.css"; 23 import "quill/dist/quill.snow.css";
24 import "quill/dist/quill.bubble.css"; 24 import "quill/dist/quill.bubble.css";
25 -import { getToken } from "@/utils/auth"; 25 +import { getAccessToken } from "@/utils/auth";
26 26
27 export default { 27 export default {
28 name: "Editor", 28 name: "Editor",
@@ -60,10 +60,8 @@ export default { @@ -60,10 +60,8 @@ export default {
60 }, 60 },
61 data() { 61 data() {
62 return { 62 return {
63 - uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址  
64 - headers: {  
65 - Authorization: "Bearer " + getToken()  
66 - }, 63 + uploadFileUrl: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址
  64 + headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
67 Quill: null, 65 Quill: null,
68 currentValue: "", 66 currentValue: "",
69 options: { 67 options: {
@@ -126,7 +124,7 @@ export default { @@ -126,7 +124,7 @@ export default {
126 const editor = this.$refs.editor; 124 const editor = this.$refs.editor;
127 this.Quill = new Quill(editor, this.options); 125 this.Quill = new Quill(editor, this.options);
128 // 如果设置了上传地址则自定义图片上传事件 126 // 如果设置了上传地址则自定义图片上传事件
129 - if (this.type == 'url') { 127 + if (this.type === 'url') {
130 let toolbar = this.Quill.getModule("toolbar"); 128 let toolbar = this.Quill.getModule("toolbar");
131 toolbar.addHandler("image", (value) => { 129 toolbar.addHandler("image", (value) => {
132 this.uploadType = "image"; 130 this.uploadType = "image";
@@ -172,11 +170,13 @@ export default { @@ -172,11 +170,13 @@ export default {
172 // 获取富文本组件实例 170 // 获取富文本组件实例
173 let quill = this.Quill; 171 let quill = this.Quill;
174 // 如果上传成功 172 // 如果上传成功
175 - if (res.code == 200) { 173 + // edit by 芋道源码
  174 + if (res.code === 200 || res.code === 0) {
176 // 获取光标所在位置 175 // 获取光标所在位置
177 let length = quill.getSelection().index; 176 let length = quill.getSelection().index;
178 // 插入图片 res.url为服务器返回的图片地址 177 // 插入图片 res.url为服务器返回的图片地址
179 - quill.insertEmbed(length, "image", process.env.VUE_APP_BASE_API + res.fileName); 178 + // edit by 芋道源码
  179 + quill.insertEmbed(length, "image", res.data);
180 // 调整光标到最后 180 // 调整光标到最后
181 quill.setSelection(length + 1); 181 quill.setSelection(length + 1);
182 } else { 182 } else {
yudao-ui-admin/src/components/FileUpload/index.vue
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 <!-- 文件列表 --> 28 <!-- 文件列表 -->
29 <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul"> 29 <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
30 <li :key="file.url" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList"> 30 <li :key="file.url" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
31 - <el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank"> 31 + <el-link :href="`${file.url}`" :underline="false" target="_blank">
32 <span class="el-icon-document"> {{ getFileName(file.name) }} </span> 32 <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
33 </el-link> 33 </el-link>
34 <div class="ele-upload-list__item-content-action"> 34 <div class="ele-upload-list__item-content-action">
@@ -40,7 +40,7 @@ @@ -40,7 +40,7 @@
40 </template> 40 </template>
41 41
42 <script> 42 <script>
43 -import { getToken } from "@/utils/auth"; 43 +import { getAccessToken } from "@/utils/auth";
44 44
45 export default { 45 export default {
46 name: "FileUpload", 46 name: "FileUpload",
@@ -72,11 +72,8 @@ export default { @@ -72,11 +72,8 @@ export default {
72 return { 72 return {
73 number: 0, 73 number: 0,
74 uploadList: [], 74 uploadList: [],
75 - baseUrl: process.env.VUE_APP_BASE_API,  
76 - uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址  
77 - headers: {  
78 - Authorization: "Bearer " + getToken(),  
79 - }, 75 + uploadFileUrl: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址
  76 + headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
80 fileList: [], 77 fileList: [],
81 }; 78 };
82 }, 79 },
@@ -121,8 +118,7 @@ export default { @@ -121,8 +118,7 @@ export default {
121 } 118 }
122 const isTypeOk = this.fileType.some((type) => { 119 const isTypeOk = this.fileType.some((type) => {
123 if (file.type.indexOf(type) > -1) return true; 120 if (file.type.indexOf(type) > -1) return true;
124 - if (fileExtension && fileExtension.indexOf(type) > -1) return true;  
125 - return false; 121 + return !!(fileExtension && fileExtension.indexOf(type) > -1);
126 }); 122 });
127 if (!isTypeOk) { 123 if (!isTypeOk) {
128 this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`); 124 this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`);
@@ -152,7 +148,8 @@ export default { @@ -152,7 +148,8 @@ export default {
152 }, 148 },
153 // 上传成功回调 149 // 上传成功回调
154 handleUploadSuccess(res) { 150 handleUploadSuccess(res) {
155 - this.uploadList.push({ name: res.fileName, url: res.fileName }); 151 + // edit by 芋道源码
  152 + this.uploadList.push({ name: res.data, url: res.data });
156 if (this.uploadList.length === this.number) { 153 if (this.uploadList.length === this.number) {
157 this.fileList = this.fileList.concat(this.uploadList); 154 this.fileList = this.fileList.concat(this.uploadList);
158 this.uploadList = []; 155 this.uploadList = [];
@@ -181,7 +178,7 @@ export default { @@ -181,7 +178,7 @@ export default {
181 for (let i in list) { 178 for (let i in list) {
182 strs += list[i].url + separator; 179 strs += list[i].url + separator;
183 } 180 }
184 - return strs != '' ? strs.substr(0, strs.length - 1) : ''; 181 + return strs !== '' ? strs.substr(0, strs.length - 1) : '';
185 } 182 }
186 } 183 }
187 }; 184 };
yudao-ui-admin/src/components/ImageUpload/index.vue
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 <div class="component-upload-image"> 2 <div class="component-upload-image">
3 <el-upload 3 <el-upload
4 multiple 4 multiple
5 - :action="uploadImgUrl" 5 + :action="uploadFileUrl"
6 list-type="picture-card" 6 list-type="picture-card"
7 :on-success="handleUploadSuccess" 7 :on-success="handleUploadSuccess"
8 :before-upload="handleBeforeUpload" 8 :before-upload="handleBeforeUpload"
@@ -43,7 +43,7 @@ @@ -43,7 +43,7 @@
43 </template> 43 </template>
44 44
45 <script> 45 <script>
46 -import { getToken } from "@/utils/auth"; 46 +import { getAccessToken } from "@/utils/auth";
47 47
48 export default { 48 export default {
49 props: { 49 props: {
@@ -76,11 +76,8 @@ export default { @@ -76,11 +76,8 @@ export default {
76 dialogImageUrl: "", 76 dialogImageUrl: "",
77 dialogVisible: false, 77 dialogVisible: false,
78 hideUpload: false, 78 hideUpload: false,
79 - baseUrl: process.env.VUE_APP_BASE_API,  
80 - uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址  
81 - headers: {  
82 - Authorization: "Bearer " + getToken(),  
83 - }, 79 + uploadFileUrl: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址
  80 + headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
84 fileList: [] 81 fileList: []
85 }; 82 };
86 }, 83 },
@@ -93,11 +90,8 @@ export default { @@ -93,11 +90,8 @@ export default {
93 // 然后将数组转为对象数组 90 // 然后将数组转为对象数组
94 this.fileList = list.map(item => { 91 this.fileList = list.map(item => {
95 if (typeof item === "string") { 92 if (typeof item === "string") {
96 - if (item.indexOf(this.baseUrl) === -1) {  
97 - item = { name: this.baseUrl + item, url: this.baseUrl + item };  
98 - } else {  
99 - item = { name: item, url: item };  
100 - } 93 + // edit by 芋道源码
  94 + item = { name: item, url: item };
101 } 95 }
102 return item; 96 return item;
103 }); 97 });
@@ -127,7 +121,8 @@ export default { @@ -127,7 +121,8 @@ export default {
127 }, 121 },
128 // 上传成功回调 122 // 上传成功回调
129 handleUploadSuccess(res) { 123 handleUploadSuccess(res) {
130 - this.uploadList.push({ name: res.fileName, url: res.fileName }); 124 + // edit by 芋道源码
  125 + this.uploadList.push({ name: res.data, url: res.data });
131 if (this.uploadList.length === this.number) { 126 if (this.uploadList.length === this.number) {
132 this.fileList = this.fileList.concat(this.uploadList); 127 this.fileList = this.fileList.concat(this.uploadList);
133 this.uploadList = []; 128 this.uploadList = [];
@@ -188,7 +183,7 @@ export default { @@ -188,7 +183,7 @@ export default {
188 for (let i in list) { 183 for (let i in list) {
189 strs += list[i].url.replace(this.baseUrl, "") + separator; 184 strs += list[i].url.replace(this.baseUrl, "") + separator;
190 } 185 }
191 - return strs != '' ? strs.substr(0, strs.length - 1) : ''; 186 + return strs !== '' ? strs.substr(0, strs.length - 1) : '';
192 } 187 }
193 } 188 }
194 }; 189 };
yudao-ui-admin/src/components/UploadImage/index.vue deleted
1 -<template>  
2 - <div class="component-upload-image">  
3 - <el-upload  
4 - :action="uploadImgUrl"  
5 - list-type="picture-card"  
6 - :on-success="handleUploadSuccess"  
7 - :before-upload="handleBeforeUpload"  
8 - :on-error="handleUploadError"  
9 - name="file"  
10 - :show-file-list="false"  
11 - :headers="headers"  
12 - style="display: inline-block; vertical-align: top"  
13 - >  
14 - <img v-if="value" :src="value" class="avatar" />  
15 - <i v-else class="el-icon-plus avatar-uploader-icon"></i>  
16 - </el-upload>  
17 - </div>  
18 -</template>  
19 -  
20 -<script>  
21 -import { getToken } from "@/utils/auth";  
22 -  
23 -export default {  
24 - components: {},  
25 - data() {  
26 - return {  
27 - uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址  
28 - headers: {  
29 - Authorization: "Bearer " + getToken(),  
30 - },  
31 - };  
32 - },  
33 - props: {  
34 - value: {  
35 - type: String,  
36 - default: "",  
37 - },  
38 - },  
39 - methods: {  
40 - handleUploadSuccess(res) {  
41 - this.$emit("input", res.url);  
42 - this.loading.close();  
43 - },  
44 - handleBeforeUpload() {  
45 - this.loading = this.$loading({  
46 - lock: true,  
47 - text: "上传中",  
48 - background: "rgba(0, 0, 0, 0.7)",  
49 - });  
50 - },  
51 - handleUploadError() {  
52 - this.$message({  
53 - type: "error",  
54 - message: "上传失败",  
55 - });  
56 - this.loading.close();  
57 - },  
58 - },  
59 - watch: {},  
60 -};  
61 -</script>  
62 -  
63 -<style scoped lang="scss">  
64 -.avatar {  
65 - width: 100%;  
66 - height: 100%;  
67 -}  
68 -</style>  
69 \ No newline at end of file 0 \ No newline at end of file
yudao-ui-admin/src/permission.js
@@ -3,7 +3,7 @@ import store from &#39;./store&#39; @@ -3,7 +3,7 @@ import store from &#39;./store&#39;
3 import { Message } from 'element-ui' 3 import { Message } from 'element-ui'
4 import NProgress from 'nprogress' 4 import NProgress from 'nprogress'
5 import 'nprogress/nprogress.css' 5 import 'nprogress/nprogress.css'
6 -import { getToken } from '@/utils/auth' 6 +import { getAccessToken } from '@/utils/auth'
7 import { isRelogin } from '@/utils/request' 7 import { isRelogin } from '@/utils/request'
8 8
9 NProgress.configure({ showSpinner: false }) 9 NProgress.configure({ showSpinner: false })
@@ -13,7 +13,7 @@ const whiteList = [&#39;/login&#39;, &#39;/social-login&#39;, &#39;/auth-redirect&#39;, &#39;/bind&#39;, &#39;/regi @@ -13,7 +13,7 @@ const whiteList = [&#39;/login&#39;, &#39;/social-login&#39;, &#39;/auth-redirect&#39;, &#39;/bind&#39;, &#39;/regi
13 13
14 router.beforeEach((to, from, next) => { 14 router.beforeEach((to, from, next) => {
15 NProgress.start() 15 NProgress.start()
16 - if (getToken()) { 16 + if (getAccessToken()) {
17 to.meta.title && store.dispatch('settings/setTitle', to.meta.title) 17 to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
18 /* has token*/ 18 /* has token*/
19 if (to.path === '/login') { 19 if (to.path === '/login') {
yudao-ui-admin/src/store/modules/user.js
1 import {login, logout, getInfo, socialQuickLogin, socialBindLogin, smsLogin} from '@/api/login' 1 import {login, logout, getInfo, socialQuickLogin, socialBindLogin, smsLogin} from '@/api/login'
2 -import { getToken, setToken, removeToken } from '@/utils/auth' 2 +import {getAccessToken, setToken, removeToken, getRefreshToken} from '@/utils/auth'
3 3
4 const user = { 4 const user = {
5 state: { 5 state: {
6 - token: getToken(),  
7 id: 0, // 用户编号 6 id: 0, // 用户编号
8 name: '', 7 name: '',
9 avatar: '', 8 avatar: '',
@@ -15,9 +14,6 @@ const user = { @@ -15,9 +14,6 @@ const user = {
15 SET_ID: (state, id) => { 14 SET_ID: (state, id) => {
16 state.id = id 15 state.id = id
17 }, 16 },
18 - SET_TOKEN: (state, token) => {  
19 - state.token = token  
20 - },  
21 SET_NAME: (state, name) => { 17 SET_NAME: (state, name) => {
22 state.name = name 18 state.name = name
23 }, 19 },
@@ -42,8 +38,8 @@ const user = { @@ -42,8 +38,8 @@ const user = {
42 return new Promise((resolve, reject) => { 38 return new Promise((resolve, reject) => {
43 login(username, password, code, uuid).then(res => { 39 login(username, password, code, uuid).then(res => {
44 res = res.data; 40 res = res.data;
45 - setToken(res.token)  
46 - commit('SET_TOKEN', res.token) 41 + // 设置 token
  42 + setToken(res)
47 resolve() 43 resolve()
48 }).catch(error => { 44 }).catch(error => {
49 reject(error) 45 reject(error)
@@ -59,8 +55,8 @@ const user = { @@ -59,8 +55,8 @@ const user = {
59 return new Promise((resolve, reject) => { 55 return new Promise((resolve, reject) => {
60 socialQuickLogin(type, code, state).then(res => { 56 socialQuickLogin(type, code, state).then(res => {
61 res = res.data; 57 res = res.data;
62 - setToken(res.token)  
63 - commit('SET_TOKEN', res.token) 58 + // 设置 token
  59 + setToken(res)
64 resolve() 60 resolve()
65 }).catch(error => { 61 }).catch(error => {
66 reject(error) 62 reject(error)
@@ -78,8 +74,8 @@ const user = { @@ -78,8 +74,8 @@ const user = {
78 return new Promise((resolve, reject) => { 74 return new Promise((resolve, reject) => {
79 socialBindLogin(type, code, state, username, password).then(res => { 75 socialBindLogin(type, code, state, username, password).then(res => {
80 res = res.data; 76 res = res.data;
81 - setToken(res.token)  
82 - commit('SET_TOKEN', res.token) 77 + // 设置 token
  78 + setToken(res)
83 resolve() 79 resolve()
84 }).catch(error => { 80 }).catch(error => {
85 reject(error) 81 reject(error)
@@ -93,8 +89,8 @@ const user = { @@ -93,8 +89,8 @@ const user = {
93 return new Promise((resolve, reject) => { 89 return new Promise((resolve, reject) => {
94 smsLogin(mobile,mobileCode).then(res => { 90 smsLogin(mobile,mobileCode).then(res => {
95 res = res.data; 91 res = res.data;
96 - setToken(res.token)  
97 - commit('SET_TOKEN', res.token) 92 + // 设置 token
  93 + setToken(res)
98 resolve() 94 resolve()
99 }).catch(error => { 95 }).catch(error => {
100 reject(error) 96 reject(error)
@@ -142,7 +138,6 @@ const user = { @@ -142,7 +138,6 @@ const user = {
142 LogOut({ commit, state }) { 138 LogOut({ commit, state }) {
143 return new Promise((resolve, reject) => { 139 return new Promise((resolve, reject) => {
144 logout(state.token).then(() => { 140 logout(state.token).then(() => {
145 - commit('SET_TOKEN', '')  
146 commit('SET_ROLES', []) 141 commit('SET_ROLES', [])
147 commit('SET_PERMISSIONS', []) 142 commit('SET_PERMISSIONS', [])
148 removeToken() 143 removeToken()
@@ -151,15 +146,6 @@ const user = { @@ -151,15 +146,6 @@ const user = {
151 reject(error) 146 reject(error)
152 }) 147 })
153 }) 148 })
154 - },  
155 -  
156 - // 前端 登出  
157 - FedLogOut({ commit }) {  
158 - return new Promise(resolve => {  
159 - commit('SET_TOKEN', '')  
160 - removeToken()  
161 - resolve()  
162 - })  
163 } 149 }
164 } 150 }
165 } 151 }
yudao-ui-admin/src/utils/auth.js
1 import Cookies from 'js-cookie' 1 import Cookies from 'js-cookie'
2 2
3 -const TokenKey = 'Admin-Token' 3 +const AccessTokenKey = 'ACCESS_TOKEN'
  4 +const RefreshTokenKey = 'REFRESH_TOKEN'
4 5
5 -export function getToken() {  
6 - return Cookies.get(TokenKey) 6 +export function getAccessToken() {
  7 + return Cookies.get(AccessTokenKey)
  8 +}
  9 +
  10 +export function getRefreshToken() {
  11 + return Cookies.get(RefreshTokenKey)
7 } 12 }
8 13
9 export function setToken(token) { 14 export function setToken(token) {
10 - return Cookies.set(TokenKey, token) 15 + Cookies.set(AccessTokenKey, token.accessToken)
  16 + Cookies.set(RefreshTokenKey, token.refreshToken)
11 } 17 }
12 18
13 export function removeToken() { 19 export function removeToken() {
14 - return Cookies.remove(TokenKey) 20 + Cookies.remove(AccessTokenKey)
  21 + Cookies.remove(RefreshTokenKey)
15 } 22 }
yudao-ui-admin/src/utils/request.js
1 import axios from 'axios' 1 import axios from 'axios'
2 import { Notification, MessageBox, Message } from 'element-ui' 2 import { Notification, MessageBox, Message } from 'element-ui'
3 import store from '@/store' 3 import store from '@/store'
4 -import { getToken } from '@/utils/auth' 4 +import {getAccessToken, getRefreshToken, setToken} from '@/utils/auth'
5 import errorCode from '@/utils/errorCode' 5 import errorCode from '@/utils/errorCode'
6 import Cookies from "js-cookie"; 6 import Cookies from "js-cookie";
7 import {getPath, getTenantEnable} from "@/utils/ruoyi"; 7 import {getPath, getTenantEnable} from "@/utils/ruoyi";
  8 +import {refreshToken} from "@/api/login";
8 9
9 // 是否显示重新登录 10 // 是否显示重新登录
10 export let isRelogin = { show: false }; 11 export let isRelogin = { show: false };
  12 +// Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现
  13 +// 请求队列
  14 +let requestList = []
  15 +// 是否正在刷新中
  16 +let isRefreshToken = false
11 17
12 axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' 18 axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
13 // 创建axios实例 19 // 创建axios实例
@@ -21,8 +27,8 @@ const service = axios.create({ @@ -21,8 +27,8 @@ const service = axios.create({
21 service.interceptors.request.use(config => { 27 service.interceptors.request.use(config => {
22 // 是否需要设置 token 28 // 是否需要设置 token
23 const isToken = (config.headers || {}).isToken === false 29 const isToken = (config.headers || {}).isToken === false
24 - if (getToken() && !isToken) {  
25 - config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 30 + if (getAccessToken() && !isToken) {
  31 + config.headers['Authorization'] = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
26 } 32 }
27 // 设置租户 33 // 设置租户
28 if (getTenantEnable()) { 34 if (getTenantEnable()) {
@@ -60,29 +66,44 @@ service.interceptors.request.use(config =&gt; { @@ -60,29 +66,44 @@ service.interceptors.request.use(config =&gt; {
60 }) 66 })
61 67
62 // 响应拦截器 68 // 响应拦截器
63 -service.interceptors.response.use(res => { 69 +service.interceptors.response.use( async res => {
64 // 未设置状态码则默认成功状态 70 // 未设置状态码则默认成功状态
65 const code = res.data.code || 200; 71 const code = res.data.code || 200;
66 // 获取错误信息 72 // 获取错误信息
67 const msg = errorCode[code] || res.data.msg || errorCode['default'] 73 const msg = errorCode[code] || res.data.msg || errorCode['default']
68 if (code === 401) { 74 if (code === 401) {
69 - if (!isRelogin.show) {  
70 - isRelogin.show = true;  
71 - MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {  
72 - confirmButtonText: '重新登录',  
73 - cancelButtonText: '取消',  
74 - type: 'warning'  
75 - }  
76 - ).then(() => {  
77 - isRelogin.show = false;  
78 - store.dispatch('LogOut').then(() => {  
79 - location.href = getPath('/index'); 75 + // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
  76 + if (!isRefreshToken) {
  77 + isRefreshToken = true;
  78 + // 1. 如果获取不到刷新令牌,则只能执行登出操作
  79 + if (!getRefreshToken()) {
  80 + return handleAuthorized();
  81 + }
  82 + // 2. 进行刷新访问令牌
  83 + try {
  84 + const refreshTokenRes = await refreshToken()
  85 + // 2.1 刷新成功,则回放队列的请求 + 当前请求
  86 + setToken(refreshTokenRes.data)
  87 + requestList.forEach(cb => cb())
  88 + return service(res.config)
  89 + } catch (e) {
  90 + // 2.2 刷新失败,则只能执行登出操作
  91 + // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
  92 + return handleAuthorized();
  93 + } finally {
  94 + requestList = []
  95 + isRefreshToken = false
  96 + }
  97 + } else {
  98 + // 添加到队列,等待刷新获取到新的令牌
  99 + return new Promise(resolve => {
  100 + requestList.push(() => {
  101 + config.headers['Authorization'] = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  102 + resolve(service(config))
80 }) 103 })
81 - }).catch(() => {  
82 - isRelogin.show = false;  
83 - }); 104 + })
84 } 105 }
85 - return Promise.reject('无效的会话,或者会话已过期,请重新登录。') 106 + return handleAuthorized();
86 } else if (code === 500) { 107 } else if (code === 500) {
87 Message({ 108 Message({
88 message: msg, 109 message: msg,
@@ -133,9 +154,29 @@ service.interceptors.response.use(res =&gt; { @@ -133,9 +154,29 @@ service.interceptors.response.use(res =&gt; {
133 154
134 export function getBaseHeader() { 155 export function getBaseHeader() {
135 return { 156 return {
136 - 'Authorization': "Bearer " + getToken(), 157 + 'Authorization': "Bearer " + getAccessToken(),
137 'tenant-id': Cookies.get('tenantId'), 158 'tenant-id': Cookies.get('tenantId'),
138 } 159 }
139 } 160 }
140 161
  162 +function handleAuthorized() {
  163 + if (!isRelogin.show) {
  164 + isRelogin.show = true;
  165 + MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
  166 + confirmButtonText: '重新登录',
  167 + cancelButtonText: '取消',
  168 + type: 'warning'
  169 + }
  170 + ).then(() => {
  171 + isRelogin.show = false;
  172 + store.dispatch('LogOut').then(() => {
  173 + location.href = getPath('/index');
  174 + })
  175 + }).catch(() => {
  176 + isRelogin.show = false;
  177 + });
  178 + }
  179 + return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
  180 +}
  181 +
141 export default service 182 export default service
yudao-ui-admin/src/views/infra/file/index.vue
@@ -77,7 +77,7 @@ @@ -77,7 +77,7 @@
77 77
78 <script> 78 <script>
79 import { deleteFile, getFilePage } from "@/api/infra/file"; 79 import { deleteFile, getFilePage } from "@/api/infra/file";
80 -import {getToken} from "@/utils/auth"; 80 +import {getAccessToken} from "@/utils/auth";
81 81
82 export default { 82 export default {
83 name: "File", 83 name: "File",
@@ -108,7 +108,7 @@ export default { @@ -108,7 +108,7 @@ export default {
108 title: "", // 弹出层标题 108 title: "", // 弹出层标题
109 isUploading: false, // 是否禁用上传 109 isUploading: false, // 是否禁用上传
110 url: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址 110 url: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址
111 - headers: { Authorization: "Bearer " + getToken() }, // 设置上传的请求头部 111 + headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
112 data: {} // 上传的额外数据,用于文件名 112 data: {} // 上传的额外数据,用于文件名
113 }, 113 },
114 }; 114 };
yudao-ui-admin/src/views/system/oauth2/client/index.vue 0 → 100755
  1 +<template>
  2 + <div class="app-container">
  3 +
  4 + <!-- 搜索工作栏 -->
  5 + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
  6 + <el-form-item label="应用名" prop="name">
  7 + <el-input v-model="queryParams.name" placeholder="请输入应用名" clearable @keyup.enter.native="handleQuery"/>
  8 + </el-form-item>
  9 + <el-form-item label="状态" prop="status">
  10 + <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
  11 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
  12 + :key="dict.value" :label="dict.label" :value="dict.value"/>
  13 + </el-select>
  14 + </el-form-item>
  15 + <el-form-item>
  16 + <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
  17 + <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
  18 + </el-form-item>
  19 + </el-form>
  20 +
  21 + <!-- 操作工具栏 -->
  22 + <el-row :gutter="10" class="mb8">
  23 + <el-col :span="1.5">
  24 + <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
  25 + v-hasPermi="['system:oauth2-client:create']">新增</el-button>
  26 + </el-col>
  27 + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  28 + </el-row>
  29 +
  30 + <!-- 列表 -->
  31 + <el-table v-loading="loading" :data="list">
  32 + <el-table-column label="客户端编号" align="center" prop="id" />
  33 + <el-table-column label="客户端密钥" align="center" prop="secret" />
  34 + <el-table-column label="应用名" align="center" prop="name" />
  35 + <el-table-column label="应用图标" align="center" prop="logo" />
  36 + <el-table-column label="应用描述" align="center" prop="description" />
  37 + <el-table-column label="状态" align="center" prop="status">
  38 + <template slot-scope="scope">
  39 + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
  40 + </template>
  41 + </el-table-column>
  42 + <el-table-column label="访问令牌的有效期" align="center" prop="accessTokenValiditySeconds" />
  43 + <el-table-column label="刷新令牌的有效期" align="center" prop="refreshTokenValiditySeconds" />
  44 + <el-table-column label="可重定向的 URI 地址" align="center" prop="redirectUris" />
  45 + <el-table-column label="创建时间" align="center" prop="createTime" width="180">
  46 + <template slot-scope="scope">
  47 + <span>{{ parseTime(scope.row.createTime) }}</span>
  48 + </template>
  49 + </el-table-column>
  50 + <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  51 + <template slot-scope="scope">
  52 + <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
  53 + v-hasPermi="['system:oauth2-client:update']">修改</el-button>
  54 + <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
  55 + v-hasPermi="['system:oauth2-client:delete']">删除</el-button>
  56 + </template>
  57 + </el-table-column>
  58 + </el-table>
  59 + <!-- 分页组件 -->
  60 + <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
  61 + @pagination="getList"/>
  62 +
  63 + <!-- 对话框(添加 / 修改) -->
  64 + <el-dialog :title="title" :visible.sync="open" width="700px" append-to-body>
  65 + <el-form ref="form" :model="form" :rules="rules" label-width="160px">
  66 + <el-form-item label="客户端密钥" prop="secret">
  67 + <el-input v-model="form.secret" placeholder="请输入客户端密钥" />
  68 + </el-form-item>
  69 + <el-form-item label="应用名" prop="name">
  70 + <el-input v-model="form.name" placeholder="请输入应用名" />
  71 + </el-form-item>
  72 + <el-form-item label="应用图标">
  73 +<!-- <imageUpload v-model="form.logo" :limit="1"/>-->
  74 + <file-upload v-model="form.logo" :limit="1"/>
  75 + </el-form-item>
  76 + <el-form-item label="应用描述">
  77 + <el-input type="textarea" v-model="form.description" placeholder="请输入应用名" />
  78 + </el-form-item>
  79 + <el-form-item label="状态" prop="status">
  80 + <el-radio-group v-model="form.status">
  81 + <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
  82 + :key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
  83 + </el-radio-group>
  84 + </el-form-item>
  85 + <el-form-item label="访问令牌的有效期" prop="accessTokenValiditySeconds">
  86 + <el-input-number v-model="form.accessTokenValiditySeconds" placeholder="单位:秒" />
  87 + </el-form-item>
  88 + <el-form-item label="刷新令牌的有效期" prop="refreshTokenValiditySeconds">
  89 + <el-input-number v-model="form.refreshTokenValiditySeconds" placeholder="单位:秒" />
  90 + </el-form-item>
  91 + <el-form-item label="可重定向的 URI 地址" prop="redirectUris">
  92 + <el-input v-model="form.redirectUris" placeholder="请输入可重定向的 URI 地址" />
  93 + </el-form-item>
  94 + </el-form>
  95 + <div slot="footer" class="dialog-footer">
  96 + <el-button type="primary" @click="submitForm">确 定</el-button>
  97 + <el-button @click="cancel">取 消</el-button>
  98 + </div>
  99 + </el-dialog>
  100 + </div>
  101 +</template>
  102 +
  103 +<script>
  104 +import { createOAuth2Client, updateOAuth2Client, deleteOAuth2Client, getOAuth2Client, getOAuth2ClientPage } from "@/api/system/oauth2/oauth2Client";
  105 +import ImageUpload from '@/components/ImageUpload';
  106 +import Editor from '@/components/Editor';
  107 +import {CommonStatusEnum} from "@/utils/constants";
  108 +import FileUpload from "@/components/FileUpload";
  109 +
  110 +export default {
  111 + name: "OAuth2Client",
  112 + components: {
  113 + FileUpload,
  114 + ImageUpload,
  115 + Editor,
  116 + },
  117 + data() {
  118 + return {
  119 + // 遮罩层
  120 + loading: true,
  121 + // 导出遮罩层
  122 + exportLoading: false,
  123 + // 显示搜索条件
  124 + showSearch: true,
  125 + // 总条数
  126 + total: 0,
  127 + // OAuth2 客户端列表
  128 + list: [],
  129 + // 弹出层标题
  130 + title: "",
  131 + // 是否显示弹出层
  132 + open: false,
  133 + // 查询参数
  134 + queryParams: {
  135 + pageNo: 1,
  136 + pageSize: 10,
  137 + name: null,
  138 + status: null,
  139 + },
  140 + // 表单参数
  141 + form: {},
  142 + // 表单校验
  143 + rules: {
  144 + secret: [{ required: true, message: "客户端密钥不能为空", trigger: "blur" }],
  145 + name: [{ required: true, message: "应用名不能为空", trigger: "blur" }],
  146 + logo: [{ required: true, message: "应用图标不能为空", trigger: "blur" }],
  147 + status: [{ required: true, message: "状态不能为空", trigger: "blur" }],
  148 + accessTokenValiditySeconds: [{ required: true, message: "访问令牌的有效期不能为空", trigger: "blur" }],
  149 + refreshTokenValiditySeconds: [{ required: true, message: "刷新令牌的有效期不能为空", trigger: "blur" }],
  150 + redirectUris: [{ required: true, message: "可重定向的 URI 地址不能为空", trigger: "blur" }],
  151 + }
  152 + };
  153 + },
  154 + created() {
  155 + this.getList();
  156 + },
  157 + methods: {
  158 + /** 查询列表 */
  159 + getList() {
  160 + this.loading = true;
  161 + // 处理查询参数
  162 + let params = {...this.queryParams};
  163 + // 执行查询
  164 + getOAuth2ClientPage(params).then(response => {
  165 + this.list = response.data.list;
  166 + this.total = response.data.total;
  167 + this.loading = false;
  168 + });
  169 + },
  170 + /** 取消按钮 */
  171 + cancel() {
  172 + this.open = false;
  173 + this.reset();
  174 + },
  175 + /** 表单重置 */
  176 + reset() {
  177 + this.form = {
  178 + id: undefined,
  179 + secret: undefined,
  180 + name: undefined,
  181 + logo: undefined,
  182 + description: undefined,
  183 + status: CommonStatusEnum.ENABLE,
  184 + accessTokenValiditySeconds: undefined,
  185 + refreshTokenValiditySeconds: undefined,
  186 + redirectUris: undefined,
  187 + };
  188 + this.resetForm("form");
  189 + },
  190 + /** 搜索按钮操作 */
  191 + handleQuery() {
  192 + this.queryParams.pageNo = 1;
  193 + this.getList();
  194 + },
  195 + /** 重置按钮操作 */
  196 + resetQuery() {
  197 + this.resetForm("queryForm");
  198 + this.handleQuery();
  199 + },
  200 + /** 新增按钮操作 */
  201 + handleAdd() {
  202 + this.reset();
  203 + this.open = true;
  204 + this.title = "添加 OAuth2 客户端";
  205 + },
  206 + /** 修改按钮操作 */
  207 + handleUpdate(row) {
  208 + this.reset();
  209 + const id = row.id;
  210 + getOAuth2Client(id).then(response => {
  211 + this.form = response.data;
  212 + this.open = true;
  213 + this.title = "修改 OAuth2 客户端";
  214 + });
  215 + },
  216 + /** 提交按钮 */
  217 + submitForm() {
  218 + this.$refs["form"].validate(valid => {
  219 + if (!valid) {
  220 + return;
  221 + }
  222 + // 修改的提交
  223 + if (this.form.id != null) {
  224 + updateOAuth2Client(this.form).then(response => {
  225 + this.$modal.msgSuccess("修改成功");
  226 + this.open = false;
  227 + this.getList();
  228 + });
  229 + return;
  230 + }
  231 + // 添加的提交
  232 + createOAuth2Client(this.form).then(response => {
  233 + this.$modal.msgSuccess("新增成功");
  234 + this.open = false;
  235 + this.getList();
  236 + });
  237 + });
  238 + },
  239 + /** 删除按钮操作 */
  240 + handleDelete(row) {
  241 + const id = row.id;
  242 + this.$modal.confirm('是否确认删除 OAuth2 客户端编号为"' + id + '"的数据项?').then(function() {
  243 + return deleteOAuth2Client(id);
  244 + }).then(() => {
  245 + this.getList();
  246 + this.$modal.msgSuccess("删除成功");
  247 + }).catch(() => {});
  248 + }
  249 + }
  250 +};
  251 +</script>
yudao-ui-admin/src/views/system/session/index.vue renamed to yudao-ui-admin/src/views/system/oauth2/token/index.vue
@@ -3,11 +3,14 @@ @@ -3,11 +3,14 @@
3 <doc-alert title="用户体系" url="https://doc.iocoder.cn/user-center/" /> 3 <doc-alert title="用户体系" url="https://doc.iocoder.cn/user-center/" />
4 <!-- 搜索工作栏 --> 4 <!-- 搜索工作栏 -->
5 <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px"> 5 <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
6 - <el-form-item label="登录地址" prop="userIp">  
7 - <el-input v-model="queryParams.userIp" placeholder="请输入登录地址" clearable @keyup.enter.native="handleQuery"/> 6 + <el-form-item label="用户编号" prop="userId">
  7 + <el-input v-model="queryParams.userId" placeholder="请输入用户编号" clearable @keyup.enter.native="handleQuery"/>
8 </el-form-item> 8 </el-form-item>
9 - <el-form-item label="用户名称" prop="username">  
10 - <el-input v-model="queryParams.username" placeholder="请输入用户名称" clearable @keyup.enter.native="handleQuery"/> 9 + <el-form-item label="用户类型" prop="userType">
  10 + <el-select v-model="queryParams.userType" placeholder="请选择用户类型" clearable>
  11 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.USER_TYPE)"
  12 + :key="dict.value" :label="dict.label" :value="dict.value"/>
  13 + </el-select>
11 </el-form-item> 14 </el-form-item>
12 <el-form-item> 15 <el-form-item>
13 <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> 16 <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
@@ -16,20 +19,28 @@ @@ -16,20 +19,28 @@
16 19
17 </el-form> 20 </el-form>
18 <el-table v-loading="loading" :data="list" style="width: 100%;"> 21 <el-table v-loading="loading" :data="list" style="width: 100%;">
19 - <el-table-column label="会话编号" align="center" prop="id" width="300" />  
20 - <el-table-column label="登录名称" align="center" prop="username" width="100" />  
21 - <el-table-column label="部门名称" align="center" prop="deptName" width="100" />  
22 - <el-table-column label="登录地址" align="center" prop="userIp" width="100" />  
23 - <el-table-column label="userAgent" align="center" prop="userAgent" :show-overflow-tooltip="true" />  
24 - <el-table-column label="登录时间" align="center" prop="createTime" width="180"> 22 + <el-table-column label="访问令牌" align="center" prop="accessToken" width="300" />
  23 + <el-table-column label="刷新令牌" align="center" prop="refreshToken" width="300" />
  24 + <el-table-column label="用户编号" align="center" prop="userId" />
  25 + <el-table-column label="用户类型" align="center" prop="userType" width="100">
  26 + <template slot-scope="scope">
  27 + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType"/>
  28 + </template>
  29 + </el-table-column>
  30 + <el-table-column label="创建时间" align="center" prop="createTime" width="180">
25 <template slot-scope="scope"> 31 <template slot-scope="scope">
26 <span>{{ parseTime(scope.row.createTime) }}</span> 32 <span>{{ parseTime(scope.row.createTime) }}</span>
27 </template> 33 </template>
28 </el-table-column> 34 </el-table-column>
  35 + <el-table-column label="过期时间" align="center" prop="expiresTime" width="180">
  36 + <template slot-scope="scope">
  37 + <span>{{ parseTime(scope.row.expiresTime) }}</span>
  38 + </template>
  39 + </el-table-column>
29 <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> 40 <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
30 <template slot-scope="scope"> 41 <template slot-scope="scope">
31 <el-button size="mini" type="text" icon="el-icon-delete" @click="handleForceLogout(scope.row)" 42 <el-button size="mini" type="text" icon="el-icon-delete" @click="handleForceLogout(scope.row)"
32 - v-hasPermi="['system:user-session:delete']">强退</el-button> 43 + v-hasPermi="['system:oauth2-token:delete']">强退</el-button>
33 </template> 44 </template>
34 </el-table-column> 45 </el-table-column>
35 </el-table> 46 </el-table>
@@ -40,10 +51,10 @@ @@ -40,10 +51,10 @@
40 </template> 51 </template>
41 52
42 <script> 53 <script>
43 -import { list, forceLogout } from "@/api/system/session"; 54 +import { getAccessTokenPage, deleteAccessToken } from "@/api/system/oauth2/oauth2Token";
44 55
45 export default { 56 export default {
46 - name: "Online", 57 + name: "OAuth2Token",
47 data() { 58 data() {
48 return { 59 return {
49 // 遮罩层 60 // 遮罩层
@@ -56,8 +67,8 @@ export default { @@ -56,8 +67,8 @@ export default {
56 queryParams: { 67 queryParams: {
57 pageNo: 1, 68 pageNo: 1,
58 pageSize: 10, 69 pageSize: 10,
59 - userIp: undefined,  
60 - username: undefined 70 + userId: undefined,
  71 + userType: undefined
61 } 72 }
62 }; 73 };
63 }, 74 },
@@ -68,7 +79,7 @@ export default { @@ -68,7 +79,7 @@ export default {
68 /** 查询登录日志列表 */ 79 /** 查询登录日志列表 */
69 getList() { 80 getList() {
70 this.loading = true; 81 this.loading = true;
71 - list(this.queryParams).then(response => { 82 + getAccessTokenPage(this.queryParams).then(response => {
72 this.list = response.data.list; 83 this.list = response.data.list;
73 this.total = response.data.total; 84 this.total = response.data.total;
74 this.loading = false; 85 this.loading = false;
@@ -86,8 +97,8 @@ export default { @@ -86,8 +97,8 @@ export default {
86 }, 97 },
87 /** 强退按钮操作 */ 98 /** 强退按钮操作 */
88 handleForceLogout(row) { 99 handleForceLogout(row) {
89 - this.$modal.confirm('是否确认强退名称为"' + row.username + '"的数据项?').then(function() {  
90 - return forceLogout(row.id); 100 + this.$modal.confirm('是否确认强退令牌为"' + row.accessToken + '"的数据项?').then(function() {
  101 + return deleteAccessToken(row.accessToken);
91 }).then(() => { 102 }).then(() => {
92 this.getList(); 103 this.getList();
93 this.$modal.msgSuccess("强退成功"); 104 this.$modal.msgSuccess("强退成功");
yudao-ui-admin/src/views/system/user/index.vue
@@ -237,7 +237,7 @@ import { @@ -237,7 +237,7 @@ import {
237 resetUserPwd, 237 resetUserPwd,
238 updateUser 238 updateUser
239 } from "@/api/system/user"; 239 } from "@/api/system/user";
240 -import {getToken} from "@/utils/auth"; 240 +import {getAccessToken} from "@/utils/auth";
241 import Treeselect from "@riophae/vue-treeselect"; 241 import Treeselect from "@riophae/vue-treeselect";
242 import "@riophae/vue-treeselect/dist/vue-treeselect.css"; 242 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
243 243