去除 Spring Security 的 Admin 的 loadUsername,使用自己定义的 login0 实现

This commit is contained in:
YunaiV 2022-05-08 02:09:22 +08:00
parent 3351ebbbb4
commit 3bd7e8e682
10 changed files with 171 additions and 204 deletions

View File

@ -105,16 +105,6 @@ public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsA
return selectService(request).verifyTokenAndRefresh(token); 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) { private SecurityAuthFrameworkService selectService(HttpServletRequest request) {
// 第一步获得用户类型 // 第一步获得用户类型
UserTypeEnum userType = getUserType(request); UserTypeEnum userType = getUserType(request);

View File

@ -20,13 +20,6 @@ public interface SecurityAuthFrameworkService extends UserDetailsService {
*/ */
LoginUser verifyTokenAndRefresh(String token); LoginUser verifyTokenAndRefresh(String token);
/**
* 基于 token 退出登录
*
* @param token token
*/
void logout(String token);
/** /**
* 获得用户类型每个用户类型对应一个 SecurityAuthFrameworkService 实现类 * 获得用户类型每个用户类型对应一个 SecurityAuthFrameworkService 实现类
* *

View File

@ -24,6 +24,13 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
*/ */
String login(@Valid AppAuthLoginReqVO reqVO, String userIp, String userAgent); String login(@Valid AppAuthLoginReqVO reqVO, String userIp, String userAgent);
/**
* 基于 token 退出登录
*
* @param token token
*/
void logout(String token);
/** /**
* 手机 + 验证码登陆 * 手机 + 验证码登陆
* *

View File

@ -12,7 +12,6 @@ public interface ErrorCodeConstants {
// ========== AUTH 模块 1002000000 ========== // ========== AUTH 模块 1002000000 ==========
ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确"); ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确");
ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用"); 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_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确"); ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定"); ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");

View File

@ -26,8 +26,6 @@ public interface AuthConvert {
SpringSecurityUser convert2(AdminUserDO user); SpringSecurityUser convert2(AdminUserDO user);
LoginUser convert(SpringSecurityUser bean);
default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) { default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
return AuthPermissionInfoRespVO.builder() return AuthPermissionInfoRespVO.builder()
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build()) .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build())

View File

@ -1,14 +1,14 @@
package cn.iocoder.yudao.module.system.service.auth; 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.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
import javax.validation.Valid; import javax.validation.Valid;
/** /**
* 管理后台的认证 Service 接口 * 管理后台的认证 Service 接口
* *
* 提供用户的账号密码登录token 的校验等认证相关的功能 * 提供用户的登录登出的能力
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@ -24,6 +24,13 @@ public interface AdminAuthService extends SecurityAuthFrameworkService {
*/ */
String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent); String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent);
/**
* 基于 token 退出登录
*
* @param token token
*/
void logout(String token);
/** /**
* 短信验证码发送 * 短信验证码发送
* *

View File

@ -1,12 +1,12 @@
package cn.iocoder.yudao.module.system.service.auth; 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.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; 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.servlet.ServletUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser; 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.logger.dto.LoginLogCreateReqDTO;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; 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.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.logger.LoginLogService;
import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j; 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.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.validation.Validator; import javax.validation.Validator;
@ -50,11 +43,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
public class AdminAuthServiceImpl implements AdminAuthService { public class AdminAuthServiceImpl implements AdminAuthService {
@Resource @Resource
@Lazy // 延迟加载因为存在相互依赖的问题
private AuthenticationManager authenticationManager;
@Autowired
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") // UserService 存在重名
private AdminUserService userService; private AdminUserService userService;
@Resource @Resource
private CaptchaService captchaService; private CaptchaService captchaService;
@ -71,17 +59,6 @@ public class AdminAuthServiceImpl implements AdminAuthService {
@Resource @Resource
private SmsCodeApi smsCodeApi; 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 @Override
public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) {
// 判断验证码是否正确 // 判断验证码是否正确
@ -124,7 +101,8 @@ public class AdminAuthServiceImpl implements AdminAuthService {
LoginLogTypeEnum.LOGIN_MOBILE, userIp, userAgent); LoginLogTypeEnum.LOGIN_MOBILE, userIp, userAgent);
} }
private void verifyCaptcha(AuthLoginReqVO reqVO) { @VisibleForTesting
void verifyCaptcha(AuthLoginReqVO reqVO) {
// 如果验证码关闭则不进行校验 // 如果验证码关闭则不进行校验
if (!captchaService.isCaptchaEnable()) { if (!captchaService.isCaptchaEnable()) {
return; return;
@ -149,46 +127,36 @@ public class AdminAuthServiceImpl implements AdminAuthService {
captchaService.deleteCaptchaCode(reqVO.getUuid()); captchaService.deleteCaptchaCode(reqVO.getUuid());
} }
private LoginUser login0(String username, String password) { @VisibleForTesting
LoginUser login0(String username, String password) {
final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
// 用户验证 // 校验账号是否存在
Authentication authentication; AdminUserDO user = userService.getUserByUsername(username);
try { if (user == null) {
// 调用 Spring Security AuthenticationManager#authenticate(...) 方法使用账号密码进行认证
// 在其内部会调用到 loadUserByUsername 方法获取 User 信息
authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
username, password, getUserType()));
} catch (BadCredentialsException badCredentialsException) {
createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
throw exception(AUTH_LOGIN_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 对象 // 构建 User 对象
return AuthConvert.INSTANCE.convert((SpringSecurityUser) authentication.getPrincipal()) return buildLoginUser(user);
.setUserType(getUserType().getValue());
} }
private void createLoginLog(Long userId, String username, private void createLoginLog(Long userId, String username,
LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) { LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {
// 获得用户
if (userId == null) {
AdminUserDO user = userService.getUserByUsername(username);
userId = user != null ? user.getId() : null;
}
// 插入登录日志 // 插入登录日志
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
reqDTO.setLogType(logTypeEnum.getType()); reqDTO.setLogType(logTypeEnum.getType());
reqDTO.setTraceId(TracerUtils.getTraceId()); reqDTO.setTraceId(TracerUtils.getTraceId());
if (userId != null) { reqDTO.setUserId(userId);
reqDTO.setUserId(userId);
}
reqDTO.setUserType(getUserType().getValue()); reqDTO.setUserType(getUserType().getValue());
reqDTO.setUsername(username); reqDTO.setUsername(username);
reqDTO.setUserAgent(ServletUtils.getUserAgent()); reqDTO.setUserAgent(ServletUtils.getUserAgent());
@ -293,4 +261,8 @@ public class AdminAuthServiceImpl implements AdminAuthService {
return user != null ? user.getUsername() : null; return user != null ? user.getUsername() : null;
} }
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
} }

View File

@ -105,7 +105,6 @@ public interface AdminUserService {
*/ */
AdminUserDO getUserByMobile(String mobile); AdminUserDO getUserByMobile(String mobile);
/** /**
* 获得用户分页列表 * 获得用户分页列表
* *
@ -209,4 +208,13 @@ public interface AdminUserService {
*/ */
List<AdminUserDO> getUsersByStatus(Integer status); List<AdminUserDO> getUsersByStatus(Integer status);
/**
* 判断密码是否匹配
*
* @param rawPassword 未加密的密码
* @param encodedPassword 加密后的密码
* @return 是否匹配
*/
boolean isPasswordMatch(String rawPassword, String encodedPassword);
} }

