From 3bd7e8e6829300d9f1d059b5b50123df3fc67af4 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 May 2022 02:09:22 +0800 Subject: [PATCH] =?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())) ); }