简化 mock login 模拟登录的实现,由 TokenAuthenticationFilter 直接实现

This commit is contained in:
YunaiV 2022-05-08 00:17:48 +08:00
parent 73bf0b6f4f
commit baadb5a937
11 changed files with 67 additions and 86 deletions

View File

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

View File

@ -105,17 +105,6 @@ public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsA
return selectService(request).verifyTokenAndRefresh(token); return selectService(request).verifyTokenAndRefresh(token);
} }
/**
* 模拟指定用户编号的 LoginUser
*
* @param request 请求
* @param userId 用户编号
* @return 登录用户
*/
public LoginUser mockLogin(HttpServletRequest request, Long userId) {
return selectService(request).mockLogin(userId);
}
/** /**
* 基于 token 退出登录 * 基于 token 退出登录
* *

View File

@ -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.authentication.MultiUserDetailsAuthenticationProvider;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
@ -38,12 +39,13 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
throws ServletException, IOException { throws ServletException, IOException {
String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
if (StrUtil.isNotEmpty(token)) { if (StrUtil.isNotEmpty(token)) {
Integer userType = WebFrameworkUtils.getLoginUserType(request);
try { try {
// 验证 token 有效性 // 验证 token 有效性
LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token); LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token);
// 模拟 Login 功能方便日常开发调试 // 模拟 Login 功能方便日常开发调试
if (loginUser == null) { if (loginUser == null) {
loginUser = mockLoginUser(request, token); loginUser = mockLoginUser(request, token, userType);
} }
// 设置当前用户 // 设置当前用户
if (loginUser != null) { if (loginUser != null) {
@ -67,9 +69,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
* *
* @param request 请求 * @param request 请求
* @param token 模拟的 token格式为 {@link SecurityProperties#getMockSecret()} + 用户编号 * @param token 模拟的 token格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
* @param userType 用户类型
* @return 模拟的 LoginUser * @return 模拟的 LoginUser
*/ */
private LoginUser mockLoginUser(HttpServletRequest request, String token) { private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) {
if (!securityProperties.getMockEnable()) { if (!securityProperties.getMockEnable()) {
return null; return null;
} }
@ -77,8 +80,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
if (!token.startsWith(securityProperties.getMockSecret())) { if (!token.startsWith(securityProperties.getMockSecret())) {
return null; return null;
} }
// 构建模拟用户
Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length())); Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
return authenticationProvider.mockLogin(request, userId); return new LoginUser().setId(userId).setUserType(userType)
.setTenantId(WebFrameworkUtils.getTenantId(request));
} }
} }

View File

@ -20,14 +20,6 @@ public interface SecurityAuthFrameworkService extends UserDetailsService {
*/ */
LoginUser verifyTokenAndRefresh(String token); LoginUser verifyTokenAndRefresh(String token);
/**
* 模拟指定用户编号的 LoginUser
*
* @param userId 用户编号
* @return 登录用户
*/
LoginUser mockLogin(Long userId);
/** /**
* 基于 token 退出登录 * 基于 token 退出登录
* *

View File

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

View File

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

View File

@ -211,21 +211,6 @@ public class MemberAuthServiceImpl implements MemberAuthService {
return userSessionApi.getLoginUser(token); 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 @Override
public void logout(String token) { public void logout(String token) {
// 查询用户信息 // 查询用户信息

View File

@ -12,7 +12,6 @@ public enum LoginLogTypeEnum {
LOGIN_USERNAME(100), // 使用账号登录 LOGIN_USERNAME(100), // 使用账号登录
LOGIN_SOCIAL(101), // 使用社交登录 LOGIN_SOCIAL(101), // 使用社交登录
LOGIN_MOCK(102), // 使用 Mock 登录
LOGIN_MOBILE(103), // 使用手机登陆 LOGIN_MOBILE(103), // 使用手机登陆
LOGIN_SMS(104), // 使用短信登陆 LOGIN_SMS(104), // 使用短信登陆

View File

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

View File

@ -82,19 +82,6 @@ public class AdminAuthServiceImpl implements AdminAuthService {
return AuthConvert.INSTANCE.convert2(user); 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 @Override
public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) {
// 判断验证码是否正确 // 判断验证码是否正确

View File

@ -96,32 +96,6 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
username); // 异常提示为 username username); // 异常提示为 username
} }
@Test
public void testMockLogin_success() {
// 准备参数
Long userId = randomLongId();
// mock 方法 01
AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId));
when(userService.getUser(eq(userId))).thenReturn(user);
// 调用
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 @Test
public void testLogin_captchaNotFound() { public void testLogin_captchaNotFound() {
// 准备参数 // 准备参数