Commit baadb5a9370d2e57b7ea3b7247c0ab34e387e31c
1 parent
73bf0b6f
简化 mock login 模拟登录的实现,由 TokenAuthenticationFilter 直接实现
Showing
11 changed files
with
67 additions
and
86 deletions
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantContextWebFilter.java
| 1 | 1 | package cn.iocoder.yudao.framework.tenant.core.web; |
| 2 | 2 | |
| 3 | -import cn.hutool.core.util.StrUtil; | |
| 4 | 3 | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; |
| 4 | +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; | |
| 5 | 5 | import org.springframework.web.filter.OncePerRequestFilter; |
| 6 | 6 | |
| 7 | 7 | import javax.servlet.FilterChain; |
| ... | ... | @@ -24,9 +24,9 @@ public class TenantContextWebFilter extends OncePerRequestFilter { |
| 24 | 24 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) |
| 25 | 25 | throws ServletException, IOException { |
| 26 | 26 | // 设置 |
| 27 | - String tenantId = request.getHeader(HEADER_TENANT_ID); | |
| 28 | - if (StrUtil.isNotEmpty(tenantId)) { | |
| 29 | - TenantContextHolder.setTenantId(Long.valueOf(tenantId)); | |
| 27 | + Long tenantId = WebFrameworkUtils.getTenantId(request); | |
| 28 | + if (tenantId != null) { | |
| 29 | + TenantContextHolder.setTenantId(tenantId); | |
| 30 | 30 | } |
| 31 | 31 | try { |
| 32 | 32 | chain.doFilter(request, response); | ... | ... |
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java
| ... | ... | @@ -106,17 +106,6 @@ public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsA |
| 106 | 106 | } |
| 107 | 107 | |
| 108 | 108 | /** |
| 109 | - * 模拟指定用户编号的 LoginUser | |
| 110 | - * | |
| 111 | - * @param request 请求 | |
| 112 | - * @param userId 用户编号 | |
| 113 | - * @return 登录用户 | |
| 114 | - */ | |
| 115 | - public LoginUser mockLogin(HttpServletRequest request, Long userId) { | |
| 116 | - return selectService(request).mockLogin(userId); | |
| 117 | - } | |
| 118 | - | |
| 119 | - /** | |
| 120 | 109 | * 基于 token 退出登录 |
| 121 | 110 | * |
| 122 | 111 | * @param request 请求 | ... | ... |
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; |
| 8 | 8 | import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider; |
| 9 | 9 | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| 10 | 10 | import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; |
| 11 | +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; | |
| 11 | 12 | import lombok.RequiredArgsConstructor; |
| 12 | 13 | import org.springframework.web.filter.OncePerRequestFilter; |
| 13 | 14 | |
| ... | ... | @@ -38,12 +39,13 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { |
| 38 | 39 | throws ServletException, IOException { |
| 39 | 40 | String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); |
| 40 | 41 | if (StrUtil.isNotEmpty(token)) { |
| 42 | + Integer userType = WebFrameworkUtils.getLoginUserType(request); | |
| 41 | 43 | try { |
| 42 | 44 | // 验证 token 有效性 |
| 43 | 45 | LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token); |
| 44 | 46 | // 模拟 Login 功能,方便日常开发调试 |
| 45 | 47 | if (loginUser == null) { |
| 46 | - loginUser = mockLoginUser(request, token); | |
| 48 | + loginUser = mockLoginUser(request, token, userType); | |
| 47 | 49 | } |
| 48 | 50 | // 设置当前用户 |
| 49 | 51 | if (loginUser != null) { |
| ... | ... | @@ -67,9 +69,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { |
| 67 | 69 | * |
| 68 | 70 | * @param request 请求 |
| 69 | 71 | * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号 |
| 72 | + * @param userType 用户类型 | |
| 70 | 73 | * @return 模拟的 LoginUser |
| 71 | 74 | */ |
| 72 | - private LoginUser mockLoginUser(HttpServletRequest request, String token) { | |
| 75 | + private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) { | |
| 73 | 76 | if (!securityProperties.getMockEnable()) { |
| 74 | 77 | return null; |
| 75 | 78 | } |
| ... | ... | @@ -77,8 +80,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { |
| 77 | 80 | if (!token.startsWith(securityProperties.getMockSecret())) { |
| 78 | 81 | return null; |
| 79 | 82 | } |
| 83 | + // 构建模拟用户 | |
| 80 | 84 | Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length())); |
| 81 | - return authenticationProvider.mockLogin(request, userId); | |
| 85 | + return new LoginUser().setId(userId).setUserType(userType) | |
| 86 | + .setTenantId(WebFrameworkUtils.getTenantId(request)); | |
| 82 | 87 | } |
| 83 | 88 | |
| 84 | 89 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java
| ... | ... | @@ -21,14 +21,6 @@ public interface SecurityAuthFrameworkService extends UserDetailsService { |
| 21 | 21 | LoginUser verifyTokenAndRefresh(String token); |
| 22 | 22 | |
| 23 | 23 | /** |
| 24 | - * 模拟指定用户编号的 LoginUser | |
| 25 | - * | |
| 26 | - * @param userId 用户编号 | |
| 27 | - * @return 登录用户 | |
| 28 | - */ | |
| 29 | - LoginUser mockLogin(Long userId); | |
| 30 | - | |
| 31 | - /** | |
| 32 | 24 | * 基于 token 退出登录 |
| 33 | 25 | * |
| 34 | 26 | * @param token token | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java
| ... | ... | @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.web.core.filter.DemoFilter; |
| 7 | 7 | import cn.iocoder.yudao.framework.web.core.filter.XssFilter; |
| 8 | 8 | import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; |
| 9 | 9 | import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler; |
| 10 | +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; | |
| 10 | 11 | import org.springframework.beans.factory.annotation.Value; |
| 11 | 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| 12 | 13 | import org.springframework.boot.context.properties.EnableConfigurationProperties; |
| ... | ... | @@ -65,6 +66,13 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer { |
| 65 | 66 | return new GlobalResponseBodyHandler(); |
| 66 | 67 | } |
| 67 | 68 | |
| 69 | + @Bean | |
| 70 | + @SuppressWarnings("InstantiationOfUtilityClass") | |
| 71 | + public WebFrameworkUtils webFrameworkUtils(WebProperties webProperties) { | |
| 72 | + // 由于 WebFrameworkUtils 需要使用到 webProperties 属性,所以注册为一个 Bean | |
| 73 | + return new WebFrameworkUtils(webProperties); | |
| 74 | + } | |
| 75 | + | |
| 68 | 76 | // ========== Filter 相关 ========== |
| 69 | 77 | |
| 70 | 78 | /** | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java
| 1 | 1 | package cn.iocoder.yudao.framework.web.core.util; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 6 | +import cn.iocoder.yudao.framework.web.config.WebProperties; | |
| 5 | 7 | import org.springframework.web.context.request.RequestAttributes; |
| 6 | 8 | import org.springframework.web.context.request.RequestContextHolder; |
| 7 | 9 | import org.springframework.web.context.request.ServletRequestAttributes; |
| ... | ... | @@ -21,16 +23,43 @@ public class WebFrameworkUtils { |
| 21 | 23 | |
| 22 | 24 | private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result"; |
| 23 | 25 | |
| 26 | + private static final String HEADER_TENANT_ID = "tenant-id"; | |
| 27 | + | |
| 28 | + private static WebProperties properties; | |
| 29 | + | |
| 30 | + public WebFrameworkUtils(WebProperties webProperties) { | |
| 31 | + WebFrameworkUtils.properties = webProperties; | |
| 32 | + } | |
| 33 | + | |
| 34 | + /** | |
| 35 | + * 获得租户编号,从 header 中 | |
| 36 | + * 考虑到其它 framework 组件也会使用到租户编号,所以不得不放在 WebFrameworkUtils 统一提供 | |
| 37 | + * | |
| 38 | + * @param request 请求 | |
| 39 | + * @return 租户编号 | |
| 40 | + */ | |
| 41 | + public static Long getTenantId(HttpServletRequest request) { | |
| 42 | + String tenantId = request.getHeader(HEADER_TENANT_ID); | |
| 43 | + return StrUtil.isNotEmpty(tenantId) ? Long.valueOf(tenantId) : null; | |
| 44 | + } | |
| 45 | + | |
| 24 | 46 | public static void setLoginUserId(ServletRequest request, Long userId) { |
| 25 | 47 | request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId); |
| 26 | 48 | } |
| 27 | 49 | |
| 50 | + /** | |
| 51 | + * 设置用户类型 | |
| 52 | + * | |
| 53 | + * @param request 请求 | |
| 54 | + * @param userType 用户类型 | |
| 55 | + */ | |
| 28 | 56 | public static void setLoginUserType(ServletRequest request, Integer userType) { |
| 29 | 57 | request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType); |
| 30 | 58 | } |
| 31 | 59 | |
| 32 | 60 | /** |
| 33 | 61 | * 获得当前用户的编号,从请求中 |
| 62 | + * 注意:该方法仅限于 framework 框架使用!!! | |
| 34 | 63 | * |
| 35 | 64 | * @param request 请求 |
| 36 | 65 | * @return 用户编号 |
| ... | ... | @@ -43,7 +72,8 @@ public class WebFrameworkUtils { |
| 43 | 72 | } |
| 44 | 73 | |
| 45 | 74 | /** |
| 46 | - * 获得当前用户的类型,从请求中 | |
| 75 | + * 获得当前用户的类型 | |
| 76 | + * 注意:该方法仅限于 web 相关的 framework 组件使用!!! | |
| 47 | 77 | * |
| 48 | 78 | * @param request 请求 |
| 49 | 79 | * @return 用户编号 |
| ... | ... | @@ -52,7 +82,19 @@ public class WebFrameworkUtils { |
| 52 | 82 | if (request == null) { |
| 53 | 83 | return null; |
| 54 | 84 | } |
| 55 | - return (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE); | |
| 85 | + // 1. 优先,从 Attribute 中获取 | |
| 86 | + Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE); | |
| 87 | + if (userType == null) { | |
| 88 | + return null; | |
| 89 | + } | |
| 90 | + // 2. 其次,基于 URL 前缀的约定 | |
| 91 | + if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) { | |
| 92 | + return UserTypeEnum.ADMIN.getValue(); | |
| 93 | + } | |
| 94 | + if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) { | |
| 95 | + return UserTypeEnum.MEMBER.getValue(); | |
| 96 | + } | |
| 97 | + return null; | |
| 56 | 98 | } |
| 57 | 99 | |
| 58 | 100 | public static Integer getLoginUserType() { | ... | ... |
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
| ... | ... | @@ -212,21 +212,6 @@ public class MemberAuthServiceImpl implements MemberAuthService { |
| 212 | 212 | } |
| 213 | 213 | |
| 214 | 214 | @Override |
| 215 | - public LoginUser mockLogin(Long userId) { | |
| 216 | - // 获取用户编号对应的 UserDO | |
| 217 | - MemberUserDO user = userService.getUser(userId); | |
| 218 | - if (user == null) { | |
| 219 | - throw new UsernameNotFoundException(String.valueOf(userId)); | |
| 220 | - } | |
| 221 | - | |
| 222 | - // 执行登陆 | |
| 223 | - createLoginLog(userId, user.getMobile(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS); | |
| 224 | - | |
| 225 | - // 创建 LoginUser 对象 | |
| 226 | - return buildLoginUser(user); | |
| 227 | - } | |
| 228 | - | |
| 229 | - @Override | |
| 230 | 215 | public void logout(String token) { |
| 231 | 216 | // 查询用户信息 |
| 232 | 217 | LoginUser loginUser = userSessionApi.getLoginUser(token); | ... | ... |
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http
| 1 | 1 | ### 请求 /login 接口 => 成功 |
| 2 | -POST {{baseUrl}}/system/login | |
| 2 | +POST {{baseUrl}}/system/auth/login | |
| 3 | 3 | Content-Type: application/json |
| 4 | 4 | tenant-id: {{adminTenentId}} |
| 5 | 5 | |
| ... | ... | @@ -11,7 +11,7 @@ tenant-id: {{adminTenentId}} |
| 11 | 11 | } |
| 12 | 12 | |
| 13 | 13 | ### 请求 /login 接口 => 成功(无验证码) |
| 14 | -POST {{baseUrl}}/system/login | |
| 14 | +POST {{baseUrl}}/system/auth/login | |
| 15 | 15 | Content-Type: application/json |
| 16 | 16 | tenant-id: {{adminTenentId}} |
| 17 | 17 | |
| ... | ... | @@ -21,7 +21,7 @@ tenant-id: {{adminTenentId}} |
| 21 | 21 | } |
| 22 | 22 | |
| 23 | 23 | ### 请求 /get-permission-info 接口 => 成功 |
| 24 | -GET {{baseUrl}}/system/get-permission-info | |
| 24 | +GET {{baseUrl}}/system/auth/get-permission-info | |
| 25 | 25 | Authorization: Bearer {{token}} |
| 26 | 26 | tenant-id: {{adminTenentId}} |
| 27 | 27 | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
| ... | ... | @@ -83,19 +83,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { |
| 83 | 83 | } |
| 84 | 84 | |
| 85 | 85 | @Override |
| 86 | - public LoginUser mockLogin(Long userId) { | |
| 87 | - // 获取用户编号对应的 AdminUserDO | |
| 88 | - AdminUserDO user = userService.getUser(userId); | |
| 89 | - if (user == null) { | |
| 90 | - throw new UsernameNotFoundException(String.valueOf(userId)); | |
| 91 | - } | |
| 92 | - createLoginLog(userId, user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS); | |
| 93 | - | |
| 94 | - // 创建 LoginUser 对象 | |
| 95 | - return buildLoginUser(user); | |
| 96 | - } | |
| 97 | - | |
| 98 | - @Override | |
| 99 | 86 | public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { |
| 100 | 87 | // 判断验证码是否正确 |
| 101 | 88 | verifyCaptcha(reqVO); | ... | ... |
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java
| ... | ... | @@ -97,32 +97,6 @@ public class AuthServiceImplTest extends BaseDbUnitTest { |
| 97 | 97 | } |
| 98 | 98 | |
| 99 | 99 | @Test |
| 100 | - public void testMockLogin_success() { | |
| 101 | - // 准备参数 | |
| 102 | - Long userId = randomLongId(); | |
| 103 | - // mock 方法 01 | |
| 104 | - AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId)); | |
| 105 | - when(userService.getUser(eq(userId))).thenReturn(user); | |
| 106 | - | |
| 107 | - // 调用 | |
| 108 | - LoginUser loginUser = authService.mockLogin(userId); | |
| 109 | - // 断言 | |
| 110 | - AssertUtils.assertPojoEquals(user, loginUser, "updateTime"); | |
| 111 | - } | |
| 112 | - | |
| 113 | - @Test | |
| 114 | - public void testMockLogin_userNotFound() { | |
| 115 | - // 准备参数 | |
| 116 | - Long userId = randomLongId(); | |
| 117 | - // mock 方法 | |
| 118 | - | |
| 119 | - // 调用, 并断言异常 | |
| 120 | - assertThrows(UsernameNotFoundException.class, // 抛出 UsernameNotFoundException 异常 | |
| 121 | - () -> authService.mockLogin(userId), | |
| 122 | - String.valueOf(userId)); // 异常提示为 userId | |
| 123 | - } | |
| 124 | - | |
| 125 | - @Test | |
| 126 | 100 | public void testLogin_captchaNotFound() { |
| 127 | 101 | // 准备参数 |
| 128 | 102 | AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); | ... | ... |