View File

@ -148,7 +148,7 @@ public class AdminUserServiceImpl implements AdminUserService {
checkOldPassword(id, reqVO.getOldPassword()); checkOldPassword(id, reqVO.getOldPassword());
// 执行更新 // 执行更新
AdminUserDO updateObj = new AdminUserDO().setId(id); AdminUserDO updateObj = new AdminUserDO().setId(id);
updateObj.setPassword(passwordEncoder.encode(reqVO.getNewPassword())); // 加密密码 updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码
userMapper.updateById(updateObj); userMapper.updateById(updateObj);
} }
@ -172,7 +172,7 @@ public class AdminUserServiceImpl implements AdminUserService {
// 更新密码 // 更新密码
AdminUserDO updateObj = new AdminUserDO(); AdminUserDO updateObj = new AdminUserDO();
updateObj.setId(id); updateObj.setId(id);
updateObj.setPassword(passwordEncoder.encode(password)); // 加密密码 updateObj.setPassword(encodePassword(password)); // 加密密码
userMapper.updateById(updateObj); userMapper.updateById(updateObj);
} }
@ -205,11 +205,6 @@ public class AdminUserServiceImpl implements AdminUserService {
return userMapper.selectByUsername(username); return userMapper.selectByUsername(username);
} }
/**
* 通过手机号获取用户
* @param mobile
* @return
*/
@Override @Override
public AdminUserDO getUserByMobile(String mobile) { public AdminUserDO getUserByMobile(String mobile) {
return userMapper.selectByMobile(mobile); return userMapper.selectByMobile(mobile);
@ -395,7 +390,7 @@ public class AdminUserServiceImpl implements AdminUserService {
if (user == null) { if (user == null) {
throw exception(USER_NOT_EXISTS); throw exception(USER_NOT_EXISTS);
} }
if (!passwordEncoder.matches(oldPassword, user.getPassword())) { if (!isPasswordMatch(oldPassword, user.getPassword())) {
throw exception(USER_PASSWORD_FAILED); throw exception(USER_PASSWORD_FAILED);
} }
} }
@ -421,7 +416,7 @@ public class AdminUserServiceImpl implements AdminUserService {
AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername()); AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
if (existUser == null) { if (existUser == null) {
userMapper.insert(UserConvert.INSTANCE.convert(importUser) userMapper.insert(UserConvert.INSTANCE.convert(importUser)
.setPassword(passwordEncoder.encode(userInitPassword))); // 设置默认密码 .setPassword(encodePassword(userInitPassword))); // 设置默认密码
respVO.getCreateUsernames().add(importUser.getUsername()); respVO.getCreateUsernames().add(importUser.getUsername());
return; return;
} }
@ -443,4 +438,19 @@ public class AdminUserServiceImpl implements AdminUserService {
return userMapper.selectListByStatus(status); 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);
}
} }

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.system.service.auth; 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.security.core.LoginUser;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.framework.test.core.util.AssertUtils; 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.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; 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.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.logger.LoginLogService;
import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService; 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.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; 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.annotation.Resource;
import javax.validation.Validator; 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.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 cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.ArgumentMatchers.eq;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ -46,10 +39,6 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
@MockBean @MockBean
private AdminUserService userService; private AdminUserService userService;
@MockBean @MockBean
private AuthenticationManager authenticationManager;
@MockBean
private Authentication authentication;
@MockBean
private CaptchaService captchaService; private CaptchaService captchaService;
@MockBean @MockBean
private LoginLogService loginLogService; private LoginLogService loginLogService;
@ -58,8 +47,6 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
@MockBean @MockBean
private SocialUserService socialService; private SocialUserService socialService;
@MockBean @MockBean
private PostService postService;
@MockBean
private SmsCodeApi smsCodeApi; private SmsCodeApi smsCodeApi;
@MockBean @MockBean
@ -71,40 +58,102 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
} }
@Test @Test
public void testLoadUserByUsername_success() { public void testLogin0_success() {
// 准备参数 // 准备参数
String username = randomString(); String username = randomString();
// mock 方法 String password = randomString();
AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)); // mock user 数据
AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)
.setPassword(password).setStatus(CommonStatusEnum.ENABLE.getStatus()));
when(userService.getUserByUsername(eq(username))).thenReturn(user); 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 @Test
public void testLoadUserByUsername_userNotFound() { public void testLogin0_userNotFound() {
// 准备参数 // 准备参数
String username = randomString(); String username = randomString();
// mock 方法 String password = randomString();
// 调用, 并断言异常 // 调用, 并断言异常
assertThrows(UsernameNotFoundException.class, // 抛出 UsernameNotFoundException 异常 AssertUtils.assertServiceException(() -> authService.login0(username, password),
() -> authService.loadUserByUsername(username), AUTH_LOGIN_BAD_CREDENTIALS);
username); // 异常提示为 username verify(loginLogService).createLoginLog(
argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
&& o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult())
&& o.getUserId() == null)
);
} }
@Test @Test
public void testLogin_captchaNotFound() { public void testLogin0_badCredentials() {
// 准备参数 // 准备参数
AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); String username = randomString();
String userIp = randomString(); String password = randomString();
String userAgent = 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( verify(loginLogService, times(1)).createLoginLog(
argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
@ -113,10 +162,8 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
} }
@Test @Test
public void testLogin_captchaCodeError() { public void testCaptcha_codeError() {
// 准备参数 // 准备参数
String userIp = randomString();
String userAgent = randomString();
AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
// mock 验证码不正确 // mock 验证码不正确
@ -124,109 +171,45 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code); 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()) argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
&& o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult())) && 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 @Test
public void testLogin_success() { public void testLogin_success() {
// 准备参数 // 准备参数
String userIp = randomString(); String userIp = randomString();
String userAgent = randomString(); String userAgent = randomString();
AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o ->
o.setUsername("test_username").setPassword("test_password"));
// mock 验证码正确 // mock 验证码正确
when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
// mock authentication // mock user 数据
Long userId = randomLongId(); AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername("test_username")
Set<Long> userRoleIds = randomSet(Long.class); .setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus()));
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(userId)); when(userService.getUserByUsername(eq("test_username"))).thenReturn(user);
when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword()))) // mock password 匹配
.thenReturn(authentication); when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true);
when(authentication.getPrincipal()).thenReturn(loginUser);
// mock 缓存登录用户到 Redis // mock 缓存登录用户到 Redis
String token = randomString(); 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); String result = authService.login(reqVO, userIp, userAgent);
assertEquals(token, login); assertEquals(token, result);
// 校验调用参数 // 校验调用参数
verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid()); verify(loginLogService).createLoginLog(
verify(loginLogService, times(1)).createLoginLog(
argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) 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()))
); );
} }