From 2edf53a40383d2a437f1ad994bc9999296b45a06 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 31 Jan 2023 22:43:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=20AdminAuthServiceImpl=20?= =?UTF-8?q?=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/auth/AdminAuthServiceImpl.java | 53 ++-- .../auth/AdminAuthServiceImplTest.java | 293 +++++++++++++----- 2 files changed, 238 insertions(+), 108 deletions(-) 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 045e3bf4b..ca34156eb 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 @@ -57,10 +57,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { private SocialUserService socialUserService; @Resource private MemberService memberService; - @Resource private Validator validator; - @Resource private CaptchaService captchaService; @Resource @@ -95,8 +93,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Override public AuthLoginRespVO login(AuthLoginReqVO reqVO) { - // 判断验证码是否正确 - verifyCaptcha(reqVO); + // 校验验证码 + validateCaptcha(reqVO); // 使用账号密码,进行登录 AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword()); @@ -106,7 +104,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())); } - // 创建 Token 令牌,记录登录日志 return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); } @@ -136,25 +133,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { return createTokenAfterLoginSuccess(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE); } - @VisibleForTesting - void verifyCaptcha(AuthLoginReqVO reqVO) { - // 如果验证码关闭,则不进行校验 - if (!captchaEnable) { - return; - } - // 校验验证码 - ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class); - CaptchaVO captchaVO = new CaptchaVO(); - captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification()); - ResponseModel response = captchaService.verification(captchaVO); - // 验证不通过 - if (!response.isSuccess()) { - // 创建登录失败日志(验证码不正确) - createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR); - throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg()); - } - } - private void createLoginLog(Long userId, String username, LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) { // 插入登录日志 @@ -193,10 +171,23 @@ public class AdminAuthServiceImpl implements AdminAuthService { return createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); } - @Override - public AuthLoginRespVO refreshToken(String refreshToken) { - OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT); - return AuthConvert.INSTANCE.convert(accessTokenDO); + @VisibleForTesting + void validateCaptcha(AuthLoginReqVO reqVO) { + // 如果验证码关闭,则不进行校验 + if (!captchaEnable) { + return; + } + // 校验验证码 + ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class); + CaptchaVO captchaVO = new CaptchaVO(); + captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification()); + ResponseModel response = captchaService.verification(captchaVO); + // 验证不通过 + if (!response.isSuccess()) { + // 创建登录失败日志(验证码不正确) + createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR); + throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg()); + } } private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) { @@ -209,6 +200,12 @@ public class AdminAuthServiceImpl implements AdminAuthService { return AuthConvert.INSTANCE.convert(accessTokenDO); } + @Override + public AuthLoginRespVO refreshToken(String refreshToken) { + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT); + return AuthConvert.INSTANCE.convert(accessTokenDO); + } + @Override public void logout(String token, Integer logType) { // 删除访问令牌 diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java index b998e6a35..9861b912c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java @@ -1,31 +1,43 @@ package cn.iocoder.yudao.module.system.service.auth; +import cn.hutool.core.util.ReflectUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.framework.test.core.util.AssertUtils; import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; +import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import cn.iocoder.yudao.module.system.service.logger.LoginLogService; import cn.iocoder.yudao.module.system.service.member.MemberService; import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; +import com.xingyuv.captcha.model.common.ResponseModel; import com.xingyuv.captcha.service.CaptchaService; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import javax.annotation.Resource; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; import javax.validation.Validator; +import static cn.hutool.core.util.RandomUtil.randomEle; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; 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.*; @@ -42,17 +54,24 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { @MockBean private LoginLogService loginLogService; @MockBean - private SocialUserService socialService; + private SocialUserService socialUserService; @MockBean private SmsCodeApi smsCodeApi; @MockBean private OAuth2TokenService oauth2TokenService; @MockBean private MemberService memberService; - @MockBean private Validator validator; + @BeforeEach + public void setUp() { + ReflectUtil.setFieldValue(authService, "captchaEnable", true); + // 注入一个 Validator 对象 + ReflectUtil.setFieldValue(authService, "validator", + Validation.buildDefaultValidatorFactory().getValidator()); + } + @Test public void testAuthenticate_success() { // 准备参数 @@ -78,7 +97,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { String password = randomString(); // 调用, 并断言异常 - AssertUtils.assertServiceException(() -> authService.authenticate(username, password), + assertServiceException(() -> authService.authenticate(username, password), AUTH_LOGIN_BAD_CREDENTIALS); verify(loginLogService).createLoginLog( argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) @@ -98,7 +117,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { when(userService.getUserByUsername(eq(username))).thenReturn(user); // 调用, 并断言异常 - AssertUtils.assertServiceException(() -> authService.authenticate(username, password), + assertServiceException(() -> authService.authenticate(username, password), AUTH_LOGIN_BAD_CREDENTIALS); verify(loginLogService).createLoginLog( argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) @@ -120,7 +139,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); // 调用, 并断言异常 - AssertUtils.assertServiceException(() -> authService.authenticate(username, password), + assertServiceException(() -> authService.authenticate(username, password), AUTH_LOGIN_USER_DISABLED); verify(loginLogService).createLoginLog( argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) @@ -129,82 +148,194 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { ); } -// @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()) -// && o.getResult().equals(LoginResultEnum.CAPTCHA_NOT_FOUND.getResult())) -// ); -// } + @Test + public void testLogin_success() { + // 准备参数 + AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> + o.setUsername("test_username").setPassword("test_password") + .setSocialType(randomEle(SocialTypeEnum.values()).getType())); -// @Test -// public void testCaptcha_codeError() { -// // 准备参数 -// AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); -// -// // mock 验证码不正确 -// String code = randomString(); -// when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code); -// -// // 调用, 并断言异常 -// assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR); -// // 校验调用参数 -// verify(loginLogService).createLoginLog( -// argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) -// && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult())) -// ); -// } + // mock 验证码正确 + ReflectUtil.setFieldValue(authService, "captchaEnable", false); + // mock user 数据 + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L).setUsername("test_username") + .setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(userService.getUserByUsername(eq("test_username"))).thenReturn(user); + // mock password 匹配 + when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true); + // mock 缓存登录用户到 Redis + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); + when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull())) + .thenReturn(accessTokenDO); -// @Test -// public void testLogin_success() { -// // 准备参数 -// AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> -// o.setUsername("test_username").setPassword("test_password")); -// -// // mock 验证码正确 -// when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); -// // mock user 数据 -// AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L).setUsername("test_username") -// .setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus())); -// when(userService.getUserByUsername(eq("test_username"))).thenReturn(user); -// // mock password 匹配 -// when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true); -// // mock 缓存登录用户到 Redis -// OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) -// .setUserType(UserTypeEnum.ADMIN.getValue())); -// when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull())) -// .thenReturn(accessTokenDO); -// -// // 调用, 并断言异常 -// AuthLoginRespVO loginRespVO = authService.login(reqVO); -// assertPojoEquals(accessTokenDO, loginRespVO); -// // 校验调用参数 -// verify(loginLogService).createLoginLog( -// argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) -// && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()) -// && o.getUserId().equals(user.getId())) -// ); -// } + // 调用,并校验 + AuthLoginRespVO loginRespVO = authService.login(reqVO); + assertPojoEquals(accessTokenDO, loginRespVO); + // 校验调用参数 + verify(loginLogService).createLoginLog( + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) + && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()) + && o.getUserId().equals(user.getId())) + ); + verify(socialUserService).bindSocialUser(eq(new SocialUserBindReqDTO( + user.getId(), UserTypeEnum.ADMIN.getValue(), + reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()))); + } + + @Test + public void testSendSmsCode() { + // 准备参数 + String mobile = randomString(); + Integer scene = randomEle(SmsSceneEnum.values()).getScene(); + AuthSmsSendReqVO reqVO = new AuthSmsSendReqVO(mobile, scene); + // mock 方法(用户信息) + AdminUserDO user = randomPojo(AdminUserDO.class); + when(userService.getUserByMobile(eq(mobile))).thenReturn(user); + + // 调用 + authService.sendSmsCode(reqVO); + // 断言 + verify(smsCodeApi).sendSmsCode(argThat(sendReqDTO -> { + assertEquals(mobile, sendReqDTO.getMobile()); + assertEquals(scene, sendReqDTO.getScene()); + return true; + })); + } + + @Test + public void testSmsLogin_success() { + // 准备参数 + String mobile = randomString(); + String scene = randomString(); + AuthSmsLoginReqVO reqVO = new AuthSmsLoginReqVO(mobile, scene); + // mock 方法(用户信息) + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L)); + when(userService.getUserByMobile(eq(mobile))).thenReturn(user); + // mock 缓存登录用户到 Redis + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); + when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull())) + .thenReturn(accessTokenDO); + + // 调用,并断言 + AuthLoginRespVO loginRespVO = authService.smsLogin(reqVO); + assertPojoEquals(accessTokenDO, loginRespVO); + // 断言调用 + verify(loginLogService).createLoginLog( + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_MOBILE.getType()) + && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()) + && o.getUserId().equals(user.getId())) + ); + } + + @Test + public void testSocialLogin_success() { + // 准备参数 + AuthSocialLoginReqVO reqVO = randomPojo(AuthSocialLoginReqVO.class); + // mock 方法(绑定的用户编号) + Long userId = 1L; + when(socialUserService.getBindUserId(eq(UserTypeEnum.ADMIN.getValue()), eq(reqVO.getType()), + eq(reqVO.getCode()), eq(reqVO.getState()))).thenReturn(userId); + // mock(用户) + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId)); + when(userService.getUser(eq(userId))).thenReturn(user); + // mock 缓存登录用户到 Redis + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) + .setUserType(UserTypeEnum.ADMIN.getValue())); + when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull())) + .thenReturn(accessTokenDO); + + // 调用,并断言 + AuthLoginRespVO loginRespVO = authService.socialLogin(reqVO); + assertPojoEquals(accessTokenDO, loginRespVO); + // 断言调用 + verify(loginLogService).createLoginLog( + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_SOCIAL.getType()) + && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()) + && o.getUserId().equals(user.getId())) + ); + } + + @Test + public void testValidateCaptcha_successWithEnable() { + // 准备参数 + AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); + + // mock 验证码打开 + ReflectUtil.setFieldValue(authService, "captchaEnable", true); + // mock 验证通过 + when(captchaService.verification(argThat(captchaVO -> { + assertEquals(reqVO.getCaptchaVerification(), captchaVO.getCaptchaVerification()); + return true; + }))).thenReturn(ResponseModel.success()); + + // 调用,无需断言 + authService.validateCaptcha(reqVO); + } + + @Test + public void testValidateCaptcha_successWithDisable() { + // 准备参数 + AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); + + // mock 验证码关闭 + ReflectUtil.setFieldValue(authService, "captchaEnable", false); + + // 调用,无需断言 + authService.validateCaptcha(reqVO); + } + + @Test + public void testValidateCaptcha_constraintViolationException() { + // 准备参数 + AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class).setCaptchaVerification(null); + + // mock 验证码打开 + ReflectUtil.setFieldValue(authService, "captchaEnable", true); + + // 调用,并断言异常 + assertThrows(ConstraintViolationException.class, () -> authService.validateCaptcha(reqVO), + "验证码不能为空"); + } + + + @Test + public void testCaptcha_fail() { + // 准备参数 + AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); + + // mock 验证码打开 + ReflectUtil.setFieldValue(authService, "captchaEnable", true); + // mock 验证通过 + when(captchaService.verification(argThat(captchaVO -> { + assertEquals(reqVO.getCaptchaVerification(), captchaVO.getCaptchaVerification()); + return true; + }))).thenReturn(ResponseModel.errorMsg("就是不对")); + + // 调用, 并断言异常 + assertServiceException(() -> authService.validateCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR, "就是不对"); + // 校验调用参数 + verify(loginLogService).createLoginLog( + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) + && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult())) + ); + } + + @Test + public void testRefreshToken() { + // 准备参数 + String refreshToken = randomString(); + // mock 方法 + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class); + when(oauth2TokenService.refreshAccessToken(eq(refreshToken), eq("default"))) + .thenReturn(accessTokenDO); + + // 调用 + AuthLoginRespVO loginRespVO = authService.refreshToken(refreshToken); + // 断言 + assertPojoEquals(accessTokenDO, loginRespVO); + } @Test public void testLogout_success() { @@ -221,6 +352,8 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { verify(loginLogService).createLoginLog(argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType()) && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())) ); + // 调用,并校验 + } @Test