From 735228fcb151e78c044f97e1133e287835c3fe29 Mon Sep 17 00:00:00 2001 From: emaisi Date: Wed, 4 May 2022 13:20:34 +0800 Subject: [PATCH 01/21] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dftp=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E5=92=8C=E4=B8=8B=E8=BD=BD=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/core/client/ftp/FtpFileClient.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java index d50bc8501..f877ce649 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java +++ b/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 { @Override protected void doInit() { - // 补全风格。例如说 Linux 是 /,Windows 是 \ - if (!config.getBasePath().endsWith(File.separator)) { - config.setBasePath(config.getBasePath() + File.separator); + //如果路径配置\a\test,替换成/a/test,替换方法已经处理为null情况 + config.setBasePath(StrUtil.replace(config.getBasePath(),StrUtil.BACKSLASH,StrUtil.SLASH)); + // ftp是的路径是"/" 结尾 + if (!config.getBasePath().endsWith(StrUtil.SLASH)) { + config.setBasePath(config.getBasePath() + StrUtil.SLASH); } // 初始化 Ftp 对象 this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(), @@ -60,7 +62,7 @@ public class FtpFileClient extends AbstractFileClient { public byte[] getContent(String path) { String filePath = getFilePath(path); String fileName = FileUtil.getName(filePath); - String dir = StrUtil.removeSuffix(path, fileName); + String dir = StrUtil.removeSuffix(filePath, fileName); ByteArrayOutputStream out = new ByteArrayOutputStream(); ftp.download(dir, fileName, out); return out.toByteArray(); @@ -70,4 +72,4 @@ public class FtpFileClient extends AbstractFileClient { return config.getBasePath() + path; } -} +} \ No newline at end of file From 472da026dee653b5770990b8e3daaca557d3af78 Mon Sep 17 00:00:00 2001 From: emaisi Date: Wed, 4 May 2022 22:46:11 +0800 Subject: [PATCH 02/21] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/framework/file/core/client/ftp/FtpFileClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java index f877ce649..f33718f54 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java @@ -27,9 +27,9 @@ public class FtpFileClient extends AbstractFileClient { @Override protected void doInit() { - //如果路径配置\a\test,替换成/a/test,替换方法已经处理为null情况 + // 把配置的 \ 替换成 /,如果路径配置 \a\test,替换成 /a/test,替换方法已经处理 null 情况 config.setBasePath(StrUtil.replace(config.getBasePath(),StrUtil.BACKSLASH,StrUtil.SLASH)); - // ftp是的路径是"/" 结尾 + // ftp的路径是 / 结尾 if (!config.getBasePath().endsWith(StrUtil.SLASH)) { config.setBasePath(config.getBasePath() + StrUtil.SLASH); } From bddd19318542bc162e4821b3668a740a6806e4a6 Mon Sep 17 00:00:00 2001 From: emaisi Date: Wed, 4 May 2022 22:46:11 +0800 Subject: [PATCH 03/21] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/framework/file/core/client/ftp/FtpFileClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java index f877ce649..6f305e342 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java @@ -27,9 +27,9 @@ public class FtpFileClient extends AbstractFileClient { @Override protected void doInit() { - //如果路径配置\a\test,替换成/a/test,替换方法已经处理为null情况 + // 把配置的 \ 替换成 /, 如果路径配置 \a\test, 替换成 /a/test, 替换方法已经处理 null 情况 config.setBasePath(StrUtil.replace(config.getBasePath(),StrUtil.BACKSLASH,StrUtil.SLASH)); - // ftp是的路径是"/" 结尾 + // ftp的路径是 / 结尾 if (!config.getBasePath().endsWith(StrUtil.SLASH)) { config.setBasePath(config.getBasePath() + StrUtil.SLASH); } From 12ede349faecfd2060b8ddf8b34f5e7bac609255 Mon Sep 17 00:00:00 2001 From: emaisi Date: Thu, 5 May 2022 07:31:56 +0000 Subject: [PATCH 04/21] =?UTF-8?q?ftp=E7=9A=84=E5=9C=A8=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E4=B9=8B=E5=89=8D=E6=B7=BB=E5=8A=A0=E4=B8=80=E4=B8=AA=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E8=B6=85=E6=97=B6=E7=9A=84=E9=87=8D=E8=AF=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/framework/file/core/client/ftp/FtpFileClient.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java index f0f9e130e..4ad345bdc 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClient.java @@ -44,6 +44,7 @@ public class FtpFileClient extends AbstractFileClient { String filePath = getFilePath(path); String fileName = FileUtil.getName(filePath); String dir = StrUtil.removeSuffix(filePath, fileName); + ftp.reconnectIfTimeout(); boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content)); if (!success) { throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath)); @@ -55,6 +56,7 @@ public class FtpFileClient extends AbstractFileClient { @Override public void delete(String path) { String filePath = getFilePath(path); + ftp.reconnectIfTimeout(); ftp.delFile(filePath); } @@ -64,6 +66,7 @@ public class FtpFileClient extends AbstractFileClient { String fileName = FileUtil.getName(filePath); String dir = StrUtil.removeSuffix(filePath, fileName); ByteArrayOutputStream out = new ByteArrayOutputStream(); + ftp.reconnectIfTimeout(); ftp.download(dir, fileName, out); return out.toByteArray(); } From 8737674d743ea2bcfdb0a50a0d7f007c58753884 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 7 May 2022 00:39:39 +0800 Subject: [PATCH 05/21] =?UTF-8?q?=E5=8E=BB=E9=99=A4=20LoginUser=20?= =?UTF-8?q?=E7=9A=84=20roleIds=E3=80=81deptId=20=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=EF=BC=8C=E7=AE=80=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-dependencies/pom.xml | 7 -- .../util/collection/CollectionUtils.java | 7 ++ .../dept/rule/DeptDataPermissionRule.java | 39 ++++-- .../DeptDataPermissionFrameworkService.java | 5 +- .../dept/rule/DeptDataPermissionRuleTest.java | 40 ++++-- .../framework/security/core/LoginUser.java | 11 -- .../core/util/SecurityFrameworkUtils.java | 12 -- .../service/auth/MemberAuthServiceImpl.java | 2 +- .../controller/admin/auth/AuthController.java | 18 +-- .../admin/auth/UserSessionController.java | 12 +- .../service/auth/AdminAuthServiceImpl.java | 23 +--- .../permission/PermissionServiceImpl.java | 54 ++++---- .../service/tenant/TenantServiceImpl.java | 1 + .../service/user/AdminUserServiceImpl.java | 2 + .../service/auth/AuthServiceImplTest.java | 17 +-- .../permission/PermissionServiceTest.java | 118 ++++++++++-------- 16 files changed, 179 insertions(+), 189 deletions(-) diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 5a7e152ae..63913572a 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -22,7 +22,6 @@ 1.5.22 2.5 - 5.1.46 1.2.8 3.4.3.4 3.5.2 @@ -76,12 +75,6 @@ ${spring.boot.version} pom import - - - mysql - mysql-connector-java - - diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java index 1ea74ee29..a6466ab44 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java @@ -54,6 +54,13 @@ public class CollectionUtils { return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList()); } + public static List convertList(Collection from, Function func, Predicate filter) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList()); + } + public static Set convertSet(Collection from, Function func) { if (CollUtil.isEmpty(from)) { return new HashSet<>(); diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRule.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRule.java index ed9168ba0..2a0c9aefa 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRule.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRule.java @@ -1,11 +1,13 @@ package cn.iocoder.yudao.framework.datapermission.core.dept.rule; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService; -import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService; +import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; @@ -13,7 +15,6 @@ import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import lombok.AllArgsConstructor; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Expression; @@ -24,10 +25,7 @@ import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.InExpression; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * 基于部门的 {@link DataPermissionRule} 数据权限规则实现 @@ -50,6 +48,11 @@ import java.util.Set; @Slf4j public class DeptDataPermissionRule implements DataPermissionRule { + /** + * LoginUser 的 Context 缓存 Key + */ + protected static final String CONTEXT_KEY = DeptDataPermissionRule.class.getSimpleName(); + private static final String DEPT_COLUMN_NAME = "dept_id"; private static final String USER_COLUMN_NAME = "user_id"; @@ -90,13 +93,23 @@ public class DeptDataPermissionRule implements DataPermissionRule { if (loginUser == null) { return null; } + // 只有管理员类型的用户,才进行数据权限的处理 + if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) { + return null; + } // 获得数据权限 - DeptDataPermissionRespDTO deptDataPermission = deptDataPermissionService.getDeptDataPermission(loginUser); + DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class); + // 从上下文中拿不到,则调用逻辑进行获取 if (deptDataPermission == null) { - log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser)); - throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限", - loginUser.getId(), tableName, tableAlias.getName())); + deptDataPermission = deptDataPermissionService.getDeptDataPermission(loginUser.getId()); + if (deptDataPermission == null) { + log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser)); + throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限", + loginUser.getId(), tableName, tableAlias.getName())); + } + // 添加到上下文中,避免重复计算 + loginUser.setContext(CONTEXT_KEY, deptDataPermission); } // 情况一,如果是 ALL 可查看全部,则无需拼接条件 @@ -111,8 +124,8 @@ public class DeptDataPermissionRule implements DataPermissionRule { } // 情况三,拼接 Dept 和 User 的条件,最后组合 - Expression deptExpression = this.buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds()); - Expression userExpression = this.buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId()); + Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds()); + Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId()); if (deptExpression == null && userExpression == null) { // TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据 log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]", diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/service/DeptDataPermissionFrameworkService.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/service/DeptDataPermissionFrameworkService.java index 3ee616755..e491aa036 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/service/DeptDataPermissionFrameworkService.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/dept/service/DeptDataPermissionFrameworkService.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.framework.datapermission.core.dept.service; import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; -import cn.iocoder.yudao.framework.security.core.LoginUser; /** * 基于部门的数据权限 Framework Service 接口 @@ -14,9 +13,9 @@ public interface DeptDataPermissionFrameworkService { /** * 获得登陆用户的部门数据权限 * - * @param loginUser 登陆用户 + * @param userId 用户编号 * @return 部门数据权限 */ - DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser); + DeptDataPermissionRespDTO getDeptDataPermission(Long userId); } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRuleTest.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRuleTest.java index 7282c1816..ef5ff16ac 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRuleTest.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/dept/rule/DeptDataPermissionRuleTest.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.datapermission.core.dept.rule; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ReflectUtil; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.collection.SetUtils; import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService; import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; @@ -69,7 +70,8 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { String tableName = "t_user"; Alias tableAlias = new Alias("u"); // mock 方法 - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)); + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); // 调用 @@ -88,16 +90,18 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { String tableName = "t_user"; Alias tableAlias = new Alias("u"); // mock 方法(LoginUser) - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)); + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); // mock 方法(DeptDataPermissionRespDTO) DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO().setAll(true); - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission); + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission); // 调用 Expression expression = rule.getExpression(tableName, tableAlias); // 断言 assertNull(expression); + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class)); } } @@ -109,16 +113,18 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { String tableName = "t_user"; Alias tableAlias = new Alias("u"); // mock 方法(LoginUser) - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)); + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); // mock 方法(DeptDataPermissionRespDTO) DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO(); - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission); + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission); // 调用 Expression expression = rule.getExpression(tableName, tableAlias); // 断言 assertEquals("null = null", expression.toString()); + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class)); } } @@ -130,17 +136,19 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { String tableName = "t_user"; Alias tableAlias = new Alias("u"); // mock 方法(LoginUser) - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)); + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); // mock 方法(DeptDataPermissionRespDTO) DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO() .setDeptIds(SetUtils.asSet(10L, 20L)).setSelf(true); - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission); + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission); // 调用 Expression expression = rule.getExpression(tableName, tableAlias); // 断言 assertSame(EXPRESSION_NULL, expression); + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class)); } } @@ -152,12 +160,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { String tableName = "t_user"; Alias tableAlias = new Alias("u"); // mock 方法(LoginUser) - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)); + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); // mock 方法(DeptDataPermissionRespDTO) DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO() .setSelf(true); - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission); + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission); // 添加 user 字段配置 rule.addUserColumn("t_user", "id"); @@ -165,6 +174,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { Expression expression = rule.getExpression(tableName, tableAlias); // 断言 assertEquals("u.id = 1", expression.toString()); + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class)); } } @@ -176,12 +186,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { String tableName = "t_user"; Alias tableAlias = new Alias("u"); // mock 方法(LoginUser) - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)); + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); // mock 方法(DeptDataPermissionRespDTO) DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO() .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L)); - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission); + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission); // 添加 dept 字段配置 rule.addDeptColumn("t_user", "dept_id"); @@ -189,6 +200,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { Expression expression = rule.getExpression(tableName, tableAlias); // 断言 assertEquals("u.dept_id IN (10, 20)", expression.toString()); + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class)); } } @@ -200,12 +212,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { String tableName = "t_user"; Alias tableAlias = new Alias("u"); // mock 方法(LoginUser) - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)); + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); // mock 方法(DeptDataPermissionRespDTO) DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO() .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L)).setSelf(true); - when(deptDataPermissionFrameworkService.getDeptDataPermission(same(loginUser))).thenReturn(deptDataPermission); + when(deptDataPermissionFrameworkService.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission); // 添加 user 字段配置 rule.addUserColumn("t_user", "id"); // 添加 dept 字段配置 @@ -215,6 +228,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { Expression expression = rule.getExpression(tableName, tableAlias); // 断言 assertEquals("u.dept_id IN (10, 20) OR u.id = 1", expression.toString()); + assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class)); } } diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java index 84c7f94c4..c78148a46 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java @@ -50,17 +50,6 @@ public class LoginUser implements UserDetails { */ private Long tenantId; - // ========== UserTypeEnum.ADMIN 独有字段 ========== - // TODO 芋艿:可以通过定义一个 Map exts 的方式,去除管理员的字段。不过这样会导致系统比较复杂,所以暂时不去掉先; - /** - * 角色编号数组 - */ - private Set roleIds; - /** - * 部门编号 - */ - private Long deptId; - // ========== 上下文 ========== /** * 上下文字段,不进行持久化 diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java index 562c6ed9f..bbc1a9435 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java @@ -11,7 +11,6 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; -import java.util.Set; /** * 安全服务工具类 @@ -79,17 +78,6 @@ public class SecurityFrameworkUtils { return loginUser != null ? loginUser.getId() : null; } - /** - * 获得当前用户的角色编号数组 - * - * @return 角色编号数组 - */ - @Nullable - public static Set getLoginUserRoleIds() { - LoginUser loginUser = getLoginUser(); - return loginUser != null ? loginUser.getRoleIds() : null; - } - /** * 设置当前用户 * diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java index 5c2788df9..f82028f39 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java @@ -84,7 +84,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { @Override public String login(AppAuthLoginReqVO reqVO, String userIp, String userAgent) { // 使用手机 + 密码,进行登录。 - LoginUser loginUser = this.login0(reqVO.getMobile(), reqVO.getPassword()); + LoginUser loginUser = login0(reqVO.getMobile(), reqVO.getPassword()); // 缓存登录用户到 Redis 中,返回 Token 令牌 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 6fbf2a21a..0e26674f8 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -26,12 +26,13 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.Valid; import java.util.List; +import java.util.Set; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserRoleIds; +import static java.util.Collections.singleton; @Api(tags = "管理后台 - 认证") @RestController @@ -69,12 +70,12 @@ public class AuthController { return null; } // 获得角色列表 - List roleList = roleService.getRolesFromCache(getLoginUserRoleIds()); + Set roleIds = permissionService.getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus())); + List roleList = roleService.getRolesFromCache(roleIds); // 获得菜单列表 - List menuList = permissionService.getRoleMenuListFromCache( - getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它 + List menuList = permissionService.getRoleMenuListFromCache(roleIds, SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()), - SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); + singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的 // 拼接结果返回 return success(AuthConvert.INSTANCE.convert(user, roleList, menuList)); } @@ -82,11 +83,12 @@ public class AuthController { @GetMapping("/list-menus") @ApiOperation("获得登录用户的菜单列表") public CommonResult> getMenus() { + // 获得角色列表 + Set roleIds = permissionService.getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus())); // 获得用户拥有的菜单列表 - List menuList = permissionService.getRoleMenuListFromCache( - getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它 + List menuList = permissionService.getRoleMenuListFromCache(roleIds, SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型 - SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的 + singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的 // 转换成 Tree 结构返回 return success(AuthConvert.INSTANCE.buildMenuTree(menuList)); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java index 023532e08..9ce2edca9 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java @@ -1,15 +1,16 @@ package cn.iocoder.yudao.module.system.controller.admin.auth; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageItemRespVO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; import cn.iocoder.yudao.module.system.convert.auth.UserSessionConvert; import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; -import cn.iocoder.yudao.module.system.service.auth.UserSessionService; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.module.system.service.auth.UserSessionService; import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import io.swagger.annotations.Api; @@ -49,7 +50,8 @@ public class UserSessionController { // 获得拼接需要的数据 Map userMap = userService.getUserMap( - convertList(pageResult.getList(), UserSessionDO::getUserId)); + convertList(pageResult.getList(), UserSessionDO::getUserId, + session -> session.getUserType().equals(UserTypeEnum.ADMIN.getValue()))); Map deptMap = deptService.getDeptMap( convertList(userMap.values(), AdminUserDO::getDeptId)); // 拼接结果返回 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index 6476102bf..ebbfb6dfa 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -17,7 +17,6 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; import cn.iocoder.yudao.module.system.service.common.CaptchaService; import cn.iocoder.yudao.module.system.service.logger.LoginLogService; -import cn.iocoder.yudao.module.system.service.permission.PermissionService; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import lombok.extern.slf4j.Slf4j; @@ -36,12 +35,10 @@ import org.springframework.util.Assert; import javax.annotation.Resource; import javax.validation.Validator; import java.util.Objects; -import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; -import static java.util.Collections.singleton; /** * Auth Service 实现类 @@ -60,8 +57,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") // UserService 存在重名 private AdminUserService userService; @Resource - private PermissionService permissionService; - @Resource private CaptchaService captchaService; @Resource private LoginLogService loginLogService; @@ -211,16 +206,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { } } - /** - * 获得 User 拥有的角色编号数组 - * - * @param userId 用户编号 - * @return 角色编号数组 - */ - private Set getUserRoleIds(Long userId) { - return permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus())); - } - @Override public String socialQuickLogin(AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) { // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 @@ -318,17 +303,13 @@ public class AdminAuthServiceImpl implements AdminAuthService { } // 刷新 LoginUser 缓存 - LoginUser newLoginUser= this.buildLoginUser(user); + LoginUser newLoginUser= buildLoginUser(user); userSessionService.refreshUserSession(token, newLoginUser); return newLoginUser; } private LoginUser buildLoginUser(AdminUserDO user) { - LoginUser loginUser = AuthConvert.INSTANCE.convert(user); - // 补全字段 - loginUser.setDeptId(user.getDeptId()); - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); - return loginUser; + return AuthConvert.INSTANCE.convert(user); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java index 08473d8cc..76e2055b3 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java @@ -3,12 +3,12 @@ package cn.iocoder.yudao.module.system.service.permission; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; -import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; @@ -22,6 +22,8 @@ import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper; import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum; import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer; import cn.iocoder.yudao.module.system.service.dept.DeptService; +import cn.iocoder.yudao.module.system.service.user.AdminUserService; +import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; @@ -36,6 +38,10 @@ import org.springframework.transaction.support.TransactionSynchronizationManager import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.util.*; +import java.util.function.Supplier; + +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static java.util.Collections.singleton; /** * 权限 Service 实现类 @@ -46,11 +52,6 @@ import java.util.*; @Slf4j public class PermissionServiceImpl implements PermissionService { - /** - * LoginUser 的 Context 缓存 Key - */ - public static final String CONTEXT_KEY = PermissionServiceImpl.class.getSimpleName(); - /** * 定时执行 {@link #schedulePeriodicRefresh()} 的周期 * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高 @@ -93,6 +94,8 @@ public class PermissionServiceImpl implements PermissionService { private MenuService menuService; @Resource private DeptService deptService; + @Resource + private AdminUserService userService; @Resource private PermissionProducer permissionProducer; @@ -319,7 +322,7 @@ public class PermissionServiceImpl implements PermissionService { } // 获得当前登录的角色。如果为空,说明没有权限 - Set roleIds = SecurityFrameworkUtils.getLoginUserRoleIds(); + Set roleIds = getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus())); if (CollUtil.isEmpty(roleIds)) { return false; } @@ -354,7 +357,7 @@ public class PermissionServiceImpl implements PermissionService { } // 获得当前登录的角色。如果为空,说明没有权限 - Set roleIds = SecurityFrameworkUtils.getLoginUserRoleIds(); + Set roleIds = getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus())); if (CollUtil.isEmpty(roleIds)) { return false; } @@ -368,16 +371,18 @@ public class PermissionServiceImpl implements PermissionService { } @Override - public DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser) { - // 判断是否 context 已经缓存 - DeptDataPermissionRespDTO result = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class); - if (result != null) { + @DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题 + public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) { + DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO(); + // 获得用户的角色 + Set roleIds = getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus())); + if (CollUtil.isEmpty(roleIds)) { return result; } - - // 创建 DeptDataPermissionRespDTO 对象 - result = new DeptDataPermissionRespDTO(); - List roles = roleService.getRolesFromCache(loginUser.getRoleIds()); + List roles = roleService.getRolesFromCache(roleIds); + // 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询 + Supplier userDeptIdCache = Suppliers.memoize(() -> userService.getUser(userId).getDeptId()); + // 遍历每个角色,计算 for (RoleDO role : roles) { // 为空时,跳过 if (role.getDataScope() == null) { @@ -393,20 +398,20 @@ public class PermissionServiceImpl implements PermissionService { CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds()); // 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。 // 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉 - CollUtil.addAll(result.getDeptIds(), loginUser.getDeptId()); + CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get()); continue; } // 情况三,DEPT_ONLY if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) { - CollectionUtils.addIfNotNull(result.getDeptIds(), loginUser.getDeptId()); + CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptIdCache.get()); continue; } // 情况四,DEPT_DEPT_AND_CHILD if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) { - List depts = deptService.getDeptsByParentIdFromCache(loginUser.getDeptId(), true); + List depts = deptService.getDeptsByParentIdFromCache(userDeptIdCache.get(), true); CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, DeptDO::getId)); - //添加本身部门id - CollUtil.addAll(result.getDeptIds(), loginUser.getDeptId()); + // 添加本身部门编号 + CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get()); continue; } // 情况五,SELF @@ -415,11 +420,8 @@ public class PermissionServiceImpl implements PermissionService { continue; } // 未知情况,error log 即可 - log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", loginUser.getId(), JsonUtils.toJsonString(result)); + log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, JsonUtils.toJsonString(result)); } - - // 添加到缓存,并返回 - loginUser.setContext(CONTEXT_KEY, result); return result; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java index e0038ffcd..160ffe824 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java @@ -81,6 +81,7 @@ public class TenantServiceImpl implements TenantService { @Getter private volatile Date maxUpdateTime; + @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") @Autowired(required = false) // 由于 yudao.tenant.enable 配置项,可以关闭多租户的功能,所以这里只能不强制注入 private TenantProperties tenantProperties; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index 44934c6fa..9208b2e69 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -25,6 +25,7 @@ import cn.iocoder.yudao.module.system.service.tenant.TenantService; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -61,6 +62,7 @@ public class AdminUserServiceImpl implements AdminUserService { @Resource private PasswordEncoder passwordEncoder; @Resource + @Lazy // 延迟,避免循环依赖报错 private TenantService tenantService; @Resource diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java index 47b767eb5..0cb376668 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.system.service.auth; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.framework.test.core.util.AssertUtils; @@ -12,7 +11,6 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.service.common.CaptchaService; import cn.iocoder.yudao.module.system.service.dept.PostService; import cn.iocoder.yudao.module.system.service.logger.LoginLogService; -import cn.iocoder.yudao.module.system.service.permission.PermissionService; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import org.junit.jupiter.api.BeforeEach; @@ -34,7 +32,6 @@ import java.util.Set; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; -import static java.util.Collections.singleton; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.eq; @@ -49,8 +46,6 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @MockBean private AdminUserService userService; @MockBean - private PermissionService permissionService; - @MockBean private AuthenticationManager authenticationManager; @MockBean private Authentication authentication; @@ -108,16 +103,11 @@ public class AuthServiceImplTest extends BaseDbUnitTest { // mock 方法 01 AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId)); when(userService.getUser(eq(userId))).thenReturn(user); - // mock 方法 02 - Set roleIds = randomSet(Long.class); - when(permissionService.getUserRoleIds(eq(userId), eq(singleton(CommonStatusEnum.ENABLE.getStatus())))) - .thenReturn(roleIds); // 调用 LoginUser loginUser = authService.mockLogin(userId); // 断言 AssertUtils.assertPojoEquals(user, loginUser, "updateTime"); - assertEquals(roleIds, loginUser.getRoleIds()); } @Test @@ -247,15 +237,10 @@ public class AuthServiceImplTest extends BaseDbUnitTest { // mock authentication Long userId = randomLongId(); Set userRoleIds = randomSet(Long.class); - LoginUser loginUser = randomPojo(LoginUser.class, o -> { - o.setId(userId); - o.setRoleIds(userRoleIds); - }); + LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(userId)); when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword()))) .thenReturn(authentication); when(authentication.getPrincipal()).thenReturn(loginUser); - // mock 获得 User 拥有的角色编号数组 - when(permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()))).thenReturn(userRoleIds); // mock 缓存登录用户到 Redis String token = randomString(); when(userSessionService.createUserSession(loginUser, userIp, userAgent)).thenReturn(token); diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java index 7ac339f54..12fddf38a 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java @@ -1,20 +1,22 @@ package cn.iocoder.yudao.module.system.service.permission; import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO; +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuBatchInsertMapper; import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper; import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleBatchInsertMapper; import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper; +import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum; import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer; import cn.iocoder.yudao.module.system.service.dept.DeptService; -import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; -import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.system.service.user.AdminUserService; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; @@ -25,10 +27,10 @@ import java.util.List; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -54,6 +56,9 @@ public class PermissionServiceTest extends BaseDbUnitTest { private MenuService menuService; @MockBean private DeptService deptService; + @MockBean + private AdminUserService userService; + @MockBean private PermissionProducer permissionProducer; @@ -124,112 +129,119 @@ public class PermissionServiceTest extends BaseDbUnitTest { assertPojoEquals(dbUserRoles.get(0), userRoleDO02); } - @Test // 测试从 context 获取的场景 - public void testGetDeptDataPermission_fromContext() { - // 准备参数 - LoginUser loginUser = randomPojo(LoginUser.class); - // mock 方法 - DeptDataPermissionRespDTO respDTO = new DeptDataPermissionRespDTO(); - loginUser.setContext(PermissionServiceImpl.CONTEXT_KEY, respDTO); - - // 调用 - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser); - // 断言 - assertSame(respDTO, result); - } - @Test public void testGetDeptDataPermission_All() { // 准备参数 - LoginUser loginUser = randomPojo(LoginUser.class); - // mock 方法 - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())); - when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO)); + Long userId = 1L; + // mock 用户的角色编号 + userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L)); + // mock 获得用户的角色 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope()) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO)); + when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO); // 调用 - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser); + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId); // 断言 assertTrue(result.getAll()); assertFalse(result.getSelf()); assertTrue(CollUtil.isEmpty(result.getDeptIds())); - assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class)); } @Test public void testGetDeptDataPermission_DeptCustom() { // 准备参数 - LoginUser loginUser = randomPojo(LoginUser.class); - // mock 方法 - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())); - when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO)); + Long userId = 1L; + // mock 用户的角色编号 + userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L)); + // mock 获得用户的角色 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope()) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO)); + when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO); + // mock 部门的返回 + when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用 // 调用 - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser); + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(1L); // 断言 assertFalse(result.getAll()); assertFalse(result.getSelf()); assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size()); assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds())); - assertTrue(CollUtil.contains(result.getDeptIds(), loginUser.getDeptId())); - assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class)); + assertTrue(CollUtil.contains(result.getDeptIds(), 3L)); } @Test public void testGetDeptDataPermission_DeptOnly() { // 准备参数 - LoginUser loginUser = randomPojo(LoginUser.class); - // mock 方法 - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())); - when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO)); + Long userId = 1L; + // mock 用户的角色编号 + userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L)); + // mock 获得用户的角色 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope()) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO)); + when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO); + // mock 部门的返回 + when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用 // 调用 - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser); + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(1L); // 断言 assertFalse(result.getAll()); assertFalse(result.getSelf()); assertEquals(1, result.getDeptIds().size()); - assertTrue(CollUtil.contains(result.getDeptIds(), loginUser.getDeptId())); - assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class)); + assertTrue(CollUtil.contains(result.getDeptIds(), 3L)); } @Test public void testGetDeptDataPermission_DeptAndChild() { // 准备参数 - LoginUser loginUser = randomPojo(LoginUser.class); - // mock 方法(角色) - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())); - when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO)); + Long userId = 1L; + // mock 用户的角色编号 + userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L)); + // mock 获得用户的角色 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope()) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO)); + when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO); + // mock 部门的返回 + when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用 // mock 方法(部门) DeptDO deptDO = randomPojo(DeptDO.class); - when(deptService.getDeptsByParentIdFromCache(eq(loginUser.getDeptId()), eq(true))) + when(deptService.getDeptsByParentIdFromCache(eq(3L), eq(true))) .thenReturn(singletonList(deptDO)); // 调用 - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser); + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId); // 断言 assertFalse(result.getAll()); assertFalse(result.getSelf()); assertEquals(2, result.getDeptIds().size()); assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId())); - assertTrue(CollUtil.contains(result.getDeptIds(), loginUser.getDeptId())); - assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class)); + assertTrue(CollUtil.contains(result.getDeptIds(), 3L)); } @Test public void testGetDeptDataPermission_Self() { // 准备参数 - LoginUser loginUser = randomPojo(LoginUser.class); - // mock 方法 - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())); - when(roleService.getRolesFromCache(same(loginUser.getRoleIds()))).thenReturn(singletonList(roleDO)); + Long userId = 1L; + // mock 用户的角色编号 + userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L)); + // mock 获得用户的角色 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope()) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(roleService.getRolesFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO)); + when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO); // 调用 - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(loginUser); + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId); // 断言 assertFalse(result.getAll()); assertTrue(result.getSelf()); assertTrue(CollUtil.isEmpty(result.getDeptIds())); - assertSame(result, loginUser.getContext(PermissionServiceImpl.CONTEXT_KEY, DeptDataPermissionRespDTO.class)); } } From 73bf0b6f4f42423d0c513560b86e44d5f7a18e66 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 7 May 2022 01:30:37 +0800 Subject: [PATCH 06/21] =?UTF-8?q?=E5=8E=BB=E9=99=A4=20LoginUser=20?= =?UTF-8?q?=E7=9A=84=20updateTime=E3=80=81username=E3=80=81password?= =?UTF-8?q?=E3=80=81status=20=E5=AD=97=E6=AE=B5=EF=BC=8C=E7=AE=80=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-dependencies/pom.xml | 5 - .../framework/security/core/LoginUser.java | 68 +------------ .../authentication/SpringSecurityUser.java | 78 +++++++++++++++ .../core/util/SecurityFrameworkUtils.java | 3 +- .../member/convert/auth/AuthConvert.java | 13 ++- .../service/auth/MemberAuthServiceImpl.java | 91 ++++++++---------- .../system/convert/auth/AuthConvert.java | 15 +-- .../service/auth/AdminAuthServiceImpl.java | 96 +++++++++---------- .../service/auth/UserSessionServiceImpl.java | 14 +-- .../auth/UserSessionServiceImplTest.java | 2 +- 10 files changed, 184 insertions(+), 201 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/SpringSecurityUser.java diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 63913572a..37a2c8a1b 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -172,11 +172,6 @@ ${revision} - - mysql - mysql-connector-java - ${mysql.version} - com.alibaba druid-spring-boot-starter diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java index c78148a46..20d567963 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java @@ -1,14 +1,12 @@ package cn.iocoder.yudao.framework.security.core; import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import java.util.*; +import java.util.HashMap; +import java.util.Map; /** * 登录用户信息 @@ -16,7 +14,7 @@ import java.util.*; * @author 芋道源码 */ @Data -public class LoginUser implements UserDetails { +public class LoginUser { /** * 用户编号 @@ -28,23 +26,6 @@ public class LoginUser implements UserDetails { * 关联 {@link UserTypeEnum} */ private Integer userType; - /** - * 最后更新时间 - */ - private Date updateTime; - - /** - * 用户名 - */ - private String username; - /** - * 密码 - */ - private String password; - /** - * 状态 - */ - private Integer status; /** * 租户编号 */ @@ -59,49 +40,6 @@ public class LoginUser implements UserDetails { @JsonIgnore private Map context; - @Override - @JsonIgnore// 避免序列化 - public String getPassword() { - return password; - } - - @Override - public String getUsername() { - return username; - } - - @Override - @JsonIgnore// 避免序列化 - public boolean isEnabled() { - return CommonStatusEnum.ENABLE.getStatus().equals(status); - } - - @Override - @JsonIgnore// 避免序列化 - public Collection getAuthorities() { - return new HashSet<>(); - } - - @Override - @JsonIgnore// 避免序列化 - public boolean isAccountNonExpired() { - return true; // 返回 true,不依赖 Spring Security 判断 - } - - @Override - @JsonIgnore// 避免序列化 - public boolean isAccountNonLocked() { - return true; // 返回 true,不依赖 Spring Security 判断 - } - - @Override - @JsonIgnore// 避免序列化 - public boolean isCredentialsNonExpired() { - return true; // 返回 true,不依赖 Spring Security 判断 - } - - // ========== 上下文 ========== - public void setContext(String key, Object value) { if (context == null) { context = new HashMap<>(); diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/SpringSecurityUser.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/SpringSecurityUser.java new file mode 100644 index 000000000..67b0c5ea6 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/SpringSecurityUser.java @@ -0,0 +1,78 @@ +package cn.iocoder.yudao.framework.security.core.authentication; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; + +/** + * 登录用户信息 + * + * @author 芋道源码 + */ +@Data +@AllArgsConstructor +public class SpringSecurityUser implements UserDetails { + + /** + * 用户编号 + */ + private Long id; + + /** + * 用户名 + */ + private String username; + /** + * 密码 + */ + private String password; + /** + * 状态 + */ + private Integer status; + /** + * 租户编号 + */ + private Long tenantId; + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public boolean isEnabled() { + return CommonStatusEnum.ENABLE.getStatus().equals(status); + } + + @Override + public Collection getAuthorities() { + return Collections.emptyList(); + } + + @Override + public boolean isAccountNonExpired() { + return true; // 返回 true,不依赖 Spring Security 判断 + } + + @Override + public boolean isAccountNonLocked() { + return true; // 返回 true,不依赖 Spring Security 判断 + } + + @Override + public boolean isCredentialsNonExpired() { + return true; // 返回 true,不依赖 Spring Security 判断 + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java index bbc1a9435..253974539 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java @@ -11,6 +11,7 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; +import java.util.Collections; /** * 安全服务工具类 @@ -98,7 +99,7 @@ public class SecurityFrameworkUtils { private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { // 创建 UsernamePasswordAuthenticationToken 对象 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( - loginUser, null, loginUser.getAuthorities()); + loginUser, null, Collections.emptyList()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); return authenticationToken; } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java index a15555df1..f33f85e70 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.member.convert.auth; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.security.core.LoginUser; +import cn.iocoder.yudao.framework.security.core.authentication.SpringSecurityUser; import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; @@ -19,13 +19,12 @@ public interface AuthConvert { AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class); - @Mapping(source = "mobile", target = "username") - LoginUser convert0(MemberUserDO bean); + LoginUser convert(MemberUserDO bean); - default LoginUser convert(MemberUserDO bean) { - // 目的,为了设置 UserTypeEnum.MEMBER.getValue() - return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue()); - } + @Mapping(source = "mobile", target = "username") + SpringSecurityUser convert2(MemberUserDO user); + + LoginUser convert(SpringSecurityUser bean); SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO); SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO); diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java index f82028f39..b79e55830 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.member.service.auth; import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; @@ -78,7 +77,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { throw new UsernameNotFoundException(mobile); } // 创建 LoginUser 对象 - return AuthConvert.INSTANCE.convert(user); + return AuthConvert.INSTANCE.convert2(user); } @Override @@ -87,7 +86,8 @@ public class MemberAuthServiceImpl implements MemberAuthService { LoginUser loginUser = login0(reqVO.getMobile(), reqVO.getPassword()); // 缓存登录用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); + return createUserSessionAfterLoginSuccess(loginUser, reqVO.getMobile(), + LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); } @Override @@ -101,10 +101,11 @@ public class MemberAuthServiceImpl implements MemberAuthService { Assert.notNull(user, "获取用户失败,结果为空"); // 执行登陆 - LoginUser loginUser = AuthConvert.INSTANCE.convert(user); + LoginUser loginUser = buildLoginUser(user); // 缓存登录用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SMS, userIp, userAgent); + return createUserSessionAfterLoginSuccess(loginUser, reqVO.getMobile(), + LoginLogTypeEnum.LOGIN_SMS, userIp, userAgent); } @Override @@ -123,10 +124,11 @@ public class MemberAuthServiceImpl implements MemberAuthService { } // 创建 LoginUser 对象 - LoginUser loginUser = AuthConvert.INSTANCE.convert(user); + LoginUser loginUser = buildLoginUser(user); // 缓存登录用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); + return createUserSessionAfterLoginSuccess(loginUser, null, + LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); } @Override @@ -142,9 +144,10 @@ public class MemberAuthServiceImpl implements MemberAuthService { return token; } - private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) { + private String createUserSessionAfterLoginSuccess(LoginUser loginUser, String mobile, + LoginLogTypeEnum logType, String userIp, String userAgent) { // 插入登陆日志 - createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS); + createLoginLog(loginUser.getId(), mobile, logType, LoginResultEnum.SUCCESS); // 缓存登录用户到 Redis 中,返回 Token 令牌 return userSessionApi.createUserSession(loginUser, userIp, userAgent); } @@ -164,29 +167,32 @@ public class MemberAuthServiceImpl implements MemberAuthService { authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken( username, password, getUserType())); } catch (BadCredentialsException badCredentialsException) { - this.createLoginLog(username, logType, LoginResultEnum.BAD_CREDENTIALS); + this.createLoginLog(null, username, logType, LoginResultEnum.BAD_CREDENTIALS); throw exception(AUTH_LOGIN_BAD_CREDENTIALS); } catch (DisabledException disabledException) { - this.createLoginLog(username, logType, LoginResultEnum.USER_DISABLED); + this.createLoginLog(null, username, logType, LoginResultEnum.USER_DISABLED); throw exception(AUTH_LOGIN_USER_DISABLED); } catch (AuthenticationException authenticationException) { log.error("[login0][username({}) 发生未知异常]", username, authenticationException); - this.createLoginLog(username, logType, LoginResultEnum.UNKNOWN_ERROR); + this.createLoginLog(null, username, logType, LoginResultEnum.UNKNOWN_ERROR); throw exception(AUTH_LOGIN_FAIL_UNKNOWN); } Assert.notNull(authentication.getPrincipal(), "Principal 不会为空"); return (LoginUser) authentication.getPrincipal(); } - private void createLoginLog(String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) { + private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) { // 获得用户 - MemberUserDO user = userService.getUserByMobile(mobile); + if (userId == null) { + MemberUserDO user = userService.getUserByMobile(mobile); + userId = user != null ? user.getId() : null; + } // 插入登录日志 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); reqDTO.setLogType(logType.getType()); reqDTO.setTraceId(TracerUtils.getTraceId()); - if (user != null) { - reqDTO.setUserId(user.getId()); + if (userId != null) { + reqDTO.setUserId(userId); } reqDTO.setUserType(getUserType().getValue()); reqDTO.setUsername(mobile); @@ -195,39 +201,14 @@ public class MemberAuthServiceImpl implements MemberAuthService { reqDTO.setResult(loginResult.getResult()); loginLogApi.createLoginLog(reqDTO); // 更新最后登录时间 - if (user != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) { - userService.updateUserLogin(user.getId(), getClientIP()); + if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) { + userService.updateUserLogin(userId, getClientIP()); } } @Override public LoginUser verifyTokenAndRefresh(String token) { - // 获得 LoginUser - LoginUser loginUser = userSessionApi.getLoginUser(token); - if (loginUser == null) { - return null; - } - // 刷新 LoginUser 缓存 - this.refreshLoginUserCache(token, loginUser); - return loginUser; - } - - private void refreshLoginUserCache(String token, LoginUser loginUser) { - // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存 - if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() < - userSessionApi.getSessionTimeoutMillis() / 3) { - return; - } - - // 重新加载 UserDO 信息 - MemberUserDO user = userService.getUser(loginUser.getId()); - if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) { - // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面 - throw exception(AUTH_TOKEN_EXPIRED); - } - - // 刷新 LoginUser 缓存 - userSessionApi.refreshUserSession(token, loginUser); + return userSessionApi.getLoginUser(token); } @Override @@ -239,10 +220,10 @@ public class MemberAuthServiceImpl implements MemberAuthService { } // 执行登陆 - this.createLoginLog(user.getMobile(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS); + createLoginLog(userId, user.getMobile(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS); // 创建 LoginUser 对象 - return AuthConvert.INSTANCE.convert(user); + return buildLoginUser(user); } @Override @@ -255,7 +236,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { // 删除 session userSessionApi.deleteUserSession(token); // 记录登出日志 - this.createLogoutLog(loginUser.getId(), loginUser.getUsername()); + createLogoutLog(loginUser.getId()); } @Override @@ -321,17 +302,29 @@ public class MemberAuthServiceImpl implements MemberAuthService { return user; } - private void createLogoutLog(Long userId, String username) { + private void createLogoutLog(Long userId) { LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType()); reqDTO.setTraceId(TracerUtils.getTraceId()); reqDTO.setUserId(userId); reqDTO.setUserType(getUserType().getValue()); - reqDTO.setUsername(username); + reqDTO.setUsername(getMobile(userId)); reqDTO.setUserAgent(ServletUtils.getUserAgent()); reqDTO.setUserIp(getClientIP()); reqDTO.setResult(LoginResultEnum.SUCCESS.getResult()); loginLogApi.createLoginLog(reqDTO); } + private LoginUser buildLoginUser(MemberUserDO user) { + return AuthConvert.INSTANCE.convert(user).setUserType(getUserType().getValue()); + } + + private String getMobile(Long userId) { + if (userId == null) { + return null; + } + MemberUserDO user = userService.getUser(userId); + return user != null ? user.getMobile() : null; + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java index d7979bc81..77232d397 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java @@ -1,20 +1,17 @@ package cn.iocoder.yudao.module.system.convert.auth; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; +import cn.iocoder.yudao.framework.security.core.authentication.SpringSecurityUser; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.enums.permission.MenuIdEnum; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; import org.slf4j.LoggerFactory; @@ -25,13 +22,11 @@ public interface AuthConvert { AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class); - @Mapping(source = "updateTime", target = "updateTime", ignore = true) // 字段相同,但是含义不同,忽略 - LoginUser convert0(AdminUserDO bean); + LoginUser convert(AdminUserDO bean); - default LoginUser convert(AdminUserDO bean) { - // 目的,为了设置 UserTypeEnum.ADMIN.getValue() - return convert0(bean).setUserType(UserTypeEnum.ADMIN.getValue()); - } + SpringSecurityUser convert2(AdminUserDO user); + + LoginUser convert(SpringSecurityUser bean); default AuthPermissionInfoRespVO convert(AdminUserDO user, List roleList, List menuList) { return AuthPermissionInfoRespVO.builder() diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index ebbfb6dfa..ab8cc9658 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -1,12 +1,12 @@ package cn.iocoder.yudao.module.system.service.auth; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; +import cn.iocoder.yudao.framework.security.core.authentication.SpringSecurityUser; import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; @@ -79,7 +79,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { throw new UsernameNotFoundException(username); } // 创建 LoginUser 对象 - return buildLoginUser(user); + return AuthConvert.INSTANCE.convert2(user); } @Override @@ -89,7 +89,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { if (user == null) { throw new UsernameNotFoundException(String.valueOf(userId)); } - createLoginLog(user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS); + createLoginLog(userId, user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS); // 创建 LoginUser 对象 return buildLoginUser(user); @@ -104,7 +104,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword()); // 缓存登陆用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); + return createUserSessionAfterLoginSuccess(loginUser, reqVO.getUsername(), + LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); } @Override @@ -132,7 +133,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { LoginUser loginUser = buildLoginUser(user); // 缓存登陆用户到 Redis 中,返回 sessionId 编号 - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_MOBILE, userIp, userAgent); + return createUserSessionAfterLoginSuccess(loginUser, reqVO.getMobile(), + LoginLogTypeEnum.LOGIN_MOBILE, userIp, userAgent); } private void verifyCaptcha(AuthLoginReqVO reqVO) { @@ -147,13 +149,13 @@ public class AdminAuthServiceImpl implements AdminAuthService { String code = captchaService.getCaptchaCode(reqVO.getUuid()); if (code == null) { // 创建登录失败日志(验证码不存在) - this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND); + createLoginLog(null, reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND); throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND); } // 验证码不正确 if (!code.equals(reqVO.getCode())) { // 创建登录失败日志(验证码不正确) - this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR); + createLoginLog(null, reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR); throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR); } // 正确,所以要删除下验证码 @@ -170,29 +172,35 @@ public class AdminAuthServiceImpl implements AdminAuthService { authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken( username, password, getUserType())); } catch (BadCredentialsException badCredentialsException) { - this.createLoginLog(username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); + createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); throw exception(AUTH_LOGIN_BAD_CREDENTIALS); } catch (DisabledException disabledException) { - this.createLoginLog(username, logTypeEnum, LoginResultEnum.USER_DISABLED); + createLoginLog(null, username, logTypeEnum, LoginResultEnum.USER_DISABLED); throw exception(AUTH_LOGIN_USER_DISABLED); } catch (AuthenticationException authenticationException) { log.error("[login0][username({}) 发生未知异常]", username, authenticationException); - this.createLoginLog(username, logTypeEnum, LoginResultEnum.UNKNOWN_ERROR); + createLoginLog(null, username, logTypeEnum, LoginResultEnum.UNKNOWN_ERROR); throw exception(AUTH_LOGIN_FAIL_UNKNOWN); } Assert.notNull(authentication.getPrincipal(), "Principal 不会为空"); - return (LoginUser) authentication.getPrincipal(); + // 构建 User 对象 + return AuthConvert.INSTANCE.convert((SpringSecurityUser) authentication.getPrincipal()) + .setUserType(getUserType().getValue()); } - private void createLoginLog(String username, LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) { + private void createLoginLog(Long userId, String username, + LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) { // 获得用户 - AdminUserDO user = userService.getUserByUsername(username); + if (userId == null) { + AdminUserDO user = userService.getUserByUsername(username); + userId = user != null ? user.getId() : null; + } // 插入登录日志 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); reqDTO.setLogType(logTypeEnum.getType()); reqDTO.setTraceId(TracerUtils.getTraceId()); - if (user != null) { - reqDTO.setUserId(user.getId()); + if (userId != null) { + reqDTO.setUserId(userId); } reqDTO.setUserType(getUserType().getValue()); reqDTO.setUsername(username); @@ -201,8 +209,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { reqDTO.setResult(loginResult.getResult()); loginLogService.createLoginLog(reqDTO); // 更新最后登录时间 - if (user != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) { - userService.updateUserLogin(user.getId(), ServletUtils.getClientIP()); + if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) { + userService.updateUserLogin(userId, ServletUtils.getClientIP()); } } @@ -225,7 +233,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { LoginUser loginUser = buildLoginUser(user); // 缓存登录用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); + return createUserSessionAfterLoginSuccess(loginUser, null, + LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); } @Override @@ -237,12 +246,14 @@ public class AdminAuthServiceImpl implements AdminAuthService { socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO)); // 缓存登录用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); + return createUserSessionAfterLoginSuccess(loginUser, reqVO.getUsername(), + LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); } - private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) { + private String createUserSessionAfterLoginSuccess(LoginUser loginUser, String username, + LoginLogTypeEnum logType, String userIp, String userAgent) { // 插入登陆日志 - createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS); + createLoginLog(loginUser.getId(), username, logType, LoginResultEnum.SUCCESS); // 缓存登录用户到 Redis 中,返回 Token 令牌 return userSessionService.createUserSession(loginUser, userIp, userAgent); } @@ -257,7 +268,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { // 删除 session userSessionService.deleteUserSession(token); // 记录登出日志 - createLogoutLog(loginUser.getId(), loginUser.getUsername()); + createLogoutLog(loginUser.getId()); } @Override @@ -265,13 +276,13 @@ public class AdminAuthServiceImpl implements AdminAuthService { return UserTypeEnum.ADMIN; } - private void createLogoutLog(Long userId, String username) { + private void createLogoutLog(Long userId) { LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType()); reqDTO.setTraceId(TracerUtils.getTraceId()); reqDTO.setUserId(userId); + reqDTO.setUsername(getUsername(userId)); reqDTO.setUserType(getUserType().getValue()); - reqDTO.setUsername(username); reqDTO.setUserAgent(ServletUtils.getUserAgent()); reqDTO.setUserIp(ServletUtils.getClientIP()); reqDTO.setResult(LoginResultEnum.SUCCESS.getResult()); @@ -280,36 +291,19 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Override public LoginUser verifyTokenAndRefresh(String token) { - // 获得 LoginUser - LoginUser loginUser = userSessionService.getLoginUser(token); - if (loginUser == null) { - return null; - } - // 刷新 LoginUser 缓存 - return this.refreshLoginUserCache(token, loginUser); - } - - private LoginUser refreshLoginUserCache(String token, LoginUser loginUser) { - // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存 - if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() < - userSessionService.getSessionTimeoutMillis() / 3) { - return loginUser; - } - - // 重新加载 AdminUserDO 信息 - AdminUserDO user = userService.getUser(loginUser.getId()); - if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) { - throw exception(AUTH_TOKEN_EXPIRED); // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面 - } - - // 刷新 LoginUser 缓存 - LoginUser newLoginUser= buildLoginUser(user); - userSessionService.refreshUserSession(token, newLoginUser); - return newLoginUser; + return userSessionService.getLoginUser(token); } private LoginUser buildLoginUser(AdminUserDO user) { - return AuthConvert.INSTANCE.convert(user); + return AuthConvert.INSTANCE.convert(user).setUserType(getUserType().getValue()); + } + + private String getUsername(Long userId) { + if (userId == null) { + return null; + } + AdminUserDO user = userService.getUser(userId); + return user != null ? user.getUsername() : null; } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java index 333e54c69..75cedcf18 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java @@ -23,7 +23,6 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.Duration; import java.util.Collection; -import java.util.Date; import java.util.List; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @@ -106,12 +105,11 @@ public class UserSessionServiceImpl implements UserSessionService { // 生成 Session 编号 String token = generateToken(); // 写入 Redis 缓存 - loginUser.setUpdateTime(new Date()); loginUserRedisDAO.set(token, loginUser); // 写入 DB 中 UserSessionDO userSession = UserSessionDO.builder().token(token) .userId(loginUser.getId()).userType(loginUser.getUserType()) - .userIp(userIp).userAgent(userAgent).username(loginUser.getUsername()) + .userIp(userIp).userAgent(userAgent).username("") .sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis()))) .build(); userSessionMapper.insert(userSession); @@ -121,15 +119,7 @@ public class UserSessionServiceImpl implements UserSessionService { @Override public void refreshUserSession(String token, LoginUser loginUser) { - // 写入 Redis 缓存 - loginUser.setUpdateTime(new Date()); - loginUserRedisDAO.set(token, loginUser); - // 更新 DB 中 - UserSessionDO updateObj = UserSessionDO.builder().build(); - updateObj.setUsername(loginUser.getUsername()); - updateObj.setUpdateTime(new Date()); - updateObj.setSessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis()))); - userSessionMapper.updateByToken(token, updateObj); + } @Override diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java index ab739be79..e06591cca 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java @@ -201,7 +201,7 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest { assertPojoEquals(redisLoginUser, loginUser, "username", "password"); // 校验 UserSessionDO 记录 UserSessionDO updateDO = userSessionMapper.selectOne(UserSessionDO::getToken, token); - assertEquals(updateDO.getUsername(), loginUser.getUsername()); +// assertEquals(updateDO.getUsername(), loginUser.getUsername()); assertNotNull(userSession.getUpdateTime()); assertNotNull(userSession.getSessionTimeout()); } From baadb5a9370d2e57b7ea3b7247c0ab34e387e31c Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 May 2022 00:17:48 +0800 Subject: [PATCH 07/21] =?UTF-8?q?=E7=AE=80=E5=8C=96=20mock=20login=20?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E7=99=BB=E5=BD=95=E7=9A=84=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=EF=BC=8C=E7=94=B1=20TokenAuthenticationFilter=20=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/web/TenantContextWebFilter.java | 8 ++-- ...ultiUserDetailsAuthenticationProvider.java | 11 ----- .../filter/TokenAuthenticationFilter.java | 11 +++-- .../service/SecurityAuthFrameworkService.java | 8 ---- .../web/config/YudaoWebAutoConfiguration.java | 8 ++++ .../web/core/util/WebFrameworkUtils.java | 46 ++++++++++++++++++- .../service/auth/MemberAuthServiceImpl.java | 15 ------ .../system/enums/logger/LoginLogTypeEnum.java | 1 - .../controller/admin/auth/AuthController.http | 6 +-- .../service/auth/AdminAuthServiceImpl.java | 13 ------ .../service/auth/AuthServiceImplTest.java | 26 ----------- 11 files changed, 67 insertions(+), 86 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantContextWebFilter.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantContextWebFilter.java index ec159e0ec..272419c09 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantContextWebFilter.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantContextWebFilter.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.framework.tenant.core.web; -import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; @@ -24,9 +24,9 @@ public class TenantContextWebFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 设置 - String tenantId = request.getHeader(HEADER_TENANT_ID); - if (StrUtil.isNotEmpty(tenantId)) { - TenantContextHolder.setTenantId(Long.valueOf(tenantId)); + Long tenantId = WebFrameworkUtils.getTenantId(request); + if (tenantId != null) { + TenantContextHolder.setTenantId(tenantId); } try { chain.doFilter(request, response); diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java index dc8533f96..bfd441160 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java @@ -105,17 +105,6 @@ public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsA return selectService(request).verifyTokenAndRefresh(token); } - /** - * 模拟指定用户编号的 LoginUser - * - * @param request 请求 - * @param userId 用户编号 - * @return 登录用户 - */ - public LoginUser mockLogin(HttpServletRequest request, Long userId) { - return selectService(request).mockLogin(userId); - } - /** * 基于 token 退出登录 * diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java index 0908560c8..cf0ee7a23 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import lombok.RequiredArgsConstructor; import org.springframework.web.filter.OncePerRequestFilter; @@ -38,12 +39,13 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { throws ServletException, IOException { String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); if (StrUtil.isNotEmpty(token)) { + Integer userType = WebFrameworkUtils.getLoginUserType(request); try { // 验证 token 有效性 LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token); // 模拟 Login 功能,方便日常开发调试 if (loginUser == null) { - loginUser = mockLoginUser(request, token); + loginUser = mockLoginUser(request, token, userType); } // 设置当前用户 if (loginUser != null) { @@ -67,9 +69,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { * * @param request 请求 * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号 + * @param userType 用户类型 * @return 模拟的 LoginUser */ - private LoginUser mockLoginUser(HttpServletRequest request, String token) { + private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) { if (!securityProperties.getMockEnable()) { return null; } @@ -77,8 +80,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { if (!token.startsWith(securityProperties.getMockSecret())) { return null; } + // 构建模拟用户 Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length())); - return authenticationProvider.mockLogin(request, userId); + return new LoginUser().setId(userId).setUserType(userType) + .setTenantId(WebFrameworkUtils.getTenantId(request)); } } diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java index 1f76e161f..11370a3d4 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java @@ -20,14 +20,6 @@ public interface SecurityAuthFrameworkService extends UserDetailsService { */ LoginUser verifyTokenAndRefresh(String token); - /** - * 模拟指定用户编号的 LoginUser - * - * @param userId 用户编号 - * @return 登录用户 - */ - LoginUser mockLogin(Long userId); - /** * 基于 token 退出登录 * diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java index a6f932dd0..a7c5b7a53 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.web.core.filter.DemoFilter; import cn.iocoder.yudao.framework.web.core.filter.XssFilter; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler; +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -65,6 +66,13 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer { return new GlobalResponseBodyHandler(); } + @Bean + @SuppressWarnings("InstantiationOfUtilityClass") + public WebFrameworkUtils webFrameworkUtils(WebProperties webProperties) { + // 由于 WebFrameworkUtils 需要使用到 webProperties 属性,所以注册为一个 Bean + return new WebFrameworkUtils(webProperties); + } + // ========== Filter 相关 ========== /** diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java index 273f34072..b3a7f7ecb 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.framework.web.core.util; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.web.config.WebProperties; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -21,16 +23,43 @@ public class WebFrameworkUtils { private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result"; + private static final String HEADER_TENANT_ID = "tenant-id"; + + private static WebProperties properties; + + public WebFrameworkUtils(WebProperties webProperties) { + WebFrameworkUtils.properties = webProperties; + } + + /** + * 获得租户编号,从 header 中 + * 考虑到其它 framework 组件也会使用到租户编号,所以不得不放在 WebFrameworkUtils 统一提供 + * + * @param request 请求 + * @return 租户编号 + */ + public static Long getTenantId(HttpServletRequest request) { + String tenantId = request.getHeader(HEADER_TENANT_ID); + return StrUtil.isNotEmpty(tenantId) ? Long.valueOf(tenantId) : null; + } + public static void setLoginUserId(ServletRequest request, Long userId) { request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId); } + /** + * 设置用户类型 + * + * @param request 请求 + * @param userType 用户类型 + */ public static void setLoginUserType(ServletRequest request, Integer userType) { request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType); } /** * 获得当前用户的编号,从请求中 + * 注意:该方法仅限于 framework 框架使用!!! * * @param request 请求 * @return 用户编号 @@ -43,7 +72,8 @@ public class WebFrameworkUtils { } /** - * 获得当前用户的类型,从请求中 + * 获得当前用户的类型 + * 注意:该方法仅限于 web 相关的 framework 组件使用!!! * * @param request 请求 * @return 用户编号 @@ -52,7 +82,19 @@ public class WebFrameworkUtils { if (request == null) { return null; } - return (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE); + // 1. 优先,从 Attribute 中获取 + Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE); + if (userType == null) { + return null; + } + // 2. 其次,基于 URL 前缀的约定 + if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) { + return UserTypeEnum.ADMIN.getValue(); + } + if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) { + return UserTypeEnum.MEMBER.getValue(); + } + return null; } public static Integer getLoginUserType() { diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java index b79e55830..c58a6d74d 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java @@ -211,21 +211,6 @@ public class MemberAuthServiceImpl implements MemberAuthService { return userSessionApi.getLoginUser(token); } - @Override - public LoginUser mockLogin(Long userId) { - // 获取用户编号对应的 UserDO - MemberUserDO user = userService.getUser(userId); - if (user == null) { - throw new UsernameNotFoundException(String.valueOf(userId)); - } - - // 执行登陆 - createLoginLog(userId, user.getMobile(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS); - - // 创建 LoginUser 对象 - return buildLoginUser(user); - } - @Override public void logout(String token) { // 查询用户信息 diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java index 2f845fd10..ab9d0bbbd 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java @@ -12,7 +12,6 @@ public enum LoginLogTypeEnum { LOGIN_USERNAME(100), // 使用账号登录 LOGIN_SOCIAL(101), // 使用社交登录 - LOGIN_MOCK(102), // 使用 Mock 登录 LOGIN_MOBILE(103), // 使用手机登陆 LOGIN_SMS(104), // 使用短信登陆 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http index e6c70f9df..c2715634e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http @@ -1,5 +1,5 @@ ### 请求 /login 接口 => 成功 -POST {{baseUrl}}/system/login +POST {{baseUrl}}/system/auth/login Content-Type: application/json tenant-id: {{adminTenentId}} @@ -11,7 +11,7 @@ tenant-id: {{adminTenentId}} } ### 请求 /login 接口 => 成功(无验证码) -POST {{baseUrl}}/system/login +POST {{baseUrl}}/system/auth/login Content-Type: application/json tenant-id: {{adminTenentId}} @@ -21,7 +21,7 @@ tenant-id: {{adminTenentId}} } ### 请求 /get-permission-info 接口 => 成功 -GET {{baseUrl}}/system/get-permission-info +GET {{baseUrl}}/system/auth/get-permission-info Authorization: Bearer {{token}} tenant-id: {{adminTenentId}} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index ab8cc9658..475966403 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -82,19 +82,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { return AuthConvert.INSTANCE.convert2(user); } - @Override - public LoginUser mockLogin(Long userId) { - // 获取用户编号对应的 AdminUserDO - AdminUserDO user = userService.getUser(userId); - if (user == null) { - throw new UsernameNotFoundException(String.valueOf(userId)); - } - createLoginLog(userId, user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS); - - // 创建 LoginUser 对象 - return buildLoginUser(user); - } - @Override public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { // 判断验证码是否正确 diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java index 0cb376668..e13570a2b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java @@ -96,32 +96,6 @@ public class AuthServiceImplTest extends BaseDbUnitTest { username); // 异常提示为 username } - @Test - public void testMockLogin_success() { - // 准备参数 - Long userId = randomLongId(); - // mock 方法 01 - AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId)); - when(userService.getUser(eq(userId))).thenReturn(user); - - // 调用 - LoginUser loginUser = authService.mockLogin(userId); - // 断言 - AssertUtils.assertPojoEquals(user, loginUser, "updateTime"); - } - - @Test - public void testMockLogin_userNotFound() { - // 准备参数 - Long userId = randomLongId(); - // mock 方法 - - // 调用, 并断言异常 - assertThrows(UsernameNotFoundException.class, // 抛出 UsernameNotFoundException 异常 - () -> authService.mockLogin(userId), - String.valueOf(userId)); // 异常提示为 userId - } - @Test public void testLogin_captchaNotFound() { // 准备参数 From 3351ebbbb4ea4971a6fde9d0b9ba97913cc46137 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 May 2022 00:38:55 +0800 Subject: [PATCH 08/21] =?UTF-8?q?=E5=8E=BB=E9=99=A4=20Spring=20Security=20?= =?UTF-8?q?=E7=9A=84=20logout=20handler=EF=BC=8C=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E8=87=AA=E5=B7=B1=E5=AE=9A=E4=B9=89=E7=9A=84=20logout=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../YudaoSecurityAutoConfiguration.java | 10 -- .../YudaoWebSecurityConfigurerAdapter.java | 14 +- .../handler/LogoutSuccessHandlerImpl.java | 40 ----- .../app/auth/AppAuthController.java | 17 +++ .../module/member/framework/package-info.java | 6 + .../config/SecurityConfiguration.java | 28 ++++ .../framework/security/core/package-info.java | 4 + .../controller/admin/auth/AuthController.java | 21 ++- .../dataobject/auth/OAuth2AccessTokenDO.java | 55 +++++++ .../dataobject/auth/OAuth2RefreshTokenDO.java | 49 ++++++ .../config/SecurityConfiguration.java | 1 + .../system/service/auth/OAuth2Service.java | 18 +++ .../service/auth/OAuth2ServiceImpl.java | 143 ++++++++++++++++++ yudao-ui-admin/src/api/login.js | 2 +- 14 files changed, 342 insertions(+), 66 deletions(-) delete mode 100644 yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java create mode 100644 yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java create mode 100644 yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/config/SecurityConfiguration.java create mode 100644 yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/core/package-info.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2Service.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java index 1db4797d4..af728045c 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java @@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocal import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter; import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl; import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl; -import cn.iocoder.yudao.framework.security.core.handler.LogoutSuccessHandlerImpl; import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService; import cn.iocoder.yudao.framework.web.config.WebProperties; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; @@ -19,7 +18,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import javax.annotation.Resource; import java.util.List; @@ -63,14 +61,6 @@ public class YudaoSecurityAutoConfiguration { return new AccessDeniedHandlerImpl(); } - /** - * 退出处理类 Bean - */ - @Bean - public LogoutSuccessHandler logoutSuccessHandler(MultiUserDetailsAuthenticationProvider authenticationProvider) { - return new LogoutSuccessHandlerImpl(securityProperties, authenticationProvider); - } - /** * Spring Security 加密器 * 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器 diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java index 0c0dd3278..c0bc2a056 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.framework.security.config; -import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider; import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter; import cn.iocoder.yudao.framework.web.config.WebProperties; @@ -17,7 +16,6 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import javax.annotation.Resource; import java.util.List; @@ -46,11 +44,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap */ @Resource private AccessDeniedHandler accessDeniedHandler; - /** - * 退出处理类 Bean - */ - @Resource - private LogoutSuccessHandler logoutSuccessHandler; /** * Token 认证过滤器 Bean */ @@ -114,11 +107,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap .headers().frameOptions().disable().and() // 一堆自定义的 Spring Security 处理器 .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) - .accessDeniedHandler(accessDeniedHandler).and() - // 登出地址的配置 - .logout().logoutSuccessHandler(logoutSuccessHandler).logoutRequestMatcher(request -> // 匹配多种用户类型的登出 - StrUtil.equalsAny(request.getRequestURI(), buildAdminApi("/system/logout"), - buildAppApi("/member/logout"))); + .accessDeniedHandler(accessDeniedHandler); + // 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高 // 设置每个请求的权限 httpSecurity diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java deleted file mode 100644 index 1a642304c..000000000 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.framework.security.core.handler; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; -import cn.iocoder.yudao.framework.security.config.SecurityProperties; -import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider; -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; -import lombok.AllArgsConstructor; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - - -/** - * 自定义退出处理器 - * - * @author ruoyi - */ -@AllArgsConstructor -public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { - - private final SecurityProperties securityProperties; - - private final MultiUserDetailsAuthenticationProvider authenticationProvider; - - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { - // 执行退出 - String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); - if (StrUtil.isNotBlank(token)) { - authenticationProvider.logout(request, token); - } - // 返回成功 - ServletUtils.writeJSON(response, CommonResult.success(null)); - } - -} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java index 3a78b3977..696ead921 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java @@ -1,7 +1,10 @@ package cn.iocoder.yudao.module.member.controller.app.auth; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.security.config.SecurityProperties; import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; import cn.iocoder.yudao.module.member.service.auth.MemberAuthService; import io.swagger.annotations.Api; @@ -13,6 +16,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -30,6 +34,9 @@ public class AppAuthController { @Resource private MemberAuthService authService; + @Resource + private SecurityProperties securityProperties; + @PostMapping("/login") @ApiOperation("使用手机 + 密码登录") public CommonResult login(@RequestBody @Valid AppAuthLoginReqVO reqVO) { @@ -37,6 +44,16 @@ public class AppAuthController { return success(AppAuthLoginRespVO.builder().token(token).build()); } + @PostMapping("/logout") + @ApiOperation("登出系统") + public CommonResult logout(HttpServletRequest request) { + String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); + if (StrUtil.isNotBlank(token)) { + authService.logout(token); + } + return success(true); + } + @PostMapping("/sms-login") @ApiOperation("使用手机 + 验证码登录") public CommonResult smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) { diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java new file mode 100644 index 000000000..02d61331a --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 system 模块的 framework 封装 + * + * @author 芋道源码 + */ +package cn.iocoder.yudao.module.member.framework; diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/config/SecurityConfiguration.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/config/SecurityConfiguration.java new file mode 100644 index 000000000..c284b35d6 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.member.framework.security.config; + +import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; + +/** + * Member 模块的 Security 配置 + */ +@Configuration("memberSecurityConfiguration") +public class SecurityConfiguration { + + @Bean("memberAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + + @Override + public void customize(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry) { + // 登录的接口 + registry.antMatchers(buildAdminApi("/member/auth/logout")).permitAll(); + } + + }; + } + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/core/package-info.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/core/package-info.java new file mode 100644 index 000000000..3abf5630f --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.member.framework.security.core; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 0e26674f8..2b494a9a1 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -1,9 +1,12 @@ package cn.iocoder.yudao.module.system.controller.admin.auth; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.collection.SetUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.framework.security.config.SecurityProperties; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; @@ -24,6 +27,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import java.util.List; import java.util.Set; @@ -52,15 +56,28 @@ public class AuthController { @Resource private SocialUserService socialUserService; + @Resource + private SecurityProperties securityProperties; + @PostMapping("/login") @ApiOperation("使用账号密码登录") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult login(@RequestBody @Valid AuthLoginReqVO reqVO) { String token = authService.login(reqVO, getClientIP(), getUserAgent()); - // 返回结果 return success(AuthLoginRespVO.builder().token(token).build()); } + @PostMapping("/logout") + @ApiOperation("登出系统") + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 + public CommonResult logout(HttpServletRequest request) { + String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); + if (StrUtil.isNotBlank(token)) { + authService.logout(token); + } + return success(true); + } + @GetMapping("/get-permission-info") @ApiOperation("获取登录用户的权限信息") public CommonResult getPermissionInfo() { @@ -130,7 +147,6 @@ public class AuthController { @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) { String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent()); - // 返回结果 return success(AuthLoginRespVO.builder().token(token).build()); } @@ -139,7 +155,6 @@ public class AuthController { @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) { String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent()); - // 返回结果 return success(AuthLoginRespVO.builder().token(token).build()); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java new file mode 100644 index 000000000..63e251335 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.system.dal.dataobject.auth; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * OAuth2 访问令牌 + * + */ +@TableName("system_oauth2_access_token") +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class OAuth2AccessTokenDO extends BaseDO { + + /** + * 编号,数据库字典 + */ + private Long id; + /** + * 访问令牌 + */ + private String accessToken; + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 刷新令牌 + * + * 关联 {@link OAuth2RefreshTokenDO#getRefreshToken()} + */ + private String refreshToken; + /** + * 过期时间 + */ + private Date expiresTime; + /** + * 创建 IP + */ + private String createIp; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java new file mode 100644 index 000000000..42824f297 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.system.dal.dataobject.auth; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * OAuth2 刷新令牌 + * + */ +@TableName("system_oauth2_refresh_token") +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class OAuth2RefreshTokenDO extends BaseDO { + + /** + * 编号,数据库字典 + */ + private Long id; + /** + * 刷新令牌 + */ + private String refreshToken; + /** + * 用户编号 + */ + private Integer userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 过期时间 + */ + private Date expiresTime; + /** + * 创建 IP + */ + private String createIp; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java index 567c98775..9a7eb9728 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java @@ -20,6 +20,7 @@ public class SecurityConfiguration { public void customize(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry) { // 登录的接口 registry.antMatchers(buildAdminApi("/system/auth/login")).permitAll(); + registry.antMatchers(buildAdminApi("/system/auth/logout")).permitAll(); // 社交登陆的接口 registry.antMatchers(buildAdminApi("/system/auth/social-auth-redirect")).permitAll(); registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll(); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2Service.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2Service.java new file mode 100644 index 000000000..030bef8f6 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2Service.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.system.service.auth; + +/** + * OAuth2.0 Service 接口 + * + * @author 芋道源码 + */ +public interface OAuth2Service { + +// OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String createIp); +// +// OAuth2AccessTokenRespDTO checkAccessToken(String accessToken); +// +// OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO); +// +// void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO); + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java new file mode 100644 index 000000000..7718f36e1 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java @@ -0,0 +1,143 @@ +package cn.iocoder.yudao.module.system.service.auth; + +import org.springframework.stereotype.Service; + +/** + * OAuth2.0 Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class OAuth2ServiceImpl implements OAuth2Service { + +// @Autowired +// private SystemBizProperties systemBizProperties; +// +// @Autowired +// private OAuth2AccessTokenMapper oauth2AccessTokenMapper; +// @Autowired +// private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper; +// +// @Autowired +// private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO; +// +// @Override +// @Transactional +// public OAuth2AccessTokenRespDTO createAccessToken(OAuth2CreateAccessTokenReqDTO createAccessTokenDTO) { +// // 创建刷新令牌 + 访问令牌 +// OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(createAccessTokenDTO.getUserId(), +// createAccessTokenDTO.getUserType(), createAccessTokenDTO.getCreateIp()); +// OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, createAccessTokenDTO.getCreateIp()); +// // 返回访问令牌 +// return OAuth2Convert.INSTANCE.convert(accessTokenDO); +// } +// +// @Override +// @Transactional +// public OAuth2AccessTokenRespDTO checkAccessToken(String accessToken) { +// OAuth2AccessTokenDO accessTokenDO = this.getOAuth2AccessToken(accessToken); +// if (accessTokenDO == null) { // 不存在 +// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_NOT_FOUND); +// } +// if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期 +// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_TOKEN_EXPIRED); +// } +// // 返回访问令牌 +// return OAuth2Convert.INSTANCE.convert(accessTokenDO); +// } +// +// @Override +// @Transactional +// public OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO) { +// OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(refreshAccessTokenDTO.getRefreshToken()); +// // 校验刷新令牌是否合法 +// if (refreshTokenDO == null) { // 不存在 +// throw ServiceExceptionUtil.exception(OAUTH2_REFRESH_TOKEN_NOT_FOUND); +// } +// if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期 +// throw ServiceExceptionUtil.exception(OAUTH_REFRESH_TOKEN_EXPIRED); +// } +// +// // 标记 refreshToken 对应的 accessToken 都不合法 +// // 这块的实现,参考了 Spring Security OAuth2 的代码 +// List accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshAccessTokenDTO.getRefreshToken()); +// accessTokenDOs.forEach(accessTokenDO -> deleteOAuth2AccessToken(accessTokenDO.getId())); +// +// // 创建访问令牌 +// OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp()); +// // 返回访问令牌 +// return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO); +// } +// +// @Override +// @Transactional +// public void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO) { +// // 删除 Access Token +// OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByUserIdAndUserType( +// removeTokenDTO.getUserId(), removeTokenDTO.getUserType()); +// if (accessTokenDO != null) { +// this.deleteOAuth2AccessToken(accessTokenDO.getId()); +// } +// +// // 删除 Refresh Token +// oauth2RefreshTokenMapper.deleteByUserIdAndUserType(removeTokenDTO.getUserId(), removeTokenDTO.getUserType()); +// } +// +// private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, String createIp) { +// OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO() +// .setId(generateAccessToken()) +// .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()) +// .setRefreshToken(refreshTokenDO.getId()) +// .setExpiresTime(new Date(System.currentTimeMillis() + systemBizProperties.getAccessTokenExpireTimeMillis())) +// .setCreateIp(createIp); +// oauth2AccessTokenMapper.insert(accessToken); +// return accessToken; +// } +// +// private OAuth2RefreshTokenDO createOAuth2RefreshToken(Integer userId, Integer userType, String createIp) { +// OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO() +// .setId(generateRefreshToken()) +// .setUserId(userId).setUserType(userType) +// .setExpiresTime(new Date(System.currentTimeMillis() + systemBizProperties.getRefreshTokenExpireTimeMillis())) +// .setCreateIp(createIp); +// oauth2RefreshTokenMapper.insert(refreshToken); +// return refreshToken; +// } +// +// private OAuth2AccessTokenDO getOAuth2AccessToken(String accessToken) { +// // 优先从 Redis 中获取 +// OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken); +// if (accessTokenDO != null) { +// return accessTokenDO; +// } +// +// // 获取不到,从 MySQL 中获取 +// accessTokenDO = oauth2AccessTokenMapper.selectById(accessToken); +// // 如果在 MySQL 存在,则往 Redis 中写入 +// if (accessTokenDO != null) { +// oauth2AccessTokenRedisDAO.set(accessTokenDO); +// } +// return accessTokenDO; +// } +// +// /** +// * 删除 accessToken 的 MySQL 与 Redis 的数据 +// * +// * @param accessToken 访问令牌 +// */ +// private void deleteOAuth2AccessToken(String accessToken) { +// // 删除 MySQL +// oauth2AccessTokenMapper.deleteById(accessToken); +// // 删除 Redis +// oauth2AccessTokenRedisDAO.delete(accessToken); +// } +// +// private static String generateAccessToken() { +// return StringUtils.uuid(true); +// } +// +// private static String generateRefreshToken() { +// return StringUtils.uuid(true); +// } + +} diff --git a/yudao-ui-admin/src/api/login.js b/yudao-ui-admin/src/api/login.js index 33222bc1d..77fbe9f9d 100644 --- a/yudao-ui-admin/src/api/login.js +++ b/yudao-ui-admin/src/api/login.js @@ -26,7 +26,7 @@ export function getInfo() { // 退出方法 export function logout() { return request({ - url: '/system/logout', + url: '/system/auth/logout', method: 'post' }) } From 3bd7e8e6829300d9f1d059b5b50123df3fc67af4 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 May 2022 02:09:22 +0800 Subject: [PATCH 09/21] =?UTF-8?q?=E5=8E=BB=E9=99=A4=20Spring=20Security=20?= =?UTF-8?q?=E7=9A=84=20Admin=20=E7=9A=84=20loadUsername=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E8=87=AA=E5=B7=B1=E5=AE=9A=E4=B9=89=E7=9A=84=20login0?= =?UTF-8?q?=20=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ultiUserDetailsAuthenticationProvider.java | 10 - .../service/SecurityAuthFrameworkService.java | 7 - .../service/auth/MemberAuthService.java | 7 + .../system/enums/ErrorCodeConstants.java | 1 - .../system/convert/auth/AuthConvert.java | 2 - .../system/service/auth/AdminAuthService.java | 11 +- .../service/auth/AdminAuthServiceImpl.java | 80 +++---- .../system/service/user/AdminUserService.java | 10 +- .../service/user/AdminUserServiceImpl.java | 28 ++- .../service/auth/AuthServiceImplTest.java | 219 ++++++++---------- 10 files changed, 171 insertions(+), 204 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java index bfd441160..416e196d6 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java @@ -105,16 +105,6 @@ public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsA return selectService(request).verifyTokenAndRefresh(token); } - /** - * 基于 token 退出登录 - * - * @param request 请求 - * @param token token - */ - public void logout(HttpServletRequest request, String token) { - selectService(request).logout(token); - } - private SecurityAuthFrameworkService selectService(HttpServletRequest request) { // 第一步,获得用户类型 UserTypeEnum userType = getUserType(request); diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java index 11370a3d4..3afef7acf 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java @@ -20,13 +20,6 @@ public interface SecurityAuthFrameworkService extends UserDetailsService { */ LoginUser verifyTokenAndRefresh(String token); - /** - * 基于 token 退出登录 - * - * @param token token - */ - void logout(String token); - /** * 获得用户类型。每个用户类型,对应一个 SecurityAuthFrameworkService 实现类。 * diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java index f7aae6ed8..f7abd8622 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java @@ -24,6 +24,13 @@ public interface MemberAuthService extends SecurityAuthFrameworkService { */ String login(@Valid AppAuthLoginReqVO reqVO, String userIp, String userAgent); + /** + * 基于 token 退出登录 + * + * @param token token + */ + void logout(String token); + /** * 手机 + 验证码登陆 * diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index 34b0c8772..87baf2416 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -12,7 +12,6 @@ public interface ErrorCodeConstants { // ========== AUTH 模块 1002000000 ========== ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确"); ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用"); - ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登录失败的兜底,未知原因 ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在"); ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确"); ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定"); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java index 77232d397..8a93e645c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java @@ -26,8 +26,6 @@ public interface AuthConvert { SpringSecurityUser convert2(AdminUserDO user); - LoginUser convert(SpringSecurityUser bean); - default AuthPermissionInfoRespVO convert(AdminUserDO user, List roleList, List menuList) { return AuthPermissionInfoRespVO.builder() .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build()) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java index 84f742207..389e1a835 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java @@ -1,14 +1,14 @@ package cn.iocoder.yudao.module.system.service.auth; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService; +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; import javax.validation.Valid; /** * 管理后台的认证 Service 接口 * - * 提供用户的账号密码登录、token 的校验等认证相关的功能 + * 提供用户的登录、登出的能力 * * @author 芋道源码 */ @@ -24,6 +24,13 @@ public interface AdminAuthService extends SecurityAuthFrameworkService { */ String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent); + /** + * 基于 token 退出登录 + * + * @param token token + */ + void logout(String token); + /** * 短信验证码发送 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index 475966403..a0c6989d1 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -1,12 +1,12 @@ package cn.iocoder.yudao.module.system.service.auth; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; -import cn.iocoder.yudao.framework.security.core.authentication.SpringSecurityUser; import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; @@ -19,18 +19,11 @@ import cn.iocoder.yudao.module.system.service.common.CaptchaService; import cn.iocoder.yudao.module.system.service.logger.LoginLogService; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; +import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.DisabledException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import org.springframework.util.Assert; import javax.annotation.Resource; import javax.validation.Validator; @@ -50,11 +43,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; public class AdminAuthServiceImpl implements AdminAuthService { @Resource - @Lazy // 延迟加载,因为存在相互依赖的问题 - private AuthenticationManager authenticationManager; - - @Autowired - @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") // UserService 存在重名 private AdminUserService userService; @Resource private CaptchaService captchaService; @@ -71,17 +59,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Resource private SmsCodeApi smsCodeApi; - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - // 获取 username 对应的 AdminUserDO - AdminUserDO user = userService.getUserByUsername(username); - if (user == null) { - throw new UsernameNotFoundException(username); - } - // 创建 LoginUser 对象 - return AuthConvert.INSTANCE.convert2(user); - } - @Override public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { // 判断验证码是否正确 @@ -124,7 +101,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { LoginLogTypeEnum.LOGIN_MOBILE, userIp, userAgent); } - private void verifyCaptcha(AuthLoginReqVO reqVO) { + @VisibleForTesting + void verifyCaptcha(AuthLoginReqVO reqVO) { // 如果验证码关闭,则不进行校验 if (!captchaService.isCaptchaEnable()) { return; @@ -149,46 +127,36 @@ public class AdminAuthServiceImpl implements AdminAuthService { captchaService.deleteCaptchaCode(reqVO.getUuid()); } - private LoginUser login0(String username, String password) { + @VisibleForTesting + LoginUser login0(String username, String password) { final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; - // 用户验证 - Authentication authentication; - try { - // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证 - // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息 - authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken( - username, password, getUserType())); - } catch (BadCredentialsException badCredentialsException) { + // 校验账号是否存在 + AdminUserDO user = userService.getUserByUsername(username); + if (user == null) { createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); throw exception(AUTH_LOGIN_BAD_CREDENTIALS); - } catch (DisabledException disabledException) { - createLoginLog(null, username, logTypeEnum, LoginResultEnum.USER_DISABLED); - throw exception(AUTH_LOGIN_USER_DISABLED); - } catch (AuthenticationException authenticationException) { - log.error("[login0][username({}) 发生未知异常]", username, authenticationException); - createLoginLog(null, username, logTypeEnum, LoginResultEnum.UNKNOWN_ERROR); - throw exception(AUTH_LOGIN_FAIL_UNKNOWN); } - Assert.notNull(authentication.getPrincipal(), "Principal 不会为空"); + if (!userService.isPasswordMatch(password, user.getPassword())) { + createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); + throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + } + // 校验是否禁用 + if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { + createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED); + throw exception(AUTH_LOGIN_USER_DISABLED); + } + // 构建 User 对象 - return AuthConvert.INSTANCE.convert((SpringSecurityUser) authentication.getPrincipal()) - .setUserType(getUserType().getValue()); + return buildLoginUser(user); } private void createLoginLog(Long userId, String username, LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) { - // 获得用户 - if (userId == null) { - AdminUserDO user = userService.getUserByUsername(username); - userId = user != null ? user.getId() : null; - } // 插入登录日志 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); reqDTO.setLogType(logTypeEnum.getType()); reqDTO.setTraceId(TracerUtils.getTraceId()); - if (userId != null) { - reqDTO.setUserId(userId); - } + reqDTO.setUserId(userId); reqDTO.setUserType(getUserType().getValue()); reqDTO.setUsername(username); reqDTO.setUserAgent(ServletUtils.getUserAgent()); @@ -293,4 +261,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { return user != null ? user.getUsername() : null; } + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return null; + } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java index d6a836f01..ae3245de2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java @@ -105,7 +105,6 @@ public interface AdminUserService { */ AdminUserDO getUserByMobile(String mobile); - /** * 获得用户分页列表 * @@ -209,4 +208,13 @@ public interface AdminUserService { */ List getUsersByStatus(Integer status); + /** + * 判断密码是否匹配 + * + * @param rawPassword 未加密的密码 + * @param encodedPassword 加密后的密码 + * @return 是否匹配 + */ + boolean isPasswordMatch(String rawPassword, String encodedPassword); + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index 9208b2e69..57fd49a7d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -148,7 +148,7 @@ public class AdminUserServiceImpl implements AdminUserService { checkOldPassword(id, reqVO.getOldPassword()); // 执行更新 AdminUserDO updateObj = new AdminUserDO().setId(id); - updateObj.setPassword(passwordEncoder.encode(reqVO.getNewPassword())); // 加密密码 + updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码 userMapper.updateById(updateObj); } @@ -172,7 +172,7 @@ public class AdminUserServiceImpl implements AdminUserService { // 更新密码 AdminUserDO updateObj = new AdminUserDO(); updateObj.setId(id); - updateObj.setPassword(passwordEncoder.encode(password)); // 加密密码 + updateObj.setPassword(encodePassword(password)); // 加密密码 userMapper.updateById(updateObj); } @@ -205,11 +205,6 @@ public class AdminUserServiceImpl implements AdminUserService { return userMapper.selectByUsername(username); } - /** - * 通过手机号获取用户 - * @param mobile - * @return - */ @Override public AdminUserDO getUserByMobile(String mobile) { return userMapper.selectByMobile(mobile); @@ -395,7 +390,7 @@ public class AdminUserServiceImpl implements AdminUserService { if (user == null) { throw exception(USER_NOT_EXISTS); } - if (!passwordEncoder.matches(oldPassword, user.getPassword())) { + if (!isPasswordMatch(oldPassword, user.getPassword())) { throw exception(USER_PASSWORD_FAILED); } } @@ -421,7 +416,7 @@ public class AdminUserServiceImpl implements AdminUserService { AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername()); if (existUser == null) { userMapper.insert(UserConvert.INSTANCE.convert(importUser) - .setPassword(passwordEncoder.encode(userInitPassword))); // 设置默认密码 + .setPassword(encodePassword(userInitPassword))); // 设置默认密码 respVO.getCreateUsernames().add(importUser.getUsername()); return; } @@ -443,4 +438,19 @@ public class AdminUserServiceImpl implements AdminUserService { return userMapper.selectListByStatus(status); } + @Override + public boolean isPasswordMatch(String rawPassword, String encodedPassword) { + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 对密码进行加密 + * + * @param password 密码 + * @return 加密后的密码 + */ + private String encodePassword(String password) { + return passwordEncoder.encode(password); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java index e13570a2b..762928f0a 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.system.service.auth; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.framework.test.core.util.AssertUtils; @@ -9,7 +10,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.service.common.CaptchaService; -import cn.iocoder.yudao.module.system.service.dept.PostService; import cn.iocoder.yudao.module.system.service.logger.LoginLogService; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; @@ -17,23 +17,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.DisabledException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import javax.annotation.Resource; import javax.validation.Validator; -import java.util.Set; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -46,10 +39,6 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @MockBean private AdminUserService userService; @MockBean - private AuthenticationManager authenticationManager; - @MockBean - private Authentication authentication; - @MockBean private CaptchaService captchaService; @MockBean private LoginLogService loginLogService; @@ -58,8 +47,6 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @MockBean private SocialUserService socialService; @MockBean - private PostService postService; - @MockBean private SmsCodeApi smsCodeApi; @MockBean @@ -71,40 +58,102 @@ public class AuthServiceImplTest extends BaseDbUnitTest { } @Test - public void testLoadUserByUsername_success() { + public void testLogin0_success() { // 准备参数 String username = randomString(); - // mock 方法 - AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)); + String password = randomString(); + // mock user 数据 + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username) + .setPassword(password).setStatus(CommonStatusEnum.ENABLE.getStatus())); when(userService.getUserByUsername(eq(username))).thenReturn(user); + // mock password 匹配 + when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); // 调用 - LoginUser loginUser = (LoginUser) authService.loadUserByUsername(username); + LoginUser loginUser = authService.login0(username, password); // 校验 - AssertUtils.assertPojoEquals(user, loginUser, "updateTime"); + assertPojoEquals(user, loginUser); } @Test - public void testLoadUserByUsername_userNotFound() { + public void testLogin0_userNotFound() { // 准备参数 String username = randomString(); - // mock 方法 + String password = randomString(); // 调用, 并断言异常 - assertThrows(UsernameNotFoundException.class, // 抛出 UsernameNotFoundException 异常 - () -> authService.loadUserByUsername(username), - username); // 异常提示为 username + AssertUtils.assertServiceException(() -> authService.login0(username, password), + AUTH_LOGIN_BAD_CREDENTIALS); + verify(loginLogService).createLoginLog( + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) + && o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult()) + && o.getUserId() == null) + ); } @Test - public void testLogin_captchaNotFound() { + public void testLogin0_badCredentials() { // 准备参数 - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); - String userIp = randomString(); - String userAgent = randomString(); + String username = randomString(); + String password = randomString(); + // mock user 数据 + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username) + .setPassword(password).setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(userService.getUserByUsername(eq(username))).thenReturn(user); // 调用, 并断言异常 - assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_NOT_FOUND); + AssertUtils.assertServiceException(() -> authService.login0(username, password), + AUTH_LOGIN_BAD_CREDENTIALS); + verify(loginLogService).createLoginLog( + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) + && o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult()) + && o.getUserId().equals(user.getId())) + ); + } + + @Test + public void testLogin0_userDisabled() { + // 准备参数 + String username = randomString(); + String password = randomString(); + // mock user 数据 + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username) + .setPassword(password).setStatus(CommonStatusEnum.DISABLE.getStatus())); + when(userService.getUserByUsername(eq(username))).thenReturn(user); + // mock password 匹配 + when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); + + // 调用, 并断言异常 + AssertUtils.assertServiceException(() -> authService.login0(username, password), + AUTH_LOGIN_USER_DISABLED); + verify(loginLogService).createLoginLog( + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) + && o.getResult().equals(LoginResultEnum.USER_DISABLED.getResult()) + && o.getUserId().equals(user.getId())) + ); + } + + @Test + public void testCaptcha_success() { + // 准备参数 + AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); + + // mock 验证码正确 + when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); + + // 调用 + authService.verifyCaptcha(reqVO); + // 断言 + verify(captchaService).deleteCaptchaCode(reqVO.getUuid()); + } + + @Test + public void testCaptcha_notFound() { + // 准备参数 + AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_NOT_FOUND); // 校验调用参数 verify(loginLogService, times(1)).createLoginLog( argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) @@ -113,10 +162,8 @@ public class AuthServiceImplTest extends BaseDbUnitTest { } @Test - public void testLogin_captchaCodeError() { + public void testCaptcha_codeError() { // 准备参数 - String userIp = randomString(); - String userAgent = randomString(); AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); // mock 验证码不正确 @@ -124,109 +171,45 @@ public class AuthServiceImplTest extends BaseDbUnitTest { when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code); // 调用, 并断言异常 - assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_CODE_ERROR); + assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR); // 校验调用参数 - verify(loginLogService, times(1)).createLoginLog( + verify(loginLogService).createLoginLog( argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult())) ); } - @Test - public void testLogin_badCredentials() { - // 准备参数 - String userIp = randomString(); - String userAgent = randomString(); - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); - // mock 验证码正确 - when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); - // mock 抛出异常 - when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword()))) - .thenThrow(new BadCredentialsException("测试账号或密码不正确")); - - // 调用, 并断言异常 - assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_BAD_CREDENTIALS); - // 校验调用参数 - verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid()); - verify(loginLogService, times(1)).createLoginLog( - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) - && o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult())) - ); - } - - @Test - public void testLogin_userDisabled() { - // 准备参数 - String userIp = randomString(); - String userAgent = randomString(); - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); - - // mock 验证码正确 - when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); - // mock 抛出异常 - when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword()))) - .thenThrow(new DisabledException("测试用户被禁用")); - - // 调用, 并断言异常 - assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_USER_DISABLED); - // 校验调用参数 - verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid()); - verify(loginLogService, times(1)).createLoginLog( - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) - && o.getResult().equals(LoginResultEnum.USER_DISABLED.getResult())) - ); - } - - @Test - public void testLogin_unknownError() { - // 准备参数 - String userIp = randomString(); - String userAgent = randomString(); - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); - // mock 验证码正确 - when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); - // mock 抛出异常 - when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword()))) - .thenThrow(new AuthenticationException("测试未知异常") {}); - - // 调用, 并断言异常 - assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_FAIL_UNKNOWN); - // 校验调用参数 - verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid()); - verify(loginLogService, times(1)).createLoginLog( - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) - && o.getResult().equals(LoginResultEnum.UNKNOWN_ERROR.getResult())) - ); - } - @Test public void testLogin_success() { // 准备参数 String userIp = randomString(); String userAgent = randomString(); - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); + AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> + o.setUsername("test_username").setPassword("test_password")); // mock 验证码正确 when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); - // mock authentication - Long userId = randomLongId(); - Set userRoleIds = randomSet(Long.class); - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(userId)); - when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword()))) - .thenReturn(authentication); - when(authentication.getPrincipal()).thenReturn(loginUser); + // mock user 数据 + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername("test_username") + .setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(userService.getUserByUsername(eq("test_username"))).thenReturn(user); + // mock password 匹配 + when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true); // mock 缓存登录用户到 Redis String token = randomString(); - when(userSessionService.createUserSession(loginUser, userIp, userAgent)).thenReturn(token); + when(userSessionService.createUserSession(argThat(argument -> { + AssertUtils.assertPojoEquals(user, argument); + return true; + }), eq(userIp), eq(userAgent))).thenReturn(token); // 调用, 并断言异常 - String login = authService.login(reqVO, userIp, userAgent); - assertEquals(token, login); + String result = authService.login(reqVO, userIp, userAgent); + assertEquals(token, result); // 校验调用参数 - verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid()); - verify(loginLogService, times(1)).createLoginLog( + verify(loginLogService).createLoginLog( argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) - && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())) + && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()) + && o.getUserId().equals(user.getId())) ); } From 5e8648508e11fa7fd72066396078944c24a18669 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 May 2022 02:33:34 +0800 Subject: [PATCH 10/21] =?UTF-8?q?=E5=8E=BB=E9=99=A4=20Spring=20Security=20?= =?UTF-8?q?=E7=9A=84=20Member=20=E7=9A=84=20loadUsername=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E8=87=AA=E5=B7=B1=E5=AE=9A=E4=B9=89=E7=9A=84=20login0?= =?UTF-8?q?=20=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../YudaoSecurityAutoConfiguration.java | 19 +-- .../YudaoWebSecurityConfigurerAdapter.java | 18 +-- ...ultiUserDetailsAuthenticationProvider.java | 128 ------------------ ...tiUsernamePasswordAuthenticationToken.java | 43 ------ .../authentication/SpringSecurityUser.java | 78 ----------- .../filter/TokenAuthenticationFilter.java | 5 +- .../member/enums/ErrorCodeConstants.java | 1 - .../app/auth/AppAuthController.http | 2 +- .../member/convert/auth/AuthConvert.java | 7 - .../service/auth/MemberAuthService.java | 3 +- .../service/auth/MemberAuthServiceImpl.java | 88 ++++-------- .../service/user/MemberUserService.java | 9 ++ .../service/user/MemberUserServiceImpl.java | 17 ++- .../service/auth/MemberAuthServiceTest.java | 5 +- .../system/convert/auth/AuthConvert.java | 3 - .../system/service/auth/AdminAuthService.java | 2 +- .../service/auth/AdminAuthServiceImpl.java | 18 +-- 17 files changed, 63 insertions(+), 383 deletions(-) delete mode 100644 yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/SpringSecurityUser.java diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java index af728045c..087edc32f 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java @@ -1,13 +1,10 @@ package cn.iocoder.yudao.framework.security.config; import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect; -import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider; import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy; import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter; import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl; import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl; -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService; -import cn.iocoder.yudao.framework.web.config.WebProperties; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -20,7 +17,6 @@ import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import javax.annotation.Resource; -import java.util.List; /** * Spring Security 自动配置类,主要用于相关组件的配置 @@ -76,19 +72,8 @@ public class YudaoSecurityAutoConfiguration { * Token 认证过滤器 Bean */ @Bean - public TokenAuthenticationFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider, - GlobalExceptionHandler globalExceptionHandler) { - return new TokenAuthenticationFilter(securityProperties, authenticationProvider, globalExceptionHandler); - } - - /** - * 身份验证的 Provider Bean,通过它实现账号 + 密码的认证 - */ - @Bean - public MultiUserDetailsAuthenticationProvider authenticationProvider( - List securityFrameworkServices, - WebProperties webProperties, PasswordEncoder passwordEncoder) { - return new MultiUserDetailsAuthenticationProvider(securityFrameworkServices, webProperties, passwordEncoder); + public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler) { + return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java index c0bc2a056..61edba6e8 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.framework.security.config; -import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider; import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter; import cn.iocoder.yudao.framework.web.config.WebProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -8,7 +7,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @@ -32,8 +30,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap @Resource private WebProperties webProperties; - @Resource - private MultiUserDetailsAuthenticationProvider authenticationProvider; /** * 认证失败处理类 Bean */ @@ -69,14 +65,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap return super.authenticationManagerBean(); } - /** - * 身份认证接口 - */ - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider(authenticationProvider); - } - /** * 配置 URL 的安全配置 * @@ -130,11 +118,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap // 添加 JWT Filter httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } - - private String buildAdminApi(String url) { - return webProperties.getAdminApi().getPrefix() + url; - } - + private String buildAppApi(String url) { return webProperties.getAppApi().getPrefix() + url; } diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java deleted file mode 100644 index 416e196d6..000000000 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java +++ /dev/null @@ -1,128 +0,0 @@ -package cn.iocoder.yudao.framework.security.core.authentication; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService; -import cn.iocoder.yudao.framework.web.config.WebProperties; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.crypto.password.PasswordEncoder; - -import javax.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 支持多用户类型的 AuthenticationProvider 实现类 - * - * 为什么不用 {@link org.springframework.security.authentication.ProviderManager} 呢? - * 原因是,需要每个用户类型实现对应的 {@link AuthenticationProvider} + authentication,略显麻烦。实际,也是可以实现的。 - * - * 另外,额外支持 verifyTokenAndRefresh 校验令牌、logout 登出、mockLogin 模拟登陆等操作。 - * 实际上,它就是 {@link SecurityAuthFrameworkService} 定义的三个接口。 - * 因为需要支持多种类型,所以需要根据请求的 URL,判断出对应的用户类型,从而使用对应的 SecurityAuthFrameworkService 是吸纳 - * - * @see cn.iocoder.yudao.framework.common.enums.UserTypeEnum - * @author 芋道源码 - */ -public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { - - private final Map services = new HashMap<>(); - - private final WebProperties properties; - - private final PasswordEncoder passwordEncoder; - - public MultiUserDetailsAuthenticationProvider(List serviceList, - WebProperties properties, PasswordEncoder passwordEncoder) { - serviceList.forEach(service -> services.put(service.getUserType(), service)); - this.properties = properties; - this.passwordEncoder = passwordEncoder; - } - - // ========== AuthenticationProvider 相关 ========== - - @Override - protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) - throws AuthenticationException { - // 执行用户的加载 - return selectService(authentication).loadUserByUsername(username); - } - - private SecurityAuthFrameworkService selectService(UsernamePasswordAuthenticationToken authentication) { - // 第一步,获得用户类型 - UserTypeEnum userType = getUserType(authentication); - // 第二步,获得 SecurityAuthFrameworkService - SecurityAuthFrameworkService service = services.get(userType); - Assert.notNull(service, "用户类型({}) 找不到 SecurityAuthFrameworkService 实现类", userType); - return service; - } - - private UserTypeEnum getUserType(UsernamePasswordAuthenticationToken authentication) { - Assert.isInstanceOf(MultiUsernamePasswordAuthenticationToken.class, authentication); - MultiUsernamePasswordAuthenticationToken multiAuthentication = (MultiUsernamePasswordAuthenticationToken) authentication; - UserTypeEnum userType = multiAuthentication.getUserType(); - Assert.notNull(userType, "用户类型不能为空"); - return userType; - } - - @Override // copy 自 DaoAuthenticationProvider 的 additionalAuthenticationChecks 方法 - protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) - throws AuthenticationException { - // 校验 credentials - if (authentication.getCredentials() == null) { - this.logger.debug("Failed to authenticate since no credentials provided"); - throw new BadCredentialsException(this.messages.getMessage( - "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); - } - // 校验 password - String presentedPassword = authentication.getCredentials().toString(); - if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { - this.logger.debug("Failed to authenticate since password does not match stored value"); - throw new BadCredentialsException(this.messages.getMessage( - "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); - } - } - - // ========== SecurityAuthFrameworkService 相关 ========== - - /** - * 校验 token 的有效性,并获取用户信息 - * 通过后,刷新 token 的过期时间 - * - * @param request 请求 - * @param token token - * @return 用户信息 - */ - public LoginUser verifyTokenAndRefresh(HttpServletRequest request, String token) { - return selectService(request).verifyTokenAndRefresh(token); - } - - private SecurityAuthFrameworkService selectService(HttpServletRequest request) { - // 第一步,获得用户类型 - UserTypeEnum userType = getUserType(request); - // 第二步,获得 SecurityAuthFrameworkService - SecurityAuthFrameworkService service = services.get(userType); - Assert.notNull(service, "URI({}) 用户类型({}) 找不到 SecurityAuthFrameworkService 实现类", - request.getRequestURI(), userType); - return service; - } - - private UserTypeEnum getUserType(HttpServletRequest request) { - if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) { - return UserTypeEnum.ADMIN; - } - if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) { - return UserTypeEnum.MEMBER; - } - throw new IllegalArgumentException(StrUtil.format("URI({}) 找不到匹配的用户类型", request.getRequestURI())); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java deleted file mode 100644 index f0bc8dfac..000000000 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java +++ /dev/null @@ -1,43 +0,0 @@ -package cn.iocoder.yudao.framework.security.core.authentication; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import lombok.Getter; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; - -import java.util.Collection; - -/** - * 支持多用户的 UsernamePasswordAuthenticationToken 实现类 - * - * @author 芋道源码 - */ -@Getter -public class MultiUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken { - - /** - * 用户类型 - */ - private UserTypeEnum userType; - - public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials) { - super(principal, credentials); - } - - public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials, - Collection authorities) { - super(principal, credentials, authorities); - } - - public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials, UserTypeEnum userType) { - super(principal, credentials); - this.userType = userType; - } - - public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials, - Collection authorities, UserTypeEnum userType) { - super(principal, credentials, authorities); - this.userType = userType; - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/SpringSecurityUser.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/SpringSecurityUser.java deleted file mode 100644 index 67b0c5ea6..000000000 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/SpringSecurityUser.java +++ /dev/null @@ -1,78 +0,0 @@ -package cn.iocoder.yudao.framework.security.core.authentication; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import java.util.Collection; -import java.util.Collections; - -/** - * 登录用户信息 - * - * @author 芋道源码 - */ -@Data -@AllArgsConstructor -public class SpringSecurityUser implements UserDetails { - - /** - * 用户编号 - */ - private Long id; - - /** - * 用户名 - */ - private String username; - /** - * 密码 - */ - private String password; - /** - * 状态 - */ - private Integer status; - /** - * 租户编号 - */ - private Long tenantId; - - @Override - public String getPassword() { - return password; - } - - @Override - public String getUsername() { - return username; - } - - @Override - public boolean isEnabled() { - return CommonStatusEnum.ENABLE.getStatus().equals(status); - } - - @Override - public Collection getAuthorities() { - return Collections.emptyList(); - } - - @Override - public boolean isAccountNonExpired() { - return true; // 返回 true,不依赖 Spring Security 判断 - } - - @Override - public boolean isAccountNonLocked() { - return true; // 返回 true,不依赖 Spring Security 判断 - } - - @Override - public boolean isCredentialsNonExpired() { - return true; // 返回 true,不依赖 Spring Security 判断 - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java index cf0ee7a23..b27863f60 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java @@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.security.config.SecurityProperties; import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; @@ -29,8 +28,6 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { private final SecurityProperties securityProperties; - private final MultiUserDetailsAuthenticationProvider authenticationProvider; - private final GlobalExceptionHandler globalExceptionHandler; @Override @@ -42,7 +39,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { Integer userType = WebFrameworkUtils.getLoginUserType(request); try { // 验证 token 有效性 - LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token); + LoginUser loginUser = null; // TODO 芋艿:待实现 // 模拟 Login 功能,方便日常开发调试 if (loginUser == null) { loginUser = mockLoginUser(request, token, userType); diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java index 9970c1ade..8b4380ca1 100644 --- a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java +++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java @@ -17,7 +17,6 @@ public interface ErrorCodeConstants { // ========== AUTH 模块 1004003000 ========== ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确"); ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用"); - ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1004003002, "登录失败"); // 登录失败的兜底,未知原因 ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期"); ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定"); diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http index 7a10c7754..210514699 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http @@ -1,5 +1,5 @@ ### 请求 /login 接口 => 成功 -POST {{appApi}}/member/login +POST {{appApi}}/member/auth/login Content-Type: application/json tenant-id: {{appTenentId}} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java index f33f85e70..9100c16e1 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.member.convert.auth; import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.framework.security.core.authentication.SpringSecurityUser; import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; @@ -11,7 +10,6 @@ import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO; import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; @Mapper @@ -21,11 +19,6 @@ public interface AuthConvert { LoginUser convert(MemberUserDO bean); - @Mapping(source = "mobile", target = "username") - SpringSecurityUser convert2(MemberUserDO user); - - LoginUser convert(SpringSecurityUser bean); - SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO); SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO); SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO); diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java index f7abd8622..6ded902c8 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.member.service.auth; -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService; import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; import javax.validation.Valid; @@ -12,7 +11,7 @@ import javax.validation.Valid; * * @author 芋道源码 */ -public interface MemberAuthService extends SecurityAuthFrameworkService { +public interface MemberAuthService { /** * 手机 + 密码登录 diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java index c58a6d74d..efcc7be5d 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java @@ -1,11 +1,12 @@ package cn.iocoder.yudao.module.member.service.auth; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; import cn.iocoder.yudao.module.member.convert.auth.AuthConvert; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; @@ -21,14 +22,6 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Lazy; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.DisabledException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -49,10 +42,6 @@ import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*; @Slf4j public class MemberAuthServiceImpl implements MemberAuthService { - @Resource - @Lazy // 延迟加载,因为存在相互依赖的问题 - private AuthenticationManager authenticationManager; - @Resource private MemberUserService userService; @Resource @@ -69,17 +58,6 @@ public class MemberAuthServiceImpl implements MemberAuthService { @Resource private MemberUserMapper userMapper; - @Override - public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException { - // 获取 username 对应的 SysUserDO - MemberUserDO user = userService.getUserByMobile(mobile); - if (user == null) { - throw new UsernameNotFoundException(mobile); - } - // 创建 LoginUser 对象 - return AuthConvert.INSTANCE.convert2(user); - } - @Override public String login(AppAuthLoginReqVO reqVO, String userIp, String userAgent) { // 使用手机 + 密码,进行登录。 @@ -157,43 +135,34 @@ public class MemberAuthServiceImpl implements MemberAuthService { return socialUserApi.getAuthorizeUrl(type, redirectUri); } - private LoginUser login0(String username, String password) { - final LoginLogTypeEnum logType = LoginLogTypeEnum.LOGIN_USERNAME; - // 用户验证 - Authentication authentication; - try { - // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证 - // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息 - authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken( - username, password, getUserType())); - } catch (BadCredentialsException badCredentialsException) { - this.createLoginLog(null, username, logType, LoginResultEnum.BAD_CREDENTIALS); + private LoginUser login0(String mobile, String password) { + final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_MOBILE; + // 校验账号是否存在 + MemberUserDO user = userService.getUserByMobile(mobile); + if (user == null) { + createLoginLog(null, mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); throw exception(AUTH_LOGIN_BAD_CREDENTIALS); - } catch (DisabledException disabledException) { - this.createLoginLog(null, username, logType, LoginResultEnum.USER_DISABLED); - throw exception(AUTH_LOGIN_USER_DISABLED); - } catch (AuthenticationException authenticationException) { - log.error("[login0][username({}) 发生未知异常]", username, authenticationException); - this.createLoginLog(null, username, logType, LoginResultEnum.UNKNOWN_ERROR); - throw exception(AUTH_LOGIN_FAIL_UNKNOWN); } - Assert.notNull(authentication.getPrincipal(), "Principal 不会为空"); - return (LoginUser) authentication.getPrincipal(); + if (!userService.isPasswordMatch(password, user.getPassword())) { + createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); + throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + } + // 校验是否禁用 + if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { + createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.USER_DISABLED); + throw exception(AUTH_LOGIN_USER_DISABLED); + } + + // 构建 User 对象 + return buildLoginUser(user); } private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) { - // 获得用户 - if (userId == null) { - MemberUserDO user = userService.getUserByMobile(mobile); - userId = user != null ? user.getId() : null; - } // 插入登录日志 LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); reqDTO.setLogType(logType.getType()); reqDTO.setTraceId(TracerUtils.getTraceId()); - if (userId != null) { - reqDTO.setUserId(userId); - } + reqDTO.setUserId(userId); reqDTO.setUserType(getUserType().getValue()); reqDTO.setUsername(mobile); reqDTO.setUserAgent(ServletUtils.getUserAgent()); @@ -206,11 +175,6 @@ public class MemberAuthServiceImpl implements MemberAuthService { } } - @Override - public LoginUser verifyTokenAndRefresh(String token) { - return userSessionApi.getLoginUser(token); - } - @Override public void logout(String token) { // 查询用户信息 @@ -224,17 +188,13 @@ public class MemberAuthServiceImpl implements MemberAuthService { createLogoutLog(loginUser.getId()); } - @Override - public UserTypeEnum getUserType() { - return UserTypeEnum.MEMBER; - } - @Override public void updatePassword(Long userId, AppAuthUpdatePasswordReqVO reqVO) { // 检验旧密码 MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword()); // 更新用户密码 + // TODO 芋艿:需要重构到用户模块 userMapper.updateById(MemberUserDO.builder().id(userDO.getId()) .password(passwordEncoder.encode(reqVO.getPassword())).build()); } @@ -312,4 +272,8 @@ public class MemberAuthServiceImpl implements MemberAuthService { return user != null ? user.getMobile() : null; } + private UserTypeEnum getUserType() { + return UserTypeEnum.MEMBER; + } + } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java index 3c0e62de8..3b63c8d71 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java @@ -69,4 +69,13 @@ public interface MemberUserService { */ void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO); + /** + * 判断密码是否匹配 + * + * @param rawPassword 未加密的密码 + * @param encodedPassword 加密后的密码 + * @return 是否匹配 + */ + boolean isPasswordMatch(String rawPassword, String encodedPassword); + } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java index 0522e8eda..ff91b0781 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java @@ -69,7 +69,7 @@ public class MemberUserServiceImpl implements MemberUserService { MemberUserDO user = new MemberUserDO(); user.setMobile(mobile); user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启 - user.setPassword(passwordEncoder.encode(password)); // 加密密码 + user.setPassword(encodePassword(password)); // 加密密码 user.setRegisterIp(registerIp); memberUserMapper.insert(user); return user; @@ -127,6 +127,21 @@ public class MemberUserServiceImpl implements MemberUserService { memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build()); } + @Override + public boolean isPasswordMatch(String rawPassword, String encodedPassword) { + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 对密码进行加密 + * + * @param password 密码 + * @return 加密后的密码 + */ + private String encodePassword(String password) { + return passwordEncoder.encode(password); + } + @VisibleForTesting public MemberUserDO checkUserExists(Long id) { if (id == null) { diff --git a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java b/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java index a982f9625..a3a43b2d2 100644 --- a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java +++ b/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java @@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.system.api.social.SocialUserApi; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; -import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.crypto.password.PasswordEncoder; import javax.annotation.Resource; @@ -38,8 +37,8 @@ import static org.mockito.Mockito.when; @Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class}) public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest { - @MockBean - private AuthenticationManager authenticationManager; + // TODO @芋艿:登录相关的单测,待补全 + @MockBean private MemberUserService userService; @MockBean diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java index 8a93e645c..fdea4063f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.convert.auth; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.framework.security.core.authentication.SpringSecurityUser; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; @@ -24,8 +23,6 @@ public interface AuthConvert { LoginUser convert(AdminUserDO bean); - SpringSecurityUser convert2(AdminUserDO user); - default AuthPermissionInfoRespVO convert(AdminUserDO user, List roleList, List menuList) { return AuthPermissionInfoRespVO.builder() .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build()) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java index 389e1a835..cf01334e8 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java @@ -12,7 +12,7 @@ import javax.validation.Valid; * * @author 芋道源码 */ -public interface AdminAuthService extends SecurityAuthFrameworkService { +public interface AdminAuthService { /** * 账号登录 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index a0c6989d1..d049d9edb 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -21,8 +21,6 @@ import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -226,11 +224,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { createLogoutLog(loginUser.getId()); } - @Override - public UserTypeEnum getUserType() { - return UserTypeEnum.ADMIN; - } - private void createLogoutLog(Long userId) { LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType()); @@ -244,11 +237,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { loginLogService.createLoginLog(reqDTO); } - @Override - public LoginUser verifyTokenAndRefresh(String token) { - return userSessionService.getLoginUser(token); - } - private LoginUser buildLoginUser(AdminUserDO user) { return AuthConvert.INSTANCE.convert(user).setUserType(getUserType().getValue()); } @@ -261,8 +249,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { return user != null ? user.getUsername() : null; } - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - return null; + private UserTypeEnum getUserType() { + return UserTypeEnum.ADMIN; } + } From ebee4ddb7c424d88b24ce4279b02447a8ba83987 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 May 2022 17:43:24 +0800 Subject: [PATCH 11/21] =?UTF-8?q?=E5=88=B6=E5=AE=9A=20OAuth2=20=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=E7=9A=84=E8=A1=A8=E7=BB=93=E6=9E=84=E4=B8=8E=20API=20?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/system/api/auth/OAuth2Api.java | 12 ++++ .../controller/admin/auth/AuthController.java | 2 +- .../admin/auth/OAuth2Controller.java | 24 +++++++ .../dataobject/auth/OAuth2AccessTokenDO.java | 15 ++--- .../dataobject/auth/OAuth2ApplicationDO.java | 65 +++++++++++++++++++ .../dal/dataobject/auth/OAuth2CodeDO.java | 62 ++++++++++++++++++ .../dal/dataobject/auth/UserSessionDO.java | 1 + .../system/service/auth/AdminAuthService.java | 1 - .../service/auth/AdminOAuth2Service.java | 14 ++++ .../service/auth/OAuth2CodeService.java | 11 ++++ .../service/auth/OAuth2ServiceImpl.java | 4 +- ...h2Service.java => OAuth2TokenService.java} | 6 +- 12 files changed, 203 insertions(+), 14 deletions(-) create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2Controller.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminOAuth2Service.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2CodeService.java rename yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/{OAuth2Service.java => OAuth2TokenService.java} (69%) diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java new file mode 100644 index 000000000..973466e55 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java @@ -0,0 +1,12 @@ +package cn.iocoder.yudao.module.system.api.auth; + +/** + * OAuth2.0 API 接口 + * + * @author 芋道源码 + */ +public interface OAuth2Api { + + + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 2b494a9a1..ce9e58e8d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -40,7 +40,7 @@ import static java.util.Collections.singleton; @Api(tags = "管理后台 - 认证") @RestController -@RequestMapping("/system/auth") // 暂时不跟 /auth 结尾 +@RequestMapping("/system/auth") @Validated @Slf4j public class AuthController { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2Controller.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2Controller.java new file mode 100644 index 000000000..d82731aab --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2Controller.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.system.controller.admin.auth; + +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Api(tags = "管理后台 - OAuth2.0 授权") +@RestController +@RequestMapping("/system/oauth2") +@Validated +@Slf4j +public class OAuth2Controller { + +// POST oauth/token TokenEndpoint:Password、Implicit、Code、Refresh Token + +// POST oauth/check_token CheckTokenEndpoint + +// DELETE oauth/token ConsumerTokenServices#revokeToken + +// GET oauth/authorize AuthorizationEndpoint + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java index 63e251335..b8a63c1e1 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java @@ -10,8 +10,9 @@ import lombok.experimental.Accessors; import java.util.Date; /** - * OAuth2 访问令牌 + * OAuth2 访问令牌 DO * + * @author 芋道源码 */ @TableName("system_oauth2_access_token") @Data @@ -20,7 +21,7 @@ import java.util.Date; public class OAuth2AccessTokenDO extends BaseDO { /** - * 编号,数据库字典 + * 编号,数据库递增 */ private Long id; /** @@ -38,18 +39,14 @@ public class OAuth2AccessTokenDO extends BaseDO { */ private Integer userType; /** - * 刷新令牌 + * 应用编号 * - * 关联 {@link OAuth2RefreshTokenDO#getRefreshToken()} + * 关联 {@link OAuth2ApplicationDO#getId()} */ - private String refreshToken; + private Long applicationId; /** * 过期时间 */ private Date expiresTime; - /** - * 创建 IP - */ - private String createIp; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java new file mode 100644 index 000000000..20a2b6087 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.system.dal.dataobject.auth; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * OAuth2 客户端 DO + * + * 为什么不使用 Client 作为表名? + * 1. clientId 字段被占用,导致表的 id 无法有合适的缩写 + * 2. 大多数 Github、Gitee 等平台,都会习惯称为第三方接入应用 + * + * 如下字段,考虑到使用相对不是很高频,主要是一些开关,暂时不支持: + * authorized_grant_types、authorities、access_token_validity、refresh_token_validity、additional_information、autoapprove、resource_ids、scope + * + * @author 芋道源码 + */ +@TableName("system_oauth2_application") +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class OAuth2ApplicationDO extends BaseDO { + + /** + * 编号,数据库递增 + */ + private Long id; + /** + * 客户端编号 + */ + private String clientId; + /** + * 客户端密钥 + */ + private String clientSecret; + /** + * 可重定向的 URI 地址 + */ + private List redirectUris; + /** + * 应用名 + */ + private String name; + /** + * 应用图标 + */ + private String logo; + /** + * 应用描述 + */ + private String description; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java new file mode 100644 index 000000000..293bd0a41 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.system.dal.dataobject.auth; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * OAuth2 授权码 DO + * + * @author 芋道源码 + */ +@TableName("system_oauth2_code") +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class OAuth2CodeDO extends BaseDO { + + /** + * 编号,数据库递增 + */ + private Long id; + /** + * 授权码 + */ + private String code; + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 应用编号 + * + * 关联 {@link OAuth2ApplicationDO#getId()} + */ + private Long applicationId; + /** + * 刷新令牌 + * + * 关联 {@link OAuth2RefreshTokenDO#getRefreshToken()} + */ + private String refreshToken; + /** + * 过期时间 + */ + private Date expiresTime; + /** + * 创建 IP + */ + private String createIp; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java index 9249dc22d..57863cb9b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java @@ -24,6 +24,7 @@ import java.util.Date; @Data @Builder @EqualsAndHashCode(callSuper = true) +@Deprecated public class UserSessionDO extends BaseDO { /** diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java index cf01334e8..46855e593 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.system.service.auth; -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; import javax.validation.Valid; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminOAuth2Service.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminOAuth2Service.java new file mode 100644 index 000000000..5c10cdfb8 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminOAuth2Service.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.system.service.auth; + +/** + * 管理后台的 OAuth2 Service 接口 + * + * 将自身的 AdminUser 用户,授权给第三方应用,采用 OAuth2.0 的协议。 + * + * 问题:为什么自身也作为一个第三方应用,也走这套流程呢? + * 回复:当然可以这么做,采用 Implicit 模式。考虑到大多数开发者使用不到这个特性,OAuth2.0 毕竟有一定学习成本,所以暂时没有采取这种方式。 + * + * @author 芋道源码 + */ +public interface AdminOAuth2Service { +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2CodeService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2CodeService.java new file mode 100644 index 000000000..f8a788eb0 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2CodeService.java @@ -0,0 +1,11 @@ +package cn.iocoder.yudao.module.system.service.auth; + +/** + * OAuth2.0 授权码 Service 接口 + * + * 从功能上,和 Spring Security OAuth 的 JdbcAuthorizationCodeServices 的功能,提供授权码的操作 + * + * @author 芋道源码 + */ +public class OAuth2CodeService { +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java index 7718f36e1..da08b0a57 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java @@ -5,10 +5,12 @@ import org.springframework.stereotype.Service; /** * OAuth2.0 Service 实现类 * + * + * * @author 芋道源码 */ @Service -public class OAuth2ServiceImpl implements OAuth2Service { +public class OAuth2ServiceImpl implements OAuth2TokenService { // @Autowired // private SystemBizProperties systemBizProperties; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2Service.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java similarity index 69% rename from yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2Service.java rename to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java index 030bef8f6..7e6bed380 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2Service.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java @@ -1,11 +1,13 @@ package cn.iocoder.yudao.module.system.service.auth; /** - * OAuth2.0 Service 接口 + * OAuth2.0 Token Service 接口 + * + * 从功能上,和 Spring Security OAuth 的 JdbcTokenStore 的功能,提供访问令牌、刷新令牌的操作 * * @author 芋道源码 */ -public interface OAuth2Service { +public interface OAuth2TokenService { // OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String createIp); // From 4f52d1367be6ca504adff544cf069ef9f38456d8 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 May 2022 23:52:31 +0800 Subject: [PATCH 12/21] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E7=99=BB=E5=BD=95=E6=97=B6=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20OAuth2=20=E7=9A=84=20access=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pom.xml | 15 +- .../YudaoSecurityAutoConfiguration.java | 6 +- .../filter/TokenAuthenticationFilter.java | 18 +- .../service/SecurityAuthFrameworkService.java | 30 --- .../web/core/util/WebFrameworkUtils.java | 4 +- .../yudao-module-system-api/pom.xml | 7 - .../module/system/api/auth/OAuth2Api.java | 12 -- .../system/api/auth/OAuth2TokenApi.java | 22 +++ .../system/api/auth/UserSessionApi.java | 56 ------ .../dto/OAuth2AccessTokenCheckRespDTO.java | 28 +++ .../dto/OAuth2AccessTokenCreateReqDTO.java | 37 ++++ .../auth/dto/OAuth2AccessTokenRespDTO.java | 39 ++++ .../system/api/auth/OAuth2TokenApiImpl.java | 33 ++++ .../system/api/auth/UserSessionApiImpl.java | 47 ----- .../convert/auth/UserSessionConvert.java | 4 + .../dataobject/auth/OAuth2AccessTokenDO.java | 19 +- ...ApplicationDO.java => OAuth2ClientDO.java} | 36 ++-- .../dal/dataobject/auth/OAuth2CodeDO.java | 6 +- .../dataobject/auth/OAuth2RefreshTokenDO.java | 13 +- .../mysql/auth/OAuth2AccessTokenMapper.java | 32 +++ .../mysql/auth/OAuth2RefreshTokenMapper.java | 16 ++ .../system/dal/redis/RedisKeyConstants.java | 5 + .../redis/auth/OAuth2AccessTokenRedisDAO.java | 46 +++++ .../service/auth/AdminAuthServiceImpl.java | 10 +- .../service/auth/OAuth2ClientService.java | 22 +++ .../service/auth/OAuth2ClientServiceImpl.java | 21 ++ .../service/auth/OAuth2ServiceImpl.java | 145 -------------- .../service/auth/OAuth2TokenService.java | 62 +++++- .../service/auth/OAuth2TokenServiceImpl.java | 182 ++++++++++++++++++ yudao-server/pom.xml | 10 +- 30 files changed, 626 insertions(+), 357 deletions(-) delete mode 100644 yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java delete mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java delete mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java rename yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/{OAuth2ApplicationDO.java => OAuth2ClientDO.java} (68%) create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java diff --git a/yudao-framework/yudao-spring-boot-starter-security/pom.xml b/yudao-framework/yudao-spring-boot-starter-security/pom.xml index ba33598c5..4e32a6c7c 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-security/pom.xml @@ -44,18 +44,11 @@ spring-boot-starter-security - + - org.activiti - activiti-engine - 7.1.0.M6 - - - * - * - - - true + cn.iocoder.boot + yudao-module-system-api + ${revision} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java index 087edc32f..4933969bc 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl; import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; +import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -72,8 +73,9 @@ public class YudaoSecurityAutoConfiguration { * Token 认证过滤器 Bean */ @Bean - public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler) { - return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler); + public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler, + OAuth2TokenApi oauth2TokenApi) { + return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java index b27863f60..75caa59ff 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.security.core.filter; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; @@ -8,7 +9,10 @@ import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; +import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; @@ -30,6 +34,8 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { private final GlobalExceptionHandler globalExceptionHandler; + private final OAuth2TokenApi oauth2TokenApi; + @Override @SuppressWarnings("NullableProblems") protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) @@ -39,11 +45,21 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { Integer userType = WebFrameworkUtils.getLoginUserType(request); try { // 验证 token 有效性 - LoginUser loginUser = null; // TODO 芋艿:待实现 + OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token); + if (accessToken != null && ObjectUtil.notEqual(accessToken.getUserType(), userType)) { // 用户类型不匹配,无权限 + throw new AccessDeniedException("错误的用户类型"); + } + LoginUser loginUser = null; + if (accessToken != null) { // 如果不为空,说明认证通过,则转换成登录用户 + loginUser = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) + .setTenantId(accessToken.getTenantId()); + } + // 模拟 Login 功能,方便日常开发调试 if (loginUser == null) { loginUser = mockLoginUser(request, token, userType); } + // 设置当前用户 if (loginUser != null) { SecurityFrameworkUtils.setLoginUser(loginUser, request); diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java deleted file mode 100644 index 3afef7acf..000000000 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.iocoder.yudao.framework.security.core.service; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.security.core.LoginUser; -import org.springframework.security.core.userdetails.UserDetailsService; - -/** - * Security 框架 Auth Service 接口,定义不同用户类型的 {@link UserTypeEnum} 需要实现的方法 - * - * @author 芋道源码 - */ -public interface SecurityAuthFrameworkService extends UserDetailsService { - - /** - * 校验 token 的有效性,并获取用户信息 - * 通过后,刷新 token 的过期时间 - * - * @param token token - * @return 用户信息 - */ - LoginUser verifyTokenAndRefresh(String token); - - /** - * 获得用户类型。每个用户类型,对应一个 SecurityAuthFrameworkService 实现类。 - * - * @return 用户类型 - */ - UserTypeEnum getUserType(); - -} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java index b3a7f7ecb..f5ac676fa 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java @@ -84,8 +84,8 @@ public class WebFrameworkUtils { } // 1. 优先,从 Attribute 中获取 Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE); - if (userType == null) { - return null; + if (userType != null) { + return userType; } // 2. 其次,基于 URL 前缀的约定 if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) { diff --git a/yudao-module-system/yudao-module-system-api/pom.xml b/yudao-module-system/yudao-module-system-api/pom.xml index 81b129916..655db05a7 100644 --- a/yudao-module-system/yudao-module-system-api/pom.xml +++ b/yudao-module-system/yudao-module-system-api/pom.xml @@ -29,13 +29,6 @@ true - - - cn.iocoder.boot - yudao-spring-boot-starter-security - true - - diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java deleted file mode 100644 index 973466e55..000000000 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.iocoder.yudao.module.system.api.auth; - -/** - * OAuth2.0 API 接口 - * - * @author 芋道源码 - */ -public interface OAuth2Api { - - - -} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java new file mode 100644 index 000000000..44c9079f5 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.system.api.auth; + +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO; + +import javax.validation.Valid; + +/** + * OAuth2.0 Token API 接口 + * + * @author 芋道源码 + */ +public interface OAuth2TokenApi { + + OAuth2AccessTokenRespDTO createAccessToken(@Valid OAuth2AccessTokenCreateReqDTO reqDTO); + + OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken); + +// void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO); + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java deleted file mode 100644 index e7fdcb982..000000000 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java +++ /dev/null @@ -1,56 +0,0 @@ -package cn.iocoder.yudao.module.system.api.auth; - -import cn.iocoder.yudao.framework.security.core.LoginUser; - -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; - -/** - * 在线用户 Session API 接口 - * - * @author 芋道源码 - */ -public interface UserSessionApi { - - /** - * 创建在线用户 Session - * - * @param loginUser 登录用户 - * @param userIp 用户 IP - * @param userAgent 用户 UA - * @return Token 令牌 - */ - String createUserSession(@NotNull(message = "登录用户不能为空") LoginUser loginUser, String userIp, String userAgent); - - /** - * 刷新在线用户 Session 的更新时间 - * - * @param token Token 令牌 - * @param loginUser 登录用户 - */ - void refreshUserSession(@NotEmpty(message = "Token 令牌不能为空") String token, - @NotNull(message = "登录用户不能为空") LoginUser loginUser); - - /** - * 删除在线用户 Session - * - * @param token Token 令牌 - */ - void deleteUserSession(String token); - - /** - * 获得 Token 令牌对应的在线用户 - * - * @param token Token 令牌 - * @return 在线用户 - */ - LoginUser getLoginUser(String token); - - /** - * 获得 Session 超时时间,单位:毫秒 - * - * @return 超时时间 - */ - Long getSessionTimeoutMillis(); - -} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java new file mode 100644 index 000000000..5acdc1ea8 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.system.api.auth.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * OAuth2.0 访问令牌的校验 Response DTO + * + * @author 芋道源码 + */ +@Data +public class OAuth2AccessTokenCheckRespDTO implements Serializable { + + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + */ + private Integer userType; + /** + * 租户编号 + */ + private Long tenantId; + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java new file mode 100644 index 000000000..bf814f888 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.system.api.auth.dto; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import lombok.Data; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +/** + * OAuth2.0 访问令牌创建 Request DTO + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class OAuth2AccessTokenCreateReqDTO implements Serializable { + + /** + * 用户编号 + */ + @NotNull(message = "用户编号不能为空") + private Integer userId; + /** + * 用户类型 + */ + @NotNull(message = "用户类型不能为空") + @InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}") + private Integer userType; + /** + * 客户端编号 + */ + @NotNull(message = "客户端编号不能为空") + private Long clientId; + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java new file mode 100644 index 000000000..429fb071d --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.system.api.auth.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.Date; + +/** + * OAuth2.0 访问令牌的信息 Response DTO + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class OAuth2AccessTokenRespDTO implements Serializable { + + /** + * 访问令牌 + */ + private String accessToken; + /** + * 刷新令牌 + */ + private String refreshToken; + /** + * 用户编号 + */ + private Integer userId; + /** + * 用户类型 + */ + private Integer userType; + /** + * 过期时间 + */ + private Date expiresTime; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java new file mode 100644 index 000000000..e8537b0dc --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.system.api.auth; + +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO; +import cn.iocoder.yudao.module.system.convert.auth.UserSessionConvert; +import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * OAuth2.0 Token API 实现类 + * + * @author 芋道源码 + */ +@Service +public class OAuth2TokenApiImpl implements OAuth2TokenApi { + + @Resource + private OAuth2TokenService oauth2TokenService; + + @Override + public OAuth2AccessTokenRespDTO createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) { + return null; + } + + @Override + public OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken) { + return UserSessionConvert.INSTANCE.convert(oauth2TokenService.checkAccessToken(accessToken)); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java deleted file mode 100644 index 63226c125..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.module.system.api.auth; - -import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.module.system.service.auth.UserSessionService; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import javax.annotation.Resource; - -/** - * 在线用户 Session API 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -public class UserSessionApiImpl implements UserSessionApi { - - @Resource - private UserSessionService userSessionService; - - @Override - public String createUserSession(LoginUser loginUser, String userIp, String userAgent) { - return userSessionService.createUserSession(loginUser, userIp, userAgent); - } - - @Override - public void refreshUserSession(String token, LoginUser loginUser) { - userSessionService.refreshUserSession(token, loginUser); - } - - @Override - public void deleteUserSession(String token) { - userSessionService.deleteUserSession(token); - } - - @Override - public LoginUser getLoginUser(String token) { - return userSessionService.getLoginUser(token); - } - - @Override - public Long getSessionTimeoutMillis() { - return userSessionService.getSessionTimeoutMillis(); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java index d30dfdcbb..9138795fd 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.system.convert.auth; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageItemRespVO; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @@ -12,4 +14,6 @@ public interface UserSessionConvert { UserSessionPageItemRespVO convert(UserSessionDO session); + OAuth2AccessTokenCheckRespDTO convert(OAuth2AccessTokenDO bean); + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java index b8a63c1e1..aaaa8152c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java @@ -1,7 +1,8 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; @@ -12,22 +13,30 @@ import java.util.Date; /** * OAuth2 访问令牌 DO * + * 如下字段,暂时未使用,暂时不支持: + * user_name、authentication(用户信息) + * * @author 芋道源码 */ @TableName("system_oauth2_access_token") @Data @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) -public class OAuth2AccessTokenDO extends BaseDO { +public class OAuth2AccessTokenDO extends TenantBaseDO { /** * 编号,数据库递增 */ + @TableId private Long id; /** * 访问令牌 */ private String accessToken; + /** + * 刷新令牌 + */ + private String refreshToken; /** * 用户编号 */ @@ -39,11 +48,11 @@ public class OAuth2AccessTokenDO extends BaseDO { */ private Integer userType; /** - * 应用编号 + * 客户端编号 * - * 关联 {@link OAuth2ApplicationDO#getId()} + * 关联 {@link OAuth2ClientDO#getId()} */ - private Long applicationId; + private Long clientId; /** * 过期时间 */ diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java similarity index 68% rename from yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java rename to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java index 20a2b6087..88db45149 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; @@ -12,12 +13,8 @@ import java.util.List; /** * OAuth2 客户端 DO * - * 为什么不使用 Client 作为表名? - * 1. clientId 字段被占用,导致表的 id 无法有合适的缩写 - * 2. 大多数 Github、Gitee 等平台,都会习惯称为第三方接入应用 - * * 如下字段,考虑到使用相对不是很高频,主要是一些开关,暂时不支持: - * authorized_grant_types、authorities、access_token_validity、refresh_token_validity、additional_information、autoapprove、resource_ids、scope + * authorized_grant_types、authorities、additional_information、autoapprove、resource_ids、scope * * @author 芋道源码 */ @@ -25,24 +22,19 @@ import java.util.List; @Data @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) -public class OAuth2ApplicationDO extends BaseDO { +public class OAuth2ClientDO extends BaseDO { - /** - * 编号,数据库递增 - */ - private Long id; /** * 客户端编号 + * + * 由于 SQL Server 在存储 String 主键有点问题,所以暂时使用 Long 类型 */ - private String clientId; + @TableId + private Long id; /** * 客户端密钥 */ - private String clientSecret; - /** - * 可重定向的 URI 地址 - */ - private List redirectUris; + private String secret; /** * 应用名 */ @@ -61,5 +53,17 @@ public class OAuth2ApplicationDO extends BaseDO { * 枚举 {@link CommonStatusEnum} */ private Integer status; + /** + * 访问令牌的有效期 + */ + private Integer accessTokenValiditySeconds; + /** + * 刷新令牌的有效期 + */ + private Integer refreshTokenValiditySeconds; + /** + * 可重定向的 URI 地址 + */ + private List redirectUris; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java index 293bd0a41..7a851b01d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java @@ -39,11 +39,11 @@ public class OAuth2CodeDO extends BaseDO { */ private Integer userType; /** - * 应用编号 + * 客户端编号 * - * 关联 {@link OAuth2ApplicationDO#getId()} + * 关联 {@link OAuth2ClientDO#getId()} */ - private Long applicationId; + private Long clientId; /** * 刷新令牌 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java index 42824f297..1f0aa3032 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java @@ -12,6 +12,7 @@ import java.util.Date; /** * OAuth2 刷新令牌 * + * @author 芋道源码 */ @TableName("system_oauth2_refresh_token") @Data @@ -30,20 +31,22 @@ public class OAuth2RefreshTokenDO extends BaseDO { /** * 用户编号 */ - private Integer userId; + private Long userId; /** * 用户类型 * * 枚举 {@link UserTypeEnum} */ private Integer userType; + /** + * 客户端编号 + * + * 关联 {@link OAuth2ClientDO#getId()} + */ + private Long clientId; /** * 过期时间 */ private Date expiresTime; - /** - * 创建 IP - */ - private String createIp; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java new file mode 100644 index 000000000..76d04d71a --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.system.dal.mysql.auth; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface OAuth2AccessTokenMapper extends BaseMapperX { + + default OAuth2AccessTokenDO selectByAccessToken(String accessToken) { + return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken); + } + +// default OAuth2AccessTokenDO selectByUserIdAndUserType(Integer userId, Integer userType) { +// return selectOne(new QueryWrapper() +// .eq("user_id", userId).eq("user_type", userType)); +// } +// +// default int deleteByUserIdAndUserType(Integer userId, Integer userType) { +// return delete(new QueryWrapper() +// .eq("user_id", userId).eq("user_type", userType)); +// } +// +// default int deleteByRefreshToken(String refreshToken) { +// return delete(new QueryWrapper().eq("refresh_token", refreshToken)); +// } +// +// default List selectListByRefreshToken(String refreshToken) { +// return selectList(new QueryWrapper().eq("refresh_token", refreshToken)); +// } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java new file mode 100644 index 000000000..e5206d242 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.system.dal.mysql.auth; + +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface OAuth2RefreshTokenMapper extends BaseMapper { + + default int deleteByUserIdAndUserType(Integer userId, Integer userType) { + return delete(new QueryWrapper() + .eq("user_id", userId).eq("user_type", userType)); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java index b17c6fb4d..f70b546fc 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.redis; import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; import cn.iocoder.yudao.framework.security.core.LoginUser; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import java.time.Duration; @@ -22,6 +23,10 @@ public interface RedisKeyConstants { "login_user:%s", // 参数为 token 令牌 STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); + RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存", + "oauth2_access_token:%s", // 参数为访问令牌 token + STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); + RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到 "social_auth_state:%s", // 参数为 state STRING, String.class, Duration.ofHours(24)); // 值为 state diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java new file mode 100644 index 000000000..cb7fba538 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.system.dal.redis.auth; + +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import javax.annotation.Resource; +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN; + +/** + * {@link OAuth2AccessTokenDO} 的 RedisDAO + * + * @author 芋道源码 + */ +@Repository +public class OAuth2AccessTokenRedisDAO { + + @Resource + private StringRedisTemplate stringRedisTemplate; + + public OAuth2AccessTokenDO get(String accessToken) { + String redisKey = formatKey(accessToken); + return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class); + } + + public void set(OAuth2AccessTokenDO accessTokenDO) { + String redisKey = formatKey(accessTokenDO.getAccessToken()); + // 清理多余字段,避免缓存 + accessTokenDO.setUpdater(null).setUpdateTime(null).setCreateTime(null).setCreator(null).setDeleted(null); + stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(accessTokenDO), + accessTokenDO.getExpiresTime().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + } + + public void delete(String accessToken) { + String redisKey = formatKey(accessToken); + stringRedisTemplate.delete(redisKey); + } + + private static String formatKey(String accessToken) { + return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index d049d9edb..69fd38e07 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -49,6 +49,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Resource private UserSessionService userSessionService; @Resource + private OAuth2TokenService oauth2TokenService; + @Resource private SocialUserService socialUserService; @Resource @@ -207,8 +209,12 @@ public class AdminAuthServiceImpl implements AdminAuthService { LoginLogTypeEnum logType, String userIp, String userAgent) { // 插入登陆日志 createLoginLog(loginUser.getId(), username, logType, LoginResultEnum.SUCCESS); - // 缓存登录用户到 Redis 中,返回 Token 令牌 - return userSessionService.createUserSession(loginUser, userIp, userAgent); + // 创建访问令牌 + // TODO userIp、userAgent + // TODO clientId + return oauth2TokenService.createAccessToken(loginUser.getId(), getUserType().getValue(), 1L) + .getAccessToken(); +// return userSessionService.createUserSession(loginUser, userIp, userAgent); } @Override diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java new file mode 100644 index 000000000..71211f410 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.system.service.auth; + +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; + +/** + * OAuth2.0 Client Service 接口 + * + * 从功能上,和 JdbcClientDetailsService 的功能,提供客户端的操作 + * + * @author 芋道源码 + */ +public interface OAuth2ClientService { + + /** + * 从缓存中,校验客户端是否合法 + * + * @param id 客户端编号 + * @return 客户端 + */ + OAuth2ClientDO validOAuthClientFromCache(Long id); + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java new file mode 100644 index 000000000..9416eb353 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.system.service.auth; + +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; +import org.springframework.stereotype.Service; + +/** + * OAuth2.0 Client Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class OAuth2ClientServiceImpl implements OAuth2ClientService { + + @Override + public OAuth2ClientDO validOAuthClientFromCache(Long id) { + return new OAuth2ClientDO().setId(id) + .setAccessTokenValiditySeconds(60 * 30) + .setRefreshTokenValiditySeconds(60 * 60 * 24 * 30); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java deleted file mode 100644 index da08b0a57..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java +++ /dev/null @@ -1,145 +0,0 @@ -package cn.iocoder.yudao.module.system.service.auth; - -import org.springframework.stereotype.Service; - -/** - * OAuth2.0 Service 实现类 - * - * - * - * @author 芋道源码 - */ -@Service -public class OAuth2ServiceImpl implements OAuth2TokenService { - -// @Autowired -// private SystemBizProperties systemBizProperties; -// -// @Autowired -// private OAuth2AccessTokenMapper oauth2AccessTokenMapper; -// @Autowired -// private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper; -// -// @Autowired -// private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO; -// -// @Override -// @Transactional -// public OAuth2AccessTokenRespDTO createAccessToken(OAuth2CreateAccessTokenReqDTO createAccessTokenDTO) { -// // 创建刷新令牌 + 访问令牌 -// OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(createAccessTokenDTO.getUserId(), -// createAccessTokenDTO.getUserType(), createAccessTokenDTO.getCreateIp()); -// OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, createAccessTokenDTO.getCreateIp()); -// // 返回访问令牌 -// return OAuth2Convert.INSTANCE.convert(accessTokenDO); -// } -// -// @Override -// @Transactional -// public OAuth2AccessTokenRespDTO checkAccessToken(String accessToken) { -// OAuth2AccessTokenDO accessTokenDO = this.getOAuth2AccessToken(accessToken); -// if (accessTokenDO == null) { // 不存在 -// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_NOT_FOUND); -// } -// if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期 -// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_TOKEN_EXPIRED); -// } -// // 返回访问令牌 -// return OAuth2Convert.INSTANCE.convert(accessTokenDO); -// } -// -// @Override -// @Transactional -// public OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO) { -// OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(refreshAccessTokenDTO.getRefreshToken()); -// // 校验刷新令牌是否合法 -// if (refreshTokenDO == null) { // 不存在 -// throw ServiceExceptionUtil.exception(OAUTH2_REFRESH_TOKEN_NOT_FOUND); -// } -// if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期 -// throw ServiceExceptionUtil.exception(OAUTH_REFRESH_TOKEN_EXPIRED); -// } -// -// // 标记 refreshToken 对应的 accessToken 都不合法 -// // 这块的实现,参考了 Spring Security OAuth2 的代码 -// List accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshAccessTokenDTO.getRefreshToken()); -// accessTokenDOs.forEach(accessTokenDO -> deleteOAuth2AccessToken(accessTokenDO.getId())); -// -// // 创建访问令牌 -// OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp()); -// // 返回访问令牌 -// return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO); -// } -// -// @Override -// @Transactional -// public void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO) { -// // 删除 Access Token -// OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByUserIdAndUserType( -// removeTokenDTO.getUserId(), removeTokenDTO.getUserType()); -// if (accessTokenDO != null) { -// this.deleteOAuth2AccessToken(accessTokenDO.getId()); -// } -// -// // 删除 Refresh Token -// oauth2RefreshTokenMapper.deleteByUserIdAndUserType(removeTokenDTO.getUserId(), removeTokenDTO.getUserType()); -// } -// -// private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, String createIp) { -// OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO() -// .setId(generateAccessToken()) -// .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()) -// .setRefreshToken(refreshTokenDO.getId()) -// .setExpiresTime(new Date(System.currentTimeMillis() + systemBizProperties.getAccessTokenExpireTimeMillis())) -// .setCreateIp(createIp); -// oauth2AccessTokenMapper.insert(accessToken); -// return accessToken; -// } -// -// private OAuth2RefreshTokenDO createOAuth2RefreshToken(Integer userId, Integer userType, String createIp) { -// OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO() -// .setId(generateRefreshToken()) -// .setUserId(userId).setUserType(userType) -// .setExpiresTime(new Date(System.currentTimeMillis() + systemBizProperties.getRefreshTokenExpireTimeMillis())) -// .setCreateIp(createIp); -// oauth2RefreshTokenMapper.insert(refreshToken); -// return refreshToken; -// } -// -// private OAuth2AccessTokenDO getOAuth2AccessToken(String accessToken) { -// // 优先从 Redis 中获取 -// OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken); -// if (accessTokenDO != null) { -// return accessTokenDO; -// } -// -// // 获取不到,从 MySQL 中获取 -// accessTokenDO = oauth2AccessTokenMapper.selectById(accessToken); -// // 如果在 MySQL 存在,则往 Redis 中写入 -// if (accessTokenDO != null) { -// oauth2AccessTokenRedisDAO.set(accessTokenDO); -// } -// return accessTokenDO; -// } -// -// /** -// * 删除 accessToken 的 MySQL 与 Redis 的数据 -// * -// * @param accessToken 访问令牌 -// */ -// private void deleteOAuth2AccessToken(String accessToken) { -// // 删除 MySQL -// oauth2AccessTokenMapper.deleteById(accessToken); -// // 删除 Redis -// oauth2AccessTokenRedisDAO.delete(accessToken); -// } -// -// private static String generateAccessToken() { -// return StringUtils.uuid(true); -// } -// -// private static String generateRefreshToken() { -// return StringUtils.uuid(true); -// } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java index 7e6bed380..65e2ddb6f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java @@ -1,20 +1,66 @@ package cn.iocoder.yudao.module.system.service.auth; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; + /** * OAuth2.0 Token Service 接口 * - * 从功能上,和 Spring Security OAuth 的 JdbcTokenStore 的功能,提供访问令牌、刷新令牌的操作 + * 从功能上,和 Spring Security OAuth 的 DefaultTokenServices + JdbcTokenStore 的功能,提供访问令牌、刷新令牌的操作 * * @author 芋道源码 */ public interface OAuth2TokenService { -// OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String createIp); -// -// OAuth2AccessTokenRespDTO checkAccessToken(String accessToken); -// -// OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO); -// -// void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO); + /** + * 创建访问令牌 + * 注意:该流程中,会包含创建刷新令牌的创建 + * + * 参考 DefaultTokenServices 的 createAccessToken 方法 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param clientId 客户端编号 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId); + + /** + * 刷新访问令牌 + * + * 参考 DefaultTokenServices 的 refreshAccessToken 方法 + * + * @param refreshToken 刷新令牌 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO refreshAccessToken(String refreshToken); + + /** + * 获得访问令牌 + * + * 参考 DefaultTokenServices 的 getAccessToken 方法 + * + * @param accessToken 访问令牌 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO getAccessToken(String accessToken); + + /** + * 校验访问令牌 + * + * @param accessToken 访问令牌 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO checkAccessToken(String accessToken); + + /** + * 移除访问令牌 + * 注意:该流程中,会移除相关的刷新令牌 + * + * 参考 DefaultTokenServices 的 revokeToken 方法 + * + * @param accessToken 刷新令牌 + * @return 是否移除到 + */ + boolean removeAccessToken(String accessToken); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java new file mode 100644 index 000000000..a9940c482 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java @@ -0,0 +1,182 @@ +package cn.iocoder.yudao.module.system.service.auth; + +import cn.hutool.core.util.IdUtil; +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; +import cn.iocoder.yudao.framework.common.util.date.DateUtils; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO; +import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2AccessTokenMapper; +import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2RefreshTokenMapper; +import cn.iocoder.yudao.module.system.dal.redis.auth.OAuth2AccessTokenRedisDAO; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Calendar; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; + +/** + * OAuth2.0 Token Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class OAuth2TokenServiceImpl implements OAuth2TokenService { + + @Resource + private OAuth2AccessTokenMapper oauth2AccessTokenMapper; + @Resource + private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper; + + @Resource + private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO; + + @Resource + private OAuth2ClientService oauth2ClientService; + + @Override + @Transactional + public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId) { + OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); + // 创建刷新令牌 + OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO); + // 创建访问令牌 + OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, clientDO); + // 记录到 Redis 中 + oauth2AccessTokenRedisDAO.set(accessTokenDO); + return accessTokenDO; + } + + @Override + public OAuth2AccessTokenDO refreshAccessToken(String refreshToken) { + return null; + } + + @Override + public OAuth2AccessTokenDO getAccessToken(String accessToken) { + // 优先从 Redis 中获取 + OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken); + if (accessTokenDO != null) { + return accessTokenDO; + } + + // 获取不到,从 MySQL 中获取 + accessTokenDO = oauth2AccessTokenMapper.selectById(accessToken); + // 如果在 MySQL 存在,则往 Redis 中写入 + if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) { + oauth2AccessTokenRedisDAO.set(accessTokenDO); + } + return accessTokenDO; + } + + @Override + public OAuth2AccessTokenDO checkAccessToken(String accessToken) { + OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken); + if (accessTokenDO == null) { + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 不存在"); + } + if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) { + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 已过期"); + } + return accessTokenDO; + } + + @Override + public boolean removeAccessToken(String accessToken) { + return false; + } + +// @Override +// @Transactional +// public OAuth2AccessTokenRespDTO checkAccessToken(String accessToken) { +// OAuth2AccessTokenDO accessTokenDO = this.getOAuth2AccessToken(accessToken); +// if (accessTokenDO == null) { // 不存在 +// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_NOT_FOUND); +// } +// if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期 +// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_TOKEN_EXPIRED); +// } +// // 返回访问令牌 +// return OAuth2Convert.INSTANCE.convert(accessTokenDO); +// } + +// @Override +// @Transactional +// public OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO) { +// OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(refreshAccessTokenDTO.getRefreshToken()); +// // 校验刷新令牌是否合法 +// if (refreshTokenDO == null) { // 不存在 +// throw ServiceExceptionUtil.exception(OAUTH2_REFRESH_TOKEN_NOT_FOUND); +// } +// if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期 +// throw ServiceExceptionUtil.exception(OAUTH_REFRESH_TOKEN_EXPIRED); +// } +// +// // 标记 refreshToken 对应的 accessToken 都不合法 +// // 这块的实现,参考了 Spring Security OAuth2 的代码 +// List accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshAccessTokenDTO.getRefreshToken()); +// accessTokenDOs.forEach(accessTokenDO -> deleteOAuth2AccessToken(accessTokenDO.getId())); +// +// // 创建访问令牌 +// OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp()); +// // 返回访问令牌 +// return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO); +// } +// +// @Override +// @Transactional +// public void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO) { +// // 删除 Access Token +// OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByUserIdAndUserType( +// removeTokenDTO.getUserId(), removeTokenDTO.getUserType()); +// if (accessTokenDO != null) { +// this.deleteOAuth2AccessToken(accessTokenDO.getId()); +// } +// +// // 删除 Refresh Token +// oauth2RefreshTokenMapper.deleteByUserIdAndUserType(removeTokenDTO.getUserId(), removeTokenDTO.getUserType()); +// } + + private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { + OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) + .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getId()) + .setRefreshToken(refreshTokenDO.getRefreshToken()) + .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds())); + accessToken.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号 + oauth2AccessTokenMapper.insert(accessToken); + return accessToken; + } + + private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) { + OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken()) + .setUserId(userId).setUserType(userType).setClientId(clientDO.getId()) + .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getRefreshTokenValiditySeconds())); + oauth2RefreshTokenMapper.insert(refreshToken); + return refreshToken; + } + + +// /** +// * 删除 accessToken 的 MySQL 与 Redis 的数据 +// * +// * @param accessToken 访问令牌 +// */ +// private void deleteOAuth2AccessToken(String accessToken) { +// // 删除 MySQL +// oauth2AccessTokenMapper.deleteById(accessToken); +// // 删除 Redis +// oauth2AccessTokenRedisDAO.delete(accessToken); +// } +// + private static String generateAccessToken() { + return IdUtil.fastSimpleUUID(); + } + + private static String generateRefreshToken() { + return IdUtil.fastSimpleUUID(); + } + +} diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index 4480f88a5..e243290d5 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -21,11 +21,11 @@ https://github.com/YunaiV/ruoyi-vue-pro - - cn.iocoder.boot - yudao-module-member-biz - ${revision} - + + + + + cn.iocoder.boot yudao-module-system-biz From 5ea9cc3cd796db0eede69e792d842b7754ffde34 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 9 May 2022 13:29:23 +0800 Subject: [PATCH 13/21] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E7=99=BB=E5=87=BA=E6=97=B6=EF=BC=8C=E5=88=A0?= =?UTF-8?q?=E9=99=A4=20oauth=20=E4=BB=A4=E7=89=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filter/TokenAuthenticationFilter.java | 37 ++++-- .../system/enums/logger/LoginLogTypeEnum.java | 1 - .../controller/admin/auth/AuthController.java | 10 +- .../mysql/auth/OAuth2RefreshTokenMapper.java | 8 +- .../job/auth/UserSessionTimeoutJob.java | 32 ----- .../system/service/auth/AdminAuthService.java | 14 +-- .../service/auth/AdminAuthServiceImpl.java | 77 ++++-------- .../service/auth/OAuth2TokenService.java | 4 +- .../service/auth/OAuth2TokenServiceImpl.java | 53 ++------ .../service/auth/UserSessionService.java | 43 +------ .../service/auth/UserSessionServiceImpl.java | 69 ---------- .../service/auth/AuthServiceImplTest.java | 18 +-- .../auth/UserSessionServiceImplTest.java | 119 +----------------- 13 files changed, 90 insertions(+), 395 deletions(-) delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java index 75caa59ff..204b03fa0 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.security.core.filter; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.security.config.SecurityProperties; @@ -44,23 +45,14 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { if (StrUtil.isNotEmpty(token)) { Integer userType = WebFrameworkUtils.getLoginUserType(request); try { - // 验证 token 有效性 - OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token); - if (accessToken != null && ObjectUtil.notEqual(accessToken.getUserType(), userType)) { // 用户类型不匹配,无权限 - throw new AccessDeniedException("错误的用户类型"); - } - LoginUser loginUser = null; - if (accessToken != null) { // 如果不为空,说明认证通过,则转换成登录用户 - loginUser = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) - .setTenantId(accessToken.getTenantId()); - } - - // 模拟 Login 功能,方便日常开发调试 + // 1.1 基于 token 构建登录用户 + LoginUser loginUser = buildLoginUserByToken(token, userType); + // 1.2 模拟 Login 功能,方便日常开发调试 if (loginUser == null) { loginUser = mockLoginUser(request, token, userType); } - // 设置当前用户 + // 2. 设置当前用户 if (loginUser != null) { SecurityFrameworkUtils.setLoginUser(loginUser, request); } @@ -75,6 +67,25 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { chain.doFilter(request, response); } + private LoginUser buildLoginUserByToken(String token, Integer userType) { + try { + OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token); + if (accessToken == null) { + return null; + } + // 用户类型不匹配,无权限 + if (ObjectUtil.notEqual(accessToken.getUserType(), userType)) { + throw new AccessDeniedException("错误的用户类型"); + } + // 构建登录用户 + return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) + .setTenantId(accessToken.getTenantId()); + } catch (ServiceException serviceException) { + // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 + return null; + } + } + /** * 模拟登录用户,方便日常开发调试 * diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java index ab9d0bbbd..4c51f9168 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java @@ -16,7 +16,6 @@ public enum LoginLogTypeEnum { LOGIN_SMS(104), // 使用短信登陆 LOGOUT_SELF(200), // 自己主动登出 - LOGOUT_TIMEOUT(201), // 超时登出 LOGOUT_DELETE(202), // 强制退出 ; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index ce9e58e8d..7c20e8d34 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -33,8 +33,6 @@ import java.util.List; import java.util.Set; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static java.util.Collections.singleton; @@ -63,7 +61,7 @@ public class AuthController { @ApiOperation("使用账号密码登录") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult login(@RequestBody @Valid AuthLoginReqVO reqVO) { - String token = authService.login(reqVO, getClientIP(), getUserAgent()); + String token = authService.login(reqVO); return success(AuthLoginRespVO.builder().token(token).build()); } @@ -116,7 +114,7 @@ public class AuthController { @ApiOperation("使用短信验证码登录") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) { - String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent()); + String token = authService.smsLogin(reqVO); // 返回结果 return success(AuthLoginRespVO.builder().token(token).build()); } @@ -146,7 +144,7 @@ public class AuthController { @ApiOperation("社交快捷登录,使用 code 授权码") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) { - String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent()); + String token = authService.socialQuickLogin(reqVO); return success(AuthLoginRespVO.builder().token(token).build()); } @@ -154,7 +152,7 @@ public class AuthController { @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) { - String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent()); + String token = authService.socialBindLogin(reqVO); return success(AuthLoginRespVO.builder().token(token).build()); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java index e5206d242..145c7e9f8 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java @@ -1,16 +1,16 @@ package cn.iocoder.yudao.module.system.dal.mysql.auth; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @Mapper public interface OAuth2RefreshTokenMapper extends BaseMapper { - default int deleteByUserIdAndUserType(Integer userId, Integer userType) { - return delete(new QueryWrapper() - .eq("user_id", userId).eq("user_type", userType)); + default int deleteByRefreshToken(String refreshToken) { + return delete(new LambdaQueryWrapperX() + .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken)); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java deleted file mode 100644 index b2bb523e3..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java +++ /dev/null @@ -1,32 +0,0 @@ -package cn.iocoder.yudao.module.system.job.auth; - -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; -import cn.iocoder.yudao.module.system.service.auth.UserSessionService; -import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; - -/** - * 用户 Session 超时 Job - * - * @author 願 - */ -@Component -@TenantJob -@Slf4j -public class UserSessionTimeoutJob implements JobHandler { - - @Resource - private UserSessionService userSessionService; - - @Override - public String execute(String param) throws Exception { - // 执行过期 - Long timeoutCount = userSessionService.deleteTimeoutSession(); - // 返回结果,记录每次的超时数量 - return String.format("移除在线会话数量为 %s 个", timeoutCount); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java index 46855e593..dc5fa2a07 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java @@ -17,11 +17,9 @@ public interface AdminAuthService { * 账号登录 * * @param reqVO 登录信息 - * @param userIp 用户 IP - * @param userAgent 用户 UA * @return 身份令牌,使用 JWT 方式 */ - String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent); + String login(@Valid AuthLoginReqVO reqVO); /** * 基于 token 退出登录 @@ -41,21 +39,17 @@ public interface AdminAuthService { * 短信登录 * * @param reqVO 登录信息 - * @param userIp 用户 IP - * @param userAgent 用户 UA * @return 身份令牌,使用 JWT 方式 */ - String smsLogin(AuthSmsLoginReqVO reqVO, String userIp, String userAgent) ; + String smsLogin(AuthSmsLoginReqVO reqVO) ; /** * 社交快捷登录,使用 code 授权码 * * @param reqVO 登录信息 - * @param userIp 用户 IP - * @param userAgent 用户 UA * @return 身份令牌,使用 JWT 方式 */ - String socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent); + String socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO); /** * 社交绑定登录,使用 code 授权码 + 账号密码 @@ -65,6 +59,6 @@ public interface AdminAuthService { * @param userAgent 用户 UA * @return 身份令牌,使用 JWT 方式 */ - String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent); + String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index 69fd38e07..ac0b32c37 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -6,11 +6,11 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; -import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; @@ -47,8 +47,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Resource private LoginLogService loginLogService; @Resource - private UserSessionService userSessionService; - @Resource private OAuth2TokenService oauth2TokenService; @Resource private SocialUserService socialUserService; @@ -60,16 +58,15 @@ public class AdminAuthServiceImpl implements AdminAuthService { private SmsCodeApi smsCodeApi; @Override - public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { + public String login(AuthLoginReqVO reqVO) { // 判断验证码是否正确 verifyCaptcha(reqVO); // 使用账号密码,进行登录 - LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword()); + AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword()); - // 缓存登陆用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, reqVO.getUsername(), - LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); } @Override @@ -83,9 +80,9 @@ public class AdminAuthServiceImpl implements AdminAuthService { } @Override - public String smsLogin(AuthSmsLoginReqVO reqVO, String userIp, String userAgent) { + public String smsLogin(AuthSmsLoginReqVO reqVO) { // 校验验证码 - smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), userIp)); + smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), getClientIP())); // 获得用户信息 AdminUserDO user = userService.getUserByMobile(reqVO.getMobile()); @@ -93,12 +90,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { throw exception(USER_NOT_EXISTS); } - // 创建 LoginUser 对象 - LoginUser loginUser = buildLoginUser(user); - // 缓存登陆用户到 Redis 中,返回 sessionId 编号 - return createUserSessionAfterLoginSuccess(loginUser, reqVO.getMobile(), - LoginLogTypeEnum.LOGIN_MOBILE, userIp, userAgent); + return createTokenAfterLoginSuccess(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE); } @VisibleForTesting @@ -128,7 +121,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { } @VisibleForTesting - LoginUser login0(String username, String password) { + AdminUserDO login0(String username, String password) { final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; // 校验账号是否存在 AdminUserDO user = userService.getUserByUsername(username); @@ -145,9 +138,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED); throw exception(AUTH_LOGIN_USER_DISABLED); } - - // 构建 User 对象 - return buildLoginUser(user); + return user; } private void createLoginLog(Long userId, String username, @@ -170,7 +161,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { } @Override - public String socialQuickLogin(AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) { + public String socialQuickLogin(AuthSocialQuickLoginReqVO reqVO) { // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getCode(), reqVO.getState()); @@ -178,56 +169,46 @@ public class AdminAuthServiceImpl implements AdminAuthService { throw exception(AUTH_THIRD_LOGIN_NOT_BIND); } - // 自动登录 + // 获得用户 AdminUserDO user = userService.getUser(userId); if (user == null) { throw exception(USER_NOT_EXISTS); } - // 创建 LoginUser 对象 - LoginUser loginUser = buildLoginUser(user); - - // 缓存登录用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, null, - LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user.getId(), null, LoginLogTypeEnum.LOGIN_SOCIAL); } @Override - public String socialBindLogin(AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) { + public String socialBindLogin(AuthSocialBindLoginReqVO reqVO) { // 使用账号密码,进行登录。 - LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword()); + AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword()); // 绑定社交用户 - socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO)); + socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(user.getId(), getUserType().getValue(), reqVO)); - // 缓存登录用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, reqVO.getUsername(), - LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); } - private String createUserSessionAfterLoginSuccess(LoginUser loginUser, String username, - LoginLogTypeEnum logType, String userIp, String userAgent) { + private String createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) { // 插入登陆日志 - createLoginLog(loginUser.getId(), username, logType, LoginResultEnum.SUCCESS); + createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); // 创建访问令牌 - // TODO userIp、userAgent // TODO clientId - return oauth2TokenService.createAccessToken(loginUser.getId(), getUserType().getValue(), 1L) + return oauth2TokenService.createAccessToken(userId, getUserType().getValue(), 1L) .getAccessToken(); -// return userSessionService.createUserSession(loginUser, userIp, userAgent); } @Override public void logout(String token) { - // 查询用户信息 - LoginUser loginUser = userSessionService.getLoginUser(token); - if (loginUser == null) { + // 删除访问令牌 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token); + if (accessTokenDO == null) { return; } - // 删除 session - userSessionService.deleteUserSession(token); - // 记录登出日志 - createLogoutLog(loginUser.getId()); + // 删除成功,则记录登出日志 + createLogoutLog(accessTokenDO.getUserId()); } private void createLogoutLog(Long userId) { @@ -243,10 +224,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { loginLogService.createLoginLog(reqDTO); } - private LoginUser buildLoginUser(AdminUserDO user) { - return AuthConvert.INSTANCE.convert(user).setUserType(getUserType().getValue()); - } - private String getUsername(Long userId) { if (userId == null) { return null; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java index 65e2ddb6f..1811dcee0 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java @@ -59,8 +59,8 @@ public interface OAuth2TokenService { * 参考 DefaultTokenServices 的 revokeToken 方法 * * @param accessToken 刷新令牌 - * @return 是否移除到 + * @return 访问令牌的信息 */ - boolean removeAccessToken(String accessToken); + OAuth2AccessTokenDO removeAccessToken(String accessToken); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java index a9940c482..1aa07c442 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java @@ -85,23 +85,19 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { } @Override - public boolean removeAccessToken(String accessToken) { - return false; + public OAuth2AccessTokenDO removeAccessToken(String accessToken) { + // 删除访问令牌 + OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); + if (accessTokenDO == null) { + return null; + } + oauth2AccessTokenMapper.deleteById(accessTokenDO.getId()); + oauth2AccessTokenRedisDAO.delete(accessToken); + // 删除刷新令牌 + oauth2RefreshTokenMapper.deleteByRefreshToken(accessTokenDO.getRefreshToken()); + return accessTokenDO; } -// @Override -// @Transactional -// public OAuth2AccessTokenRespDTO checkAccessToken(String accessToken) { -// OAuth2AccessTokenDO accessTokenDO = this.getOAuth2AccessToken(accessToken); -// if (accessTokenDO == null) { // 不存在 -// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_NOT_FOUND); -// } -// if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期 -// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_TOKEN_EXPIRED); -// } -// // 返回访问令牌 -// return OAuth2Convert.INSTANCE.convert(accessTokenDO); -// } // @Override // @Transactional @@ -124,20 +120,6 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { // OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp()); // // 返回访问令牌 // return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO); -// } -// -// @Override -// @Transactional -// public void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO) { -// // 删除 Access Token -// OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByUserIdAndUserType( -// removeTokenDTO.getUserId(), removeTokenDTO.getUserType()); -// if (accessTokenDO != null) { -// this.deleteOAuth2AccessToken(accessTokenDO.getId()); -// } -// -// // 删除 Refresh Token -// oauth2RefreshTokenMapper.deleteByUserIdAndUserType(removeTokenDTO.getUserId(), removeTokenDTO.getUserType()); // } private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { @@ -158,19 +140,6 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { return refreshToken; } - -// /** -// * 删除 accessToken 的 MySQL 与 Redis 的数据 -// * -// * @param accessToken 访问令牌 -// */ -// private void deleteOAuth2AccessToken(String accessToken) { -// // 删除 MySQL -// oauth2AccessTokenMapper.deleteById(accessToken); -// // 删除 Redis -// oauth2AccessTokenRedisDAO.delete(accessToken); -// } -// private static String generateAccessToken() { return IdUtil.fastSimpleUUID(); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java index 844cef250..aaf02fc7b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java @@ -1,9 +1,8 @@ package cn.iocoder.yudao.module.system.service.auth; -import cn.iocoder.yudao.framework.security.core.LoginUser; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; -import cn.iocoder.yudao.framework.common.pojo.PageResult; /** * 在线用户 Session Service 接口 @@ -20,31 +19,6 @@ public interface UserSessionService { */ PageResult getUserSessionPage(UserSessionPageReqVO reqVO); - /** - * 移除超时的在线用户 - * - * @return {@link Long } 移出的超时用户数量 - **/ - long deleteTimeoutSession(); - - /** - * 创建在线用户 Session - * - * @param loginUser 登录用户 - * @param userIp 用户 IP - * @param userAgent 用户 UA - * @return Token 令牌 - */ - String createUserSession(LoginUser loginUser, String userIp, String userAgent); - - /** - * 刷新在线用户 Session 的更新时间 - * - * @param token 令牌 - * @param loginUser 登录用户 - */ - void refreshUserSession(String token, LoginUser loginUser); - /** * 删除在线用户 Session * @@ -59,19 +33,4 @@ public interface UserSessionService { */ void deleteUserSession(Long id); - /** - * 获得 Token 对应的在线用户 - * - * @param token 令牌 - * @return 在线用户 - */ - LoginUser getLoginUser(String token); - - /** - * 获得 Session 超时时间,单位:毫秒 - * - * @return 超时时间 - */ - Long getSessionTimeoutMillis(); - } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java index 75cedcf18..b09416486 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java @@ -1,12 +1,10 @@ package cn.iocoder.yudao.module.system.service.auth; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.security.config.SecurityProperties; -import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; @@ -21,12 +19,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; -import java.time.Duration; import java.util.Collection; -import java.util.List; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime; /** * 在线用户 Session Service 实现类 @@ -64,29 +59,6 @@ public class UserSessionServiceImpl implements UserSessionService { return userSessionMapper.selectPage(reqVO, userIds); } - @Override - public long deleteTimeoutSession() { - // 获取 db 里已经超时的用户列表 - List timeoutSessions = userSessionMapper.selectListBySessionTimoutLt(); - if (CollUtil.isEmpty(timeoutSessions)) { - return 0L; - } - - // 由于过期的用户一般不多,所以顺序遍历,进行清理 - int count = 0; - for (UserSessionDO session : timeoutSessions) { - // 基于 Redis 二次判断,同时也保证 Redis Key 的立即过期,避免延迟导致浪费内存空间 - if (loginUserRedisDAO.exists(session.getToken())) { - continue; - } - userSessionMapper.deleteById(session.getId()); - // 记录退出日志 - createLogoutLog(session, LoginLogTypeEnum.LOGOUT_TIMEOUT); - count++; - } - return count; - } - private void createLogoutLog(UserSessionDO session, LoginLogTypeEnum type) { LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); reqDTO.setLogType(type.getType()); @@ -100,28 +72,6 @@ public class UserSessionServiceImpl implements UserSessionService { loginLogService.createLoginLog(reqDTO); } - @Override - public String createUserSession(LoginUser loginUser, String userIp, String userAgent) { - // 生成 Session 编号 - String token = generateToken(); - // 写入 Redis 缓存 - loginUserRedisDAO.set(token, loginUser); - // 写入 DB 中 - UserSessionDO userSession = UserSessionDO.builder().token(token) - .userId(loginUser.getId()).userType(loginUser.getUserType()) - .userIp(userIp).userAgent(userAgent).username("") - .sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis()))) - .build(); - userSessionMapper.insert(userSession); - // 返回 Token 令牌 - return token; - } - - @Override - public void refreshUserSession(String token, LoginUser loginUser) { - - } - @Override public void deleteUserSession(String token) { // 删除 Redis 缓存 @@ -145,23 +95,4 @@ public class UserSessionServiceImpl implements UserSessionService { createLogoutLog(session, LoginLogTypeEnum.LOGOUT_DELETE); } - @Override - public LoginUser getLoginUser(String token) { - return loginUserRedisDAO.get(token); - } - - @Override - public Long getSessionTimeoutMillis() { - return securityProperties.getSessionTimeout().toMillis(); - } - - /** - * 生成 Token 令牌,目前采用 UUID 算法 - * - * @return Session 编号 - */ - private static String generateToken() { - return IdUtil.fastSimpleUUID(); - } - } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java index 762928f0a..134f074ce 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java @@ -70,7 +70,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); // 调用 - LoginUser loginUser = authService.login0(username, password); + AdminUserDO loginUser = authService.login0(username, password); // 校验 assertPojoEquals(user, loginUser); } @@ -182,8 +182,6 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @Test public void testLogin_success() { // 准备参数 - String userIp = randomString(); - String userAgent = randomString(); AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> o.setUsername("test_username").setPassword("test_password")); @@ -197,13 +195,14 @@ public class AuthServiceImplTest extends BaseDbUnitTest { when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true); // mock 缓存登录用户到 Redis String token = randomString(); - when(userSessionService.createUserSession(argThat(argument -> { - AssertUtils.assertPojoEquals(user, argument); - return true; - }), eq(userIp), eq(userAgent))).thenReturn(token); +// when(userSessionService.createUserSession(argThat(argument -> { +// AssertUtils.assertPojoEquals(user, argument); +// return true; +// }), eq(userIp), eq(userAgent))).thenReturn(token); + // TODO 芋艿:oauth2 // 调用, 并断言异常 - String result = authService.login(reqVO, userIp, userAgent); + String result = authService.login(reqVO); assertEquals(token, result); // 校验调用参数 verify(loginLogService).createLoginLog( @@ -219,7 +218,8 @@ public class AuthServiceImplTest extends BaseDbUnitTest { String token = randomString(); LoginUser loginUser = randomPojo(LoginUser.class); // mock - when(userSessionService.getLoginUser(token)).thenReturn(loginUser); +// when(userSessionService.getLoginUser(token)).thenReturn(loginUser); + // TODO @芋艿:oauth2 // 调用 authService.logout(token); // 校验调用参数 diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java index e06591cca..8daf56525 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.service.auth; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.security.config.SecurityProperties; import cn.iocoder.yudao.framework.security.core.LoginUser; @@ -14,8 +13,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper; import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO; import cn.iocoder.yudao.module.system.enums.common.SexEnum; -import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; -import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.service.logger.LoginLogService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import org.junit.jupiter.api.BeforeEach; @@ -25,17 +22,15 @@ import org.springframework.context.annotation.Import; import javax.annotation.Resource; import java.time.Duration; -import java.util.Calendar; import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.argThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -100,112 +95,6 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest { assertPojoEquals(dbSession, pageResult.getList().get(0)); } - @Test - public void testClearSessionTimeout_none() { - // mock db 数据 - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setSessionTimeout(addTime(Duration.ofDays(1))); - }); - userSessionMapper.insert(userSession); - - // 调用 - long count = userSessionService.deleteTimeoutSession(); - // 断言 - assertEquals(0, count); - assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除 - } - - @Test // Redis 还存在的情况 - public void testClearSessionTimeout_exists() { - // mock db 数据 - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1)); - }); - userSessionMapper.insert(userSession); - // mock redis 数据 - loginUserRedisDAO.set(userSession.getToken(), new LoginUser()); - - // 调用 - long count = userSessionService.deleteTimeoutSession(); - // 断言 - assertEquals(0, count); - assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除 - } - - @Test - public void testClearSessionTimeout_success() { - // mock db 数据 - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1)); - }); - userSessionMapper.insert(userSession); - - // 清空超时数据 - long count = userSessionService.deleteTimeoutSession(); - // 校验 - assertEquals(1, count); - assertNull(userSessionMapper.selectById(userSession.getId())); // 已删除 - verify(loginLogService).createLoginLog(argThat(loginLog -> { - assertPojoEquals(userSession, loginLog); - assertEquals(LoginLogTypeEnum.LOGOUT_TIMEOUT.getType(), loginLog.getLogType()); - assertEquals(LoginResultEnum.SUCCESS.getResult(), loginLog.getResult()); - return true; - })); - } - - @Test - public void testCreateUserSession_success() { - // 准备参数 - String userIp = randomString(); - String userAgent = randomString(); - LoginUser loginUser = randomPojo(LoginUser.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setTenantId(0L); // 租户设置为 0,因为暂未启用多租户组件 - }); - - // 调用 - String token = userSessionService.createUserSession(loginUser, userIp, userAgent); - // 校验 UserSessionDO 记录 - UserSessionDO userSessionDO = userSessionMapper.selectOne(UserSessionDO::getToken, token); - assertPojoEquals(loginUser, userSessionDO, "id", "updateTime"); - assertEquals(token, userSessionDO.getToken()); - assertEquals(userIp, userSessionDO.getUserIp()); - assertEquals(userAgent, userSessionDO.getUserAgent()); - // 校验 LoginUser 缓存 - LoginUser redisLoginUser = loginUserRedisDAO.get(token); - assertPojoEquals(loginUser, redisLoginUser, "username", "password"); - } - - @Test - public void testCreateRefreshUserSession() { - // 准备参数 - String token = randomString(); - - // mock redis 数据 - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setUserType(randomEle(UserTypeEnum.values()).getValue())); - loginUserRedisDAO.set(token, loginUser); - // mock db 数据 - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setToken(token); - }); - userSessionMapper.insert(userSession); - - // 调用 - userSessionService.refreshUserSession(token, loginUser); - // 校验 LoginUser 缓存 - LoginUser redisLoginUser = loginUserRedisDAO.get(token); - assertPojoEquals(redisLoginUser, loginUser, "username", "password"); - // 校验 UserSessionDO 记录 - UserSessionDO updateDO = userSessionMapper.selectOne(UserSessionDO::getToken, token); -// assertEquals(updateDO.getUsername(), loginUser.getUsername()); - assertNotNull(userSession.getUpdateTime()); - assertNotNull(userSession.getSessionTimeout()); - } - @Test public void testDeleteUserSession_Token() { // 准备参数 From 86e6c04e078721a77f60413000d4d99f402aeedb Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 9 May 2022 19:40:10 +0800 Subject: [PATCH 14/21] =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=90=8E=EF=BC=8C?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=20OAuth2=20=E7=9A=84=20access=20token=20+=20?= =?UTF-8?q?refresh=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/enums/auth/OAuth2ClientIdEnum.java | 17 +++ .../system/api/auth/OAuth2TokenApiImpl.java | 4 +- .../controller/admin/auth/AuthController.java | 13 +- .../admin/auth/UserSessionController.java | 81 ---------- .../admin/auth/vo/auth/AuthLoginRespVO.java | 17 ++- .../vo/session/UserSessionPageItemRespVO.java | 38 ----- .../auth/vo/session/UserSessionPageReqVO.java | 20 --- .../system/convert/auth/AuthConvert.java | 4 +- ...onConvert.java => OAuth2TokenConvert.java} | 8 +- .../dal/dataobject/auth/UserSessionDO.java | 72 --------- .../dal/mysql/auth/UserSessionMapper.java | 37 ----- .../system/dal/redis/RedisKeyConstants.java | 5 - .../dal/redis/auth/LoginUserRedisDAO.java | 52 ------- .../system/service/auth/AdminAuthService.java | 18 +-- .../service/auth/AdminAuthServiceImpl.java | 18 ++- .../service/auth/UserSessionService.java | 36 ----- .../service/auth/UserSessionServiceImpl.java | 98 ------------ .../service/auth/AuthServiceImplTest.java | 47 +++--- .../auth/UserSessionServiceImplTest.java | 139 ------------------ .../src/components/Editor/index.vue | 4 +- .../src/components/FileUpload/index.vue | 4 +- .../src/components/ImageUpload/index.vue | 4 +- .../src/components/UploadImage/index.vue | 6 +- yudao-ui-admin/src/permission.js | 4 +- yudao-ui-admin/src/store/modules/user.js | 37 +++-- yudao-ui-admin/src/utils/auth.js | 17 ++- yudao-ui-admin/src/utils/request.js | 8 +- yudao-ui-admin/src/views/infra/file/index.vue | 4 +- .../src/views/system/user/index.vue | 2 +- 29 files changed, 134 insertions(+), 680 deletions(-) create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientIdEnum.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageItemRespVO.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageReqVO.java rename yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/{UserSessionConvert.java => OAuth2TokenConvert.java} (53%) delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/LoginUserRedisDAO.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientIdEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientIdEnum.java new file mode 100644 index 000000000..d41c2d25e --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientIdEnum.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.system.enums.auth; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * OAuth2.0 客户端的编号枚举 + */ +@AllArgsConstructor +@Getter +public enum OAuth2ClientIdEnum { + + DEFAULT(1L); // 系统默认 + + private final Long id; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java index e8537b0dc..8a3cef022 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.system.api.auth; import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO; import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO; -import cn.iocoder.yudao.module.system.convert.auth.UserSessionConvert; +import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert; import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService; import org.springframework.stereotype.Service; @@ -27,7 +27,7 @@ public class OAuth2TokenApiImpl implements OAuth2TokenApi { @Override public OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken) { - return UserSessionConvert.INSTANCE.convert(oauth2TokenService.checkAccessToken(accessToken)); + return OAuth2TokenConvert.INSTANCE.convert(oauth2TokenService.checkAccessToken(accessToken)); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 7c20e8d34..153bc9c20 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -61,8 +61,7 @@ public class AuthController { @ApiOperation("使用账号密码登录") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult login(@RequestBody @Valid AuthLoginReqVO reqVO) { - String token = authService.login(reqVO); - return success(AuthLoginRespVO.builder().token(token).build()); + return success(authService.login(reqVO)); } @PostMapping("/logout") @@ -114,9 +113,7 @@ public class AuthController { @ApiOperation("使用短信验证码登录") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) { - String token = authService.smsLogin(reqVO); - // 返回结果 - return success(AuthLoginRespVO.builder().token(token).build()); + return success(authService.smsLogin(reqVO)); } @PostMapping("/send-sms-code") @@ -144,16 +141,14 @@ public class AuthController { @ApiOperation("社交快捷登录,使用 code 授权码") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) { - String token = authService.socialQuickLogin(reqVO); - return success(AuthLoginRespVO.builder().token(token).build()); + return success(authService.socialQuickLogin(reqVO)); } @PostMapping("/social-bind-login") @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) { - String token = authService.socialBindLogin(reqVO); - return success(AuthLoginRespVO.builder().token(token).build()); + return success(authService.socialBindLogin(reqVO)); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java deleted file mode 100644 index 9ce2edca9..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java +++ /dev/null @@ -1,81 +0,0 @@ -package cn.iocoder.yudao.module.system.controller.admin.auth; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageItemRespVO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; -import cn.iocoder.yudao.module.system.convert.auth.UserSessionConvert; -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; -import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; -import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; -import cn.iocoder.yudao.module.system.service.auth.UserSessionService; -import cn.iocoder.yudao.module.system.service.dept.DeptService; -import cn.iocoder.yudao.module.system.service.user.AdminUserService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiImplicitParam; -import io.swagger.annotations.ApiOperation; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import javax.annotation.Resource; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; - -@Api(tags = "管理后台 - 用户 Session") -@RestController -@RequestMapping("/system/user-session") -public class UserSessionController { - - @Resource - private UserSessionService userSessionService; - @Resource - private AdminUserService userService; - - @Resource - private DeptService deptService; - - @GetMapping("/page") - @ApiOperation("获得 Session 分页列表") - @PreAuthorize("@ss.hasPermission('system:user-session:page')") - public CommonResult> getUserSessionPage(@Validated UserSessionPageReqVO reqVO) { - // 获得 Session 分页 - PageResult pageResult = userSessionService.getUserSessionPage(reqVO); - - // 获得拼接需要的数据 - Map userMap = userService.getUserMap( - convertList(pageResult.getList(), UserSessionDO::getUserId, - session -> session.getUserType().equals(UserTypeEnum.ADMIN.getValue()))); - Map deptMap = deptService.getDeptMap( - convertList(userMap.values(), AdminUserDO::getDeptId)); - // 拼接结果返回 - List sessionList = new ArrayList<>(pageResult.getList().size()); - pageResult.getList().forEach(session -> { - UserSessionPageItemRespVO respVO = UserSessionConvert.INSTANCE.convert(session); - sessionList.add(respVO); - // 设置用户账号 - MapUtils.findAndThen(userMap, session.getUserId(), user -> { - respVO.setUsername(user.getUsername()); - // 设置用户部门 - MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> respVO.setDeptName(dept.getName())); - }); - }); - return success(new PageResult<>(sessionList, pageResult.getTotal())); - } - - @DeleteMapping("/delete") - @ApiOperation("删除 Session") - @ApiImplicitParam(name = "id", value = "Session 编号", required = true, dataTypeClass = Long.class, example = "1024") - @PreAuthorize("@ss.hasPermission('system:user-session:delete')") - public CommonResult deleteUserSession(@RequestParam("id") Long id) { - userSessionService.deleteUserSession(id); - return success(true); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginRespVO.java index bd13ba377..915a550c6 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginRespVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginRespVO.java @@ -7,14 +7,25 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -@ApiModel("管理后台 - 账号密码登录 Response VO") +import java.util.Date; + +@ApiModel("管理后台 - 登录 Response VO") @Data @NoArgsConstructor @AllArgsConstructor @Builder public class AuthLoginRespVO { - @ApiModelProperty(value = "token", required = true, example = "yudaoyuanma") - private String token; + @ApiModelProperty(value = "用户编号", required = true, example = "1024") + private Long userId; + + @ApiModelProperty(value = "访问令牌", required = true, example = "happy") + private String accessToken; + + @ApiModelProperty(value = "刷新令牌", required = true, example = "nice") + private String refreshToken; + + @ApiModelProperty(value = "过期时间", required = true) + private Date expiresTime; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageItemRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageItemRespVO.java deleted file mode 100644 index ad12877ef..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageItemRespVO.java +++ /dev/null @@ -1,38 +0,0 @@ -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.session; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -import java.util.Date; - -@ApiModel(value = "管理后台 - 用户在线 Session Response VO", description = "相比用户基本信息来说,会多部门、用户账号等信息") -@Data -@NoArgsConstructor -@AllArgsConstructor -@EqualsAndHashCode(callSuper = true) -public class UserSessionPageItemRespVO extends PageParam { - - @ApiModelProperty(value = "Session 编号", required = true, example = "fe50b9f6-d177-44b1-8da9-72ea34f63db7") - private String id; - - @ApiModelProperty(value = "用户 IP", required = true, example = "127.0.0.1") - private String userIp; - - @ApiModelProperty(value = "浏览器 UserAgent", required = true, example = "Mozilla/5.0") - private String userAgent; - - @ApiModelProperty(value = "登录时间", required = true) - private Date createTime; - - @ApiModelProperty(value = "用户账号", required = true, example = "yudao") - private String username; - - @ApiModelProperty(value = "部门名称", example = "研发部") - private String deptName; - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageReqVO.java deleted file mode 100644 index 7e85c87ba..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/session/UserSessionPageReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.session; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import lombok.EqualsAndHashCode; - -@ApiModel("管理后台 - 在线用户 Session 分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -public class UserSessionPageReqVO extends PageParam { - - @ApiModelProperty(value = "用户 IP", example = "127.0.0.1", notes = "模糊匹配") - private String userIp; - - @ApiModelProperty(value = "用户账号", example = "yudao", notes = "模糊匹配") - private String username; - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java index fdea4063f..9f8a32225 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java @@ -1,11 +1,11 @@ package cn.iocoder.yudao.module.system.convert.auth; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; @@ -21,7 +21,7 @@ public interface AuthConvert { AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class); - LoginUser convert(AdminUserDO bean); + AuthLoginRespVO convert(OAuth2AccessTokenDO bean); default AuthPermissionInfoRespVO convert(AdminUserDO user, List roleList, List menuList) { return AuthPermissionInfoRespVO.builder() diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/OAuth2TokenConvert.java similarity index 53% rename from yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java rename to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/OAuth2TokenConvert.java index 9138795fd..3bf96da32 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/OAuth2TokenConvert.java @@ -1,18 +1,14 @@ package cn.iocoder.yudao.module.system.convert.auth; import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageItemRespVO; import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @Mapper -public interface UserSessionConvert { +public interface OAuth2TokenConvert { - UserSessionConvert INSTANCE = Mappers.getMapper(UserSessionConvert.class); - - UserSessionPageItemRespVO convert(UserSessionDO session); + OAuth2TokenConvert INSTANCE = Mappers.getMapper(OAuth2TokenConvert.class); OAuth2AccessTokenCheckRespDTO convert(OAuth2AccessTokenDO bean); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java deleted file mode 100644 index 57863cb9b..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java +++ /dev/null @@ -1,72 +0,0 @@ -package cn.iocoder.yudao.module.system.dal.dataobject.auth; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.framework.security.core.LoginUser; -import com.baomidou.mybatisplus.annotation.*; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.Date; - -/** - * 在线用户表 - * - * 我们已经将 {@link LoginUser} 缓存在 Redis 当中。 - * 这里额外存储在线用户到 MySQL 中,目的是为了方便管理界面可以灵活查询。 - * 同时,通过定时轮询 UserSessionDO 表,可以主动删除 Redis 的缓存,因为 Redis 的过期删除是延迟的。 - * - * @author 芋道源码 - */ -@TableName(value = "system_user_session") -@KeySequence(value = "system_user_session_seq") -@Data -@Builder -@EqualsAndHashCode(callSuper = true) -@Deprecated -public class UserSessionDO extends BaseDO { - - /** - * 会话编号 - */ - private Long id; - /** - * 令牌 - */ - private String token; - - /** - * 用户编号 - * - * 关联 AdminUserDO.id 或者 MemberUserDO.id - */ - private Long userId; - /** - * 用户类型 - * - * 枚举 {@link UserTypeEnum} - */ - private Integer userType; - - /** - * 用户账号 - * - * 冗余,因为账号可以变更 - */ - private String username; - - /** - * 用户 IP - */ - private String userIp; - /** - * 浏览器 UA - */ - private String userAgent; - /** - * 会话超时时间 - */ - private Date sessionTimeout; - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java deleted file mode 100644 index dea0d4792..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.system.dal.mysql.auth; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.Collection; -import java.util.Date; -import java.util.List; - -@Mapper -public interface UserSessionMapper extends BaseMapperX { - - default PageResult selectPage(UserSessionPageReqVO reqVO, Collection userIds) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .inIfPresent(UserSessionDO::getUserId, userIds) - .likeIfPresent(UserSessionDO::getUserIp, reqVO.getUserIp())); - } - - default List selectListBySessionTimoutLt() { - return selectList(new LambdaQueryWrapperX() - .lt(UserSessionDO::getSessionTimeout, new Date())); - } - - default void updateByToken(String token, UserSessionDO updateObj) { - update(updateObj, new LambdaQueryWrapperX() - .eq(UserSessionDO::getToken, token)); - } - - default void deleteByToken(String token) { - delete(new LambdaQueryWrapperX().eq(UserSessionDO::getToken, token)); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java index f70b546fc..839b973ef 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.system.dal.redis; import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; -import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import java.time.Duration; @@ -19,10 +18,6 @@ public interface RedisKeyConstants { "captcha_code:%s", // 参数为 uuid STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); - RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登录用户的缓存", - "login_user:%s", // 参数为 token 令牌 - STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); - RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存", "oauth2_access_token:%s", // 参数为访问令牌 token STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/LoginUserRedisDAO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/LoginUserRedisDAO.java deleted file mode 100644 index 8af701f71..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/LoginUserRedisDAO.java +++ /dev/null @@ -1,52 +0,0 @@ -package cn.iocoder.yudao.module.system.dal.redis.auth; - -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.framework.security.config.SecurityProperties; -import cn.iocoder.yudao.framework.security.core.LoginUser; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.stereotype.Repository; - -import javax.annotation.Resource; - -import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.LOGIN_USER; - -/** - * {@link LoginUser} 的 RedisDAO - * - * @author 芋道源码 - */ -@Repository -public class LoginUserRedisDAO { - - @Resource - private StringRedisTemplate stringRedisTemplate; - - @Resource - private SecurityProperties securityProperties; - - public LoginUser get(String token) { - String redisKey = formatKey(token); - return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), LoginUser.class); - } - - public Boolean exists(String token) { - String redisKey = formatKey(token); - return stringRedisTemplate.hasKey(redisKey); - } - - public void set(String token, LoginUser loginUser) { - String redisKey = formatKey(token); - stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser), - securityProperties.getSessionTimeout()); - } - - public void delete(String token) { - String redisKey = formatKey(token); - stringRedisTemplate.delete(redisKey); - } - - private static String formatKey(String token) { - return LOGIN_USER.formatKey(token); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java index dc5fa2a07..84bfe5e0f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java @@ -17,9 +17,9 @@ public interface AdminAuthService { * 账号登录 * * @param reqVO 登录信息 - * @return 身份令牌,使用 JWT 方式 + * @return 登录结果 */ - String login(@Valid AuthLoginReqVO reqVO); + AuthLoginRespVO login(@Valid AuthLoginReqVO reqVO); /** * 基于 token 退出登录 @@ -39,26 +39,24 @@ public interface AdminAuthService { * 短信登录 * * @param reqVO 登录信息 - * @return 身份令牌,使用 JWT 方式 + * @return 登录结果 */ - String smsLogin(AuthSmsLoginReqVO reqVO) ; + AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) ; /** * 社交快捷登录,使用 code 授权码 * * @param reqVO 登录信息 - * @return 身份令牌,使用 JWT 方式 + * @return 登录结果 */ - String socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO); + AuthLoginRespVO socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO); /** * 社交绑定登录,使用 code 授权码 + 账号密码 * * @param reqVO 登录信息 - * @param userIp 用户 IP - * @param userAgent 用户 UA - * @return 身份令牌,使用 JWT 方式 + * @return 登录结果 */ - String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO); + AuthLoginRespVO socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index ac0b32c37..b82ec951b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; +import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientIdEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; @@ -58,7 +59,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { private SmsCodeApi smsCodeApi; @Override - public String login(AuthLoginReqVO reqVO) { + public AuthLoginRespVO login(AuthLoginReqVO reqVO) { // 判断验证码是否正确 verifyCaptcha(reqVO); @@ -80,7 +81,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { } @Override - public String smsLogin(AuthSmsLoginReqVO reqVO) { + public AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) { // 校验验证码 smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), getClientIP())); @@ -161,7 +162,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { } @Override - public String socialQuickLogin(AuthSocialQuickLoginReqVO reqVO) { + public AuthLoginRespVO socialQuickLogin(AuthSocialQuickLoginReqVO reqVO) { // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getCode(), reqVO.getState()); @@ -180,7 +181,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { } @Override - public String socialBindLogin(AuthSocialBindLoginReqVO reqVO) { + public AuthLoginRespVO socialBindLogin(AuthSocialBindLoginReqVO reqVO) { // 使用账号密码,进行登录。 AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword()); @@ -191,13 +192,14 @@ public class AdminAuthServiceImpl implements AdminAuthService { return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); } - private String createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) { + private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) { // 插入登陆日志 createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); // 创建访问令牌 - // TODO clientId - return oauth2TokenService.createAccessToken(userId, getUserType().getValue(), 1L) - .getAccessToken(); + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(), + OAuth2ClientIdEnum.DEFAULT.getId()); + // 构建返回结果 + return AuthConvert.INSTANCE.convert(accessTokenDO); } @Override diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java deleted file mode 100644 index aaf02fc7b..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.module.system.service.auth; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; - -/** - * 在线用户 Session Service 接口 - * - * @author 芋道源码 - */ -public interface UserSessionService { - - /** - * 获得在线用户分页列表 - * - * @param reqVO 分页条件 - * @return 份额与列表 - */ - PageResult getUserSessionPage(UserSessionPageReqVO reqVO); - - /** - * 删除在线用户 Session - * - * @param token token 令牌 - */ - void deleteUserSession(String token); - - /** - * 删除在线用户 Session - * - * @param id 编号 - */ - void deleteUserSession(Long id); - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java deleted file mode 100644 index b09416486..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java +++ /dev/null @@ -1,98 +0,0 @@ -package cn.iocoder.yudao.module.system.service.auth; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; -import cn.iocoder.yudao.framework.security.config.SecurityProperties; -import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; -import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; -import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper; -import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO; -import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; -import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; -import cn.iocoder.yudao.module.system.service.logger.LoginLogService; -import cn.iocoder.yudao.module.system.service.user.AdminUserService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.Collection; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; - -/** - * 在线用户 Session Service 实现类 - * - * @author 芋道源码 - */ -@Slf4j -@Service -public class UserSessionServiceImpl implements UserSessionService { - - @Resource - private UserSessionMapper userSessionMapper; - - @Resource - private AdminUserService userService; - @Resource - private LoginLogService loginLogService; - - @Resource - private LoginUserRedisDAO loginUserRedisDAO; - - @Resource - private SecurityProperties securityProperties; - - @Override - public PageResult getUserSessionPage(UserSessionPageReqVO reqVO) { - // 处理基于用户昵称的查询 - Collection userIds = null; - if (StrUtil.isNotEmpty(reqVO.getUsername())) { - userIds = convertSet(userService.getUsersByUsername(reqVO.getUsername()), AdminUserDO::getId); - if (CollUtil.isEmpty(userIds)) { - return PageResult.empty(); - } - } - return userSessionMapper.selectPage(reqVO, userIds); - } - - private void createLogoutLog(UserSessionDO session, LoginLogTypeEnum type) { - LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); - reqDTO.setLogType(type.getType()); - reqDTO.setTraceId(TracerUtils.getTraceId()); - reqDTO.setUserId(session.getUserId()); - reqDTO.setUserType(session.getUserType()); - reqDTO.setUsername(session.getUsername()); - reqDTO.setUserAgent(session.getUserAgent()); - reqDTO.setUserIp(session.getUserIp()); - reqDTO.setResult(LoginResultEnum.SUCCESS.getResult()); - loginLogService.createLoginLog(reqDTO); - } - - @Override - public void deleteUserSession(String token) { - // 删除 Redis 缓存 - loginUserRedisDAO.delete(token); - // 删除 DB 记录 - userSessionMapper.deleteByToken(token); - // 无需记录日志,因为退出那已经记录 - } - - @Override - public void deleteUserSession(Long id) { - UserSessionDO session = userSessionMapper.selectById(id); - if (session == null) { - return; - } - // 删除 Redis 缓存 - loginUserRedisDAO.delete(session.getToken()); - // 删除 DB 记录 - userSessionMapper.deleteById(id); - // 记录退出日志 - createLogoutLog(session, LoginLogTypeEnum.LOGOUT_DELETE); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java index 134f074ce..b8441d111 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java @@ -1,11 +1,13 @@ package cn.iocoder.yudao.module.system.service.auth; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.security.core.LoginUser; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.framework.test.core.util.AssertUtils; import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO; +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginRespVO; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; @@ -26,7 +28,6 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -43,11 +44,11 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @MockBean private LoginLogService loginLogService; @MockBean - private UserSessionService userSessionService; - @MockBean private SocialUserService socialService; @MockBean private SmsCodeApi smsCodeApi; + @MockBean + private OAuth2TokenService oauth2TokenService; @MockBean private Validator validator; @@ -188,22 +189,20 @@ public class AuthServiceImplTest extends BaseDbUnitTest { // mock 验证码正确 when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); // mock user 数据 - AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername("test_username") + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L).setUsername("test_username") .setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus())); when(userService.getUserByUsername(eq("test_username"))).thenReturn(user); // mock password 匹配 when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true); // mock 缓存登录用户到 Redis - String token = randomString(); -// when(userSessionService.createUserSession(argThat(argument -> { -// AssertUtils.assertPojoEquals(user, argument); -// return true; -// }), eq(userIp), eq(userAgent))).thenReturn(token); - // TODO 芋艿:oauth2 + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); + when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq(1L))) + .thenReturn(accessTokenDO); // 调用, 并断言异常 - String result = authService.login(reqVO); - assertEquals(token, result); + AuthLoginRespVO loginRespVO = authService.login(reqVO); + assertPojoEquals(accessTokenDO, loginRespVO); // 校验调用参数 verify(loginLogService).createLoginLog( argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) @@ -216,18 +215,28 @@ public class AuthServiceImplTest extends BaseDbUnitTest { public void testLogout_success() { // 准备参数 String token = randomString(); - LoginUser loginUser = randomPojo(LoginUser.class); // mock -// when(userSessionService.getLoginUser(token)).thenReturn(loginUser); - // TODO @芋艿:oauth2 + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); + when(oauth2TokenService.removeAccessToken(eq(token))).thenReturn(accessTokenDO); + // 调用 authService.logout(token); // 校验调用参数 - verify(userSessionService, times(1)).deleteUserSession(token); - verify(loginLogService, times(1)).createLoginLog( - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType()) + verify(loginLogService).createLoginLog(argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType()) && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())) ); } + @Test + public void testLogout_fail() { + // 准备参数 + String token = randomString(); + + // 调用 + authService.logout(token); + // 校验调用参数 + verify(loginLogService, never()).createLoginLog(any()); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java deleted file mode 100644 index 8daf56525..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java +++ /dev/null @@ -1,139 +0,0 @@ -package cn.iocoder.yudao.module.system.service.auth; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; -import cn.iocoder.yudao.framework.security.config.SecurityProperties; -import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; -import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; -import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper; -import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO; -import cn.iocoder.yudao.module.system.enums.common.SexEnum; -import cn.iocoder.yudao.module.system.service.logger.LoginLogService; -import cn.iocoder.yudao.module.system.service.user.AdminUserService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.time.Duration; - -import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -/** - * {@link UserSessionServiceImpl} 的单元测试 - * - * @author Lyon - */ -@Import({UserSessionServiceImpl.class, LoginUserRedisDAO.class}) -public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest { - - @Resource - private UserSessionServiceImpl userSessionService; - - @Resource - private UserSessionMapper userSessionMapper; - - @MockBean - private AdminUserService userService; - @MockBean - private LoginLogService loginLogService; - @Resource - private LoginUserRedisDAO loginUserRedisDAO; - - @MockBean - private SecurityProperties securityProperties; - - @BeforeEach - public void setUp() { - when(securityProperties.getSessionTimeout()).thenReturn(Duration.ofDays(1L)); - } - - @Test - public void testGetUserSessionPage_success() { - // mock 数据 - AdminUserDO dbUser = randomPojo(AdminUserDO.class, o -> { - o.setSex(randomEle(SexEnum.values()).getSex()); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - }); - when(userService.getUsersByUsername(eq(dbUser.getUsername()))).thenReturn(singletonList(dbUser)); - // 插入可被查询到的数据 - String userIp = randomString(); - UserSessionDO dbSession = randomPojo(UserSessionDO.class, o -> { - o.setUserId(dbUser.getId()); - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setUserIp(userIp); - }); - userSessionMapper.insert(dbSession); - // 测试 username 不匹配 - userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> o.setUserId(123456L))); - // 测试 userIp 不匹配 - userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> o.setUserIp("testUserIp"))); - // 准备参数 - UserSessionPageReqVO reqVO = new UserSessionPageReqVO(); - reqVO.setUsername(dbUser.getUsername()); - reqVO.setUserIp(userIp); - - // 调用 - PageResult pageResult = userSessionService.getUserSessionPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbSession, pageResult.getList().get(0)); - } - - @Test - public void testDeleteUserSession_Token() { - // 准备参数 - String token = randomString(); - - // mock redis 数据 - loginUserRedisDAO.set(token, new LoginUser()); - // mock db 数据 - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setToken(token); - }); - userSessionMapper.insert(userSession); - - // 调用 - userSessionService.deleteUserSession(token); - // 校验数据不存在了 - assertNull(loginUserRedisDAO.get(token)); - assertNull(userSessionMapper.selectOne(UserSessionDO::getToken, token)); - } - - @Test - public void testDeleteUserSession_Id() { - // mock db 数据 - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - }); - userSessionMapper.insert(userSession); - // mock redis 数据 - loginUserRedisDAO.set(userSession.getToken(), new LoginUser()); - - // 准备参数 - Long id = userSession.getId(); - - // 调用 - userSessionService.deleteUserSession(id); - // 校验数据不存在了 - assertNull(loginUserRedisDAO.get(userSession.getToken())); - assertNull(userSessionMapper.selectById(id)); - } - -} diff --git a/yudao-ui-admin/src/components/Editor/index.vue b/yudao-ui-admin/src/components/Editor/index.vue index 6bb5a18d3..9cbb981ee 100644 --- a/yudao-ui-admin/src/components/Editor/index.vue +++ b/yudao-ui-admin/src/components/Editor/index.vue @@ -22,7 +22,7 @@ import Quill from "quill"; import "quill/dist/quill.core.css"; import "quill/dist/quill.snow.css"; import "quill/dist/quill.bubble.css"; -import { getToken } from "@/utils/auth"; +import { getAccessToken } from "@/utils/auth"; export default { name: "Editor", @@ -62,7 +62,7 @@ export default { return { uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址 headers: { - Authorization: "Bearer " + getToken() + Authorization: "Bearer " + getAccessToken() }, Quill: null, currentValue: "", diff --git a/yudao-ui-admin/src/components/FileUpload/index.vue b/yudao-ui-admin/src/components/FileUpload/index.vue index aa2296b93..d19b7db91 100644 --- a/yudao-ui-admin/src/components/FileUpload/index.vue +++ b/yudao-ui-admin/src/components/FileUpload/index.vue @@ -40,7 +40,7 @@ From 63e632ceb7c9df92b406017b16039a83ee3e9218 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 11 May 2022 01:20:07 +0800 Subject: [PATCH 19/21] =?UTF-8?q?=E5=9B=BE=E7=89=87=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=20ImageUpload=20=E4=B8=8A=E4=BC=A0=E6=8A=A5?= =?UTF-8?q?=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/file/FileController.java | 4 +++- .../infra/service/file/FileServiceImpl.java | 10 ++++++++- .../src/components/ImageUpload/index.vue | 21 +++++++------------ 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index e3d810dec..4500133ed 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -4,6 +4,7 @@ import cn.hutool.core.io.IoUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO; import cn.iocoder.yudao.module.infra.convert.file.FileConvert; @@ -42,8 +43,9 @@ public class FileController { @ApiImplicitParam(name = "file", value = "文件附件", required = true, dataTypeClass = MultipartFile.class), @ApiImplicitParam(name = "path", value = "文件路径", example = "yudaoyuanma.png", dataTypeClass = String.class) }) + @OperateLog(logArgs = false) // 上传文件,没有记录操作日志的必要 public CommonResult uploadFile(@RequestParam("file") MultipartFile file, - @RequestParam("path") String path) throws Exception { + @RequestParam(value = "path", required = false) String path) throws Exception { return success(fileService.createFile(path, IoUtil.readBytes(file.getInputStream()))); } diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java index 66af402e0..4f3736f1b 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.infra.service.file; import cn.hutool.core.io.FileTypeUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.file.core.client.FileClient; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; @@ -36,6 +38,12 @@ public class FileServiceImpl implements FileService { @Override public String createFile(String path, byte[] content) throws Exception { + // 计算默认的 path 名 + String type = FileTypeUtil.getType(new ByteArrayInputStream(content)); + if (StrUtil.isEmpty(path)) { + path = DigestUtil.md5Hex(content) + '.' + type; + } + // 上传到文件存储器 FileClient client = fileConfigService.getMasterFileClient(); Assert.notNull(client, "客户端(master) 不能为空"); @@ -46,7 +54,7 @@ public class FileServiceImpl implements FileService { file.setConfigId(client.getId()); file.setPath(path); file.setUrl(url); - file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content))); + file.setType(type); file.setSize(content.length); fileMapper.insert(file); return url; diff --git a/yudao-ui-admin/src/components/ImageUpload/index.vue b/yudao-ui-admin/src/components/ImageUpload/index.vue index 6fb6b5e11..e43fa780c 100644 --- a/yudao-ui-admin/src/components/ImageUpload/index.vue +++ b/yudao-ui-admin/src/components/ImageUpload/index.vue @@ -2,7 +2,7 @@
{ if (typeof item === "string") { - if (item.indexOf(this.baseUrl) === -1) { - item = { name: this.baseUrl + item, url: this.baseUrl + item }; - } else { - item = { name: item, url: item }; - } + // edit by 芋道源码 + item = { name: item, url: item }; } return item; }); @@ -127,7 +121,8 @@ export default { }, // 上传成功回调 handleUploadSuccess(res) { - this.uploadList.push({ name: res.fileName, url: res.fileName }); + // edit by 芋道源码 + this.uploadList.push({ name: res.data, url: res.data }); if (this.uploadList.length === this.number) { this.fileList = this.fileList.concat(this.uploadList); this.uploadList = []; @@ -188,7 +183,7 @@ export default { for (let i in list) { strs += list[i].url.replace(this.baseUrl, "") + separator; } - return strs != '' ? strs.substr(0, strs.length - 1) : ''; + return strs !== '' ? strs.substr(0, strs.length - 1) : ''; } } }; From b6cb6469f1fefb083e3c0134c0217ea14dcd7495 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 11 May 2022 12:35:42 +0800 Subject: [PATCH 20/21] =?UTF-8?q?=E5=AF=8C=E6=96=87=E6=9C=AC=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E7=9A=84=20Editor=20=E7=9A=84=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E4=B8=8A=E4=BC=A0=E6=8A=A5=E9=94=99=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Editor/index.vue | 18 ++--- .../src/components/ImageUpload/index.vue | 4 +- .../src/components/UploadImage/index.vue | 68 ------------------- 3 files changed, 11 insertions(+), 79 deletions(-) delete mode 100644 yudao-ui-admin/src/components/UploadImage/index.vue diff --git a/yudao-ui-admin/src/components/Editor/index.vue b/yudao-ui-admin/src/components/Editor/index.vue index 9cbb981ee..61d62434e 100644 --- a/yudao-ui-admin/src/components/Editor/index.vue +++ b/yudao-ui-admin/src/components/Editor/index.vue @@ -1,7 +1,7 @@ - - - - From 1f36af8e6a3c8a3b5f274df7180b075bb268254f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 11 May 2022 12:40:54 +0800 Subject: [PATCH 21/21] =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=20FileUpload=20=E4=B8=8A=E4=BC=A0=E6=8A=A5?= =?UTF-8?q?=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/FileUpload/index.vue | 17 +++++++---------- .../src/views/system/oauth2/client/index.vue | 5 ++++- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/yudao-ui-admin/src/components/FileUpload/index.vue b/yudao-ui-admin/src/components/FileUpload/index.vue index d19b7db91..84b7df236 100644 --- a/yudao-ui-admin/src/components/FileUpload/index.vue +++ b/yudao-ui-admin/src/components/FileUpload/index.vue @@ -28,7 +28,7 @@
  • - + {{ getFileName(file.name) }}
    @@ -72,11 +72,8 @@ export default { return { number: 0, uploadList: [], - baseUrl: process.env.VUE_APP_BASE_API, - uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址 - headers: { - Authorization: "Bearer " + getAccessToken(), - }, + uploadFileUrl: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址 + headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部 fileList: [], }; }, @@ -121,8 +118,7 @@ export default { } const isTypeOk = this.fileType.some((type) => { if (file.type.indexOf(type) > -1) return true; - if (fileExtension && fileExtension.indexOf(type) > -1) return true; - return false; + return !!(fileExtension && fileExtension.indexOf(type) > -1); }); if (!isTypeOk) { this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`); @@ -152,7 +148,8 @@ export default { }, // 上传成功回调 handleUploadSuccess(res) { - this.uploadList.push({ name: res.fileName, url: res.fileName }); + // edit by 芋道源码 + this.uploadList.push({ name: res.data, url: res.data }); if (this.uploadList.length === this.number) { this.fileList = this.fileList.concat(this.uploadList); this.uploadList = []; @@ -181,7 +178,7 @@ export default { for (let i in list) { strs += list[i].url + separator; } - return strs != '' ? strs.substr(0, strs.length - 1) : ''; + return strs !== '' ? strs.substr(0, strs.length - 1) : ''; } } }; diff --git a/yudao-ui-admin/src/views/system/oauth2/client/index.vue b/yudao-ui-admin/src/views/system/oauth2/client/index.vue index 31cf94142..f80c847ca 100755 --- a/yudao-ui-admin/src/views/system/oauth2/client/index.vue +++ b/yudao-ui-admin/src/views/system/oauth2/client/index.vue @@ -70,7 +70,8 @@ - + + @@ -104,10 +105,12 @@ import { createOAuth2Client, updateOAuth2Client, deleteOAuth2Client, getOAuth2Cl import ImageUpload from '@/components/ImageUpload'; import Editor from '@/components/Editor'; import {CommonStatusEnum} from "@/utils/constants"; +import FileUpload from "@/components/FileUpload"; export default { name: "OAuth2Client", components: { + FileUpload, ImageUpload, Editor, },