diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/auth/AuthController.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/auth/AuthController.java index bcebf2a77..f75cee261 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/auth/AuthController.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/auth/AuthController.java @@ -3,7 +3,6 @@ package com.cf.imes.module.system.controller.admin.auth; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import com.cf.imes.framework.common.enums.CommonStatusEnum; -import com.cf.imes.framework.common.enums.UserTypeEnum; import com.cf.imes.framework.common.exception.ServiceException; import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants; import com.cf.imes.framework.common.pojo.CommonResult; @@ -18,7 +17,6 @@ import com.cf.imes.module.system.api.sms.dto.code.SmsCodeSendReqDTO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginSmsCheckReqVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthPermissionInfoRespVO; -import com.cf.imes.module.system.controller.admin.auth.vo.AuthSocialLoginReqVO; import com.cf.imes.module.system.convert.auth.AuthConvert; import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO; import com.cf.imes.module.system.dal.dataobject.permission.MenuDO; @@ -31,14 +29,11 @@ import com.cf.imes.module.system.service.permission.MenuService; import com.cf.imes.module.system.service.permission.PermissionService; import com.cf.imes.module.system.service.permission.RoleService; import com.cf.imes.module.system.service.sms.SmsCodeService; -import com.cf.imes.module.system.service.social.SocialClientService; import com.cf.imes.module.system.service.user.AdminUserService; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginRespVO; -import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsSendReqVO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -76,8 +71,6 @@ public class AuthController { private MenuService menuService; @Resource private PermissionService permissionService; - @Resource - private SocialClientService socialClientService; @Resource private SecurityProperties securityProperties; @@ -138,15 +131,6 @@ public class AuthController { return success(true); } - @PostMapping("/refresh-token") - @PermitAll - @Operation(summary = "刷新令牌") - @Parameter(name = "refreshToken", description = "刷新令牌", required = true) - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 - public CommonResult refreshToken(@RequestParam("refreshToken") String refreshToken) { - return success(authService.refreshToken(refreshToken)); - } - @GetMapping("/get-permission-info") @Operation(summary = "获取登录用户的权限信息") public CommonResult getPermissionInfo(HttpServletRequest request) { @@ -158,7 +142,7 @@ public class AuthController { // 1.1 获得用户信息 AdminUserDO user = userService.getUser(userId); if (user == null) { - return null; + return success(null); } // 获取机构的有效期 OrganizationDO organizationDO = organService.validOrgan(user.getOrganId()); @@ -192,14 +176,6 @@ public class AuthController { // ========== 短信登录相关 ========== - @PostMapping("/sms-login") - @PermitAll - @Operation(summary = "使用短信验证码登录") - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 - public CommonResult smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) { - return success(authService.smsLogin(reqVO)); - } - @PostMapping("/send-sms-code") @PermitAll @Operation(summary = "发送手机验证码") @@ -221,28 +197,9 @@ public class AuthController { return success(true); } - // ========== 社交登录相关 ========== - - @GetMapping("/social-auth-redirect") - @PermitAll - @Operation(summary = "社交授权的跳转") - @Parameters({ - @Parameter(name = "type", description = "社交类型", required = true), - @Parameter(name = "redirectUri", description = "回调路径") - }) - public CommonResult socialLogin(@RequestParam("type") Integer type, - @RequestParam("redirectUri") String redirectUri) { - return success(socialClientService.getAuthorizeUrl( - type, UserTypeEnum.ADMIN.getValue(), redirectUri)); + @GetMapping("/mes/token") + @Operation(summary = "创建mes系统jwt token") + public CommonResult getMesJwtToken() { + return success(authService.getMesJwtToken()); } - - - @PostMapping("/social-login") - @PermitAll - @Operation(summary = "社交快捷登录,使用 code 授权码", description = "适合未登录的用户,但是社交账号已绑定用户") - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 - public CommonResult socialQuickLogin(@RequestBody @Valid AuthSocialLoginReqVO reqVO) { - return success(authService.socialLogin(reqVO)); - } - } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/auth/vo/AuthLoginReqVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/auth/vo/AuthLoginReqVO.java index 1167d0600..54602d389 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/auth/vo/AuthLoginReqVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/auth/vo/AuthLoginReqVO.java @@ -1,20 +1,16 @@ package com.cf.imes.module.system.controller.admin.auth.vo; -import com.cf.imes.framework.common.validation.InEnum; -import com.cf.imes.module.system.enums.social.SocialTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.apache.commons.lang3.StringUtils; import org.hibernate.validator.constraints.Length; -import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Pattern; -@Schema(description = "管理后台 - 账号密码登录 Request VO,如果登录并绑定社交用户,需要传递 social 开头的参数") +@Schema(description = "管理后台 - 账号密码登录 Request VO") @Data @NoArgsConstructor @AllArgsConstructor @@ -39,30 +35,8 @@ public class AuthLoginReqVO { @NotEmpty(message = "验证码不能为空", groups = CodeEnableGroup.class) private String captchaVerification; - // ========== 绑定社交登录时,需要传递如下参数 ========== - - @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - @InEnum(SocialTypeEnum.class) - private Integer socialType; - - @Schema(description = "授权码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private String socialCode; - - @Schema(description = "state", requiredMode = Schema.RequiredMode.REQUIRED, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62") - private String socialState; - /** * 开启验证码的 Group */ public interface CodeEnableGroup {} - - @AssertTrue(message = "授权码不能为空") - public boolean isSocialCodeValid() { - return socialType == null || StringUtils.isNotEmpty(socialCode); - } - - @AssertTrue(message = "授权 state 不能为空") - public boolean isSocialState() { - return socialType == null || StringUtils.isNotEmpty(socialState); - } } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/tokenconfig/vo/JwtConfig.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/tokenconfig/vo/JwtConfig.java deleted file mode 100644 index f1b1215ae..000000000 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/tokenconfig/vo/JwtConfig.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.cf.imes.module.system.controller.admin.tokenconfig.vo; - -import lombok.Data; -import lombok.Getter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -@Component -@Getter -@Data -public class JwtConfig{ - - @Value("${jwt.secret}") - private String secret; - - -} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/chenfeng/config/ChenfengConfiguration.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/chenfeng/config/ChenfengConfiguration.java new file mode 100644 index 000000000..19ad68c6a --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/chenfeng/config/ChenfengConfiguration.java @@ -0,0 +1,15 @@ +package com.cf.imes.module.system.framework.chenfeng.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * chenfeng基础配置注册 + * + * @author Gqr + * @since 2026/1/9 11:53 + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(ChenfengProperties.class) +public class ChenfengConfiguration { +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/chenfeng/config/ChenfengProperties.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/chenfeng/config/ChenfengProperties.java new file mode 100644 index 000000000..5510b845b --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/chenfeng/config/ChenfengProperties.java @@ -0,0 +1,40 @@ +package com.cf.imes.module.system.framework.chenfeng.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +/** + * chenfeng属性 + * + * @author Gqr + * @since 2026/1/9 11:50 + */ +@ConfigurationProperties(prefix = "chenfeng") +@Validated +@Data +public class ChenfengProperties { + private Captcha captcha = new Captcha(); + private Gray gray = new Gray(); + + @Data + public static class Captcha { + /** + * 是否开启验证码 + */ + private Boolean enable = true; + } + + @Data + public static class Gray { + /** + * 当前灰度版本 + */ + private String version = "1.0.0"; + + /** + * 默认版本 + */ + private String defaultVersion = "1.0.0"; + } +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/chenfeng/package-info.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/chenfeng/package-info.java new file mode 100644 index 000000000..c2a32a93c --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/chenfeng/package-info.java @@ -0,0 +1 @@ +package com.cf.imes.module.system.framework.chenfeng; \ No newline at end of file diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/jwt/config/JwtConfiguration.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/jwt/config/JwtConfiguration.java new file mode 100644 index 000000000..911579653 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/jwt/config/JwtConfiguration.java @@ -0,0 +1,15 @@ +package com.cf.imes.module.system.framework.jwt.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * jwt配置注册 + * + * @author Gqr + * @since 2026/1/8 14:47 + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(JwtProperties.class) +public class JwtConfiguration { +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/jwt/config/JwtProperties.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/jwt/config/JwtProperties.java new file mode 100644 index 000000000..d9ae9de0f --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/jwt/config/JwtProperties.java @@ -0,0 +1,39 @@ +package com.cf.imes.module.system.framework.jwt.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import java.time.Duration; + +/** + * jwt属性 + * + * @author Gqr + * @since 2026/1/8 14:24 + */ +@ConfigurationProperties(prefix = "jwt") +@Validated +@Data +public class JwtProperties { + + /** + * imes密钥 + */ + private String secret; + + /** + * mes密钥 + */ + private String mesSecret; + + /** + * mes aud:jwt接受方 + */ + private String mesAud; + + /** + * 令牌有效时长 + */ + private Duration mesTokenTtl; +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/jwt/package-info.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/jwt/package-info.java new file mode 100644 index 000000000..f0628752d --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/jwt/package-info.java @@ -0,0 +1 @@ +package com.cf.imes.module.system.framework.jwt; \ No newline at end of file diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/auth/AdminAuthService.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/auth/AdminAuthService.java index f94239cdd..6007a988e 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/auth/AdminAuthService.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/auth/AdminAuthService.java @@ -3,9 +3,7 @@ package com.cf.imes.module.system.service.auth; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginRespVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginSmsCheckReqVO; -import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsSendReqVO; -import com.cf.imes.module.system.controller.admin.auth.vo.AuthSocialLoginReqVO; import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO; import jakarta.validation.Valid; @@ -66,27 +64,9 @@ public interface AdminAuthService { void sendSmsCode(AuthSmsSendReqVO reqVO); /** - * 短信登录 + * 获取 mes系统 JWT 令牌 * - * @param reqVO 登录信息 - * @return 登录结果 + * @return 令牌 */ - AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) ; - - /** - * 社交快捷登录,使用 code 授权码 - * - * @param reqVO 登录信息 - * @return 登录结果 - */ - AuthLoginRespVO socialLogin(@Valid AuthSocialLoginReqVO reqVO); - - /** - * 刷新访问令牌 - * - * @param refreshToken 刷新令牌 - * @return 登录结果 - */ - AuthLoginRespVO refreshToken(String refreshToken); - + String getMesJwtToken(); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/auth/AdminAuthServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/auth/AdminAuthServiceImpl.java index c7b9a6f50..ea43fb87b 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/auth/AdminAuthServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/auth/AdminAuthServiceImpl.java @@ -1,11 +1,13 @@ package com.cf.imes.module.system.service.auth; +import cn.hutool.core.util.BooleanUtil; import com.anji.captcha.model.common.ResponseModel; import com.anji.captcha.model.vo.CaptchaVO; import com.anji.captcha.service.CaptchaService; import com.cf.imes.framework.common.enums.CommonStatusEnum; import com.cf.imes.framework.common.enums.UserTypeEnum; import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants; import com.cf.imes.framework.common.util.monitor.TracerUtils; import com.cf.imes.framework.common.util.servlet.ServletUtils; import com.cf.imes.framework.common.util.validation.ValidationUtils; @@ -13,17 +15,15 @@ import com.cf.imes.framework.ip.core.service.IPQueryService; import com.cf.imes.framework.ip.core.service.dto.IPQueryDataRespDTO; import com.cf.imes.framework.organ.core.context.OrganContextHolder; import com.cf.imes.framework.organ.core.service.OrganFrameworkService; +import com.cf.imes.framework.security.core.LoginUser; import com.cf.imes.framework.security.core.service.SecurityFrameworkService; import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; import com.cf.imes.module.system.api.logger.dto.LoginLogCreateReqDTO; import com.cf.imes.module.system.api.sms.SmsCodeApi; -import com.cf.imes.module.system.api.social.dto.SocialUserRespDTO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginRespVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginSmsCheckReqVO; -import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsSendReqVO; -import com.cf.imes.module.system.controller.admin.auth.vo.AuthSocialLoginReqVO; import com.cf.imes.module.system.convert.auth.AuthConvert; import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO; @@ -31,29 +31,35 @@ import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO; import com.cf.imes.module.system.enums.logger.LoginLogTypeEnum; import com.cf.imes.module.system.enums.logger.LoginResultEnum; import com.cf.imes.module.system.enums.oauth2.OAuth2ClientConstants; -import com.cf.imes.module.system.enums.sms.SmsSceneEnum; +import com.cf.imes.module.system.framework.chenfeng.config.ChenfengProperties; +import com.cf.imes.module.system.framework.jwt.config.JwtProperties; import com.cf.imes.module.system.framework.product.ProductProperties; import com.cf.imes.module.system.service.dept.DeptService; import com.cf.imes.module.system.service.logger.LoginLogService; -import com.cf.imes.module.system.service.member.MemberService; import com.cf.imes.module.system.service.oauth2.OAuth2TokenService; import com.cf.imes.module.system.service.sms.SmsCodeService; -import com.cf.imes.module.system.service.social.SocialUserService; import com.cf.imes.module.system.service.organ.OrganService; import com.cf.imes.module.system.service.user.AdminUserService; import com.cf.imes.module.system.enums.ErrorCodeConstants; import com.google.common.annotations.VisibleForTesting; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.JwsHeader; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Service; import jakarta.annotation.Resource; import jakarta.validation.Validator; import org.springframework.transaction.support.TransactionTemplate; +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.Objects; import static com.cf.imes.framework.common.util.servlet.ServletUtils.getClientIP; @@ -66,7 +72,6 @@ import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORGAN_DATA_CODE */ @Service @Slf4j -@RefreshScope public class AdminAuthServiceImpl implements AdminAuthService { @Resource @@ -76,10 +81,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Resource private OAuth2TokenService oauth2TokenService; @Resource - private SocialUserService socialUserService; - @Resource - private MemberService memberService; - @Resource private Validator validator; @Resource private CaptchaService captchaService; @@ -108,17 +109,11 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Resource private ProductProperties productProperties; - /** - * 验证码的开关,默认为 true - */ - @Value("${chenfeng.captcha.enable:true}") - private Boolean captchaEnable; + @Resource + private JwtProperties jwtProperties; - @Value("${chenfeng.gray.version:1.0.0}") - private String grayVersion; - - @Value("${chenfeng.gray.default-version:1.0.0}") - private String grayDefaultVersion; + @Resource + private ChenfengProperties chenfengProperties; @Override public AdminUserDO authenticate(AuthLoginReqVO reqVO) { @@ -170,10 +165,10 @@ public class AdminAuthServiceImpl implements AdminAuthService { if (isSuperAdmin || SecurityFrameworkUtils.isCfOrg(user.getOrganId())) { user.setUserType(isManageEndPoint ? UserTypeEnum.ADMIN : UserTypeEnum.MEMBER); } else { + user.setUserType(UserTypeEnum.MEMBER); if (isManageEndPoint) { throw new ServiceException(ErrorCodeConstants.AUTH_MANAGEENDPOINT_LOGIN_PERMISSION_ERROR); } - user.setUserType(UserTypeEnum.MEMBER); } Long organId = user.getOrganId(); @@ -185,10 +180,10 @@ public class AdminAuthServiceImpl implements AdminAuthService { } if (CommonStatusEnum.ENABLE.getStatus().equals(organ.getGrayStatus())) { // 机构启用灰度、版本赋值到user - user.setGrayVersion(grayVersion); + user.setGrayVersion(chenfengProperties.getGray().getVersion()); } else { // 未启用灰度的机构需要默认版本号来做网关实例选择 - user.setGrayDefaultVersion(grayDefaultVersion); + user.setGrayDefaultVersion(chenfengProperties.getGray().getDefaultVersion()); } // 创建 Token 令牌,记录登录日志 return createTokenAfterLoginSuccess(user, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, dataSourceCode); @@ -234,28 +229,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP())); } - @Override - public AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) { - // 校验验证码 - smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), getClientIP())); - - // 获得用户信息 - AdminUserDO user = userService.getUserByMobile(reqVO.getMobile()); - if (user == null) { - throw new ServiceException(ErrorCodeConstants.USER_NOT_EXISTS); - } - Long organId = user.getOrganId(); - OrganizationDO organ = organService.getOrgan(organId); - String dataSourceCode = organ.getDataSourceCode(); - if(StringUtils.isBlank(dataSourceCode)) { - throw new ServiceException(ORGAN_DATA_CODE_NOT_EXISTS); - } - - // 创建 Token 令牌,记录登录日志 - return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE, dataSourceCode); - } - - private void createLoginLog(Long userId, String username, UserTypeEnum userTypeEnum, Long organId, + protected void createLoginLog(Long userId, String username, UserTypeEnum userTypeEnum, Long organId, LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) { // 手动控制事务 transactionTemplate.executeWithoutResult(status -> { @@ -279,7 +253,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { } loginLogService.createLoginLog(reqDTO); // 更新最后登录时间 - if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) { + if (BooleanUtil.and(userId != null, Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult()))) { userService.updateUserLogin(userId, clientIP); } } catch (Exception e) { @@ -289,36 +263,10 @@ public class AdminAuthServiceImpl implements AdminAuthService { }); } - @Override - public AuthLoginRespVO socialLogin(AuthSocialLoginReqVO reqVO) { - // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 - SocialUserRespDTO socialUser = socialUserService.getSocialUserByCode(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), - reqVO.getCode(), reqVO.getState()); - if (socialUser == null || socialUser.getUserId() == null) { - throw new ServiceException(ErrorCodeConstants.AUTH_THIRD_LOGIN_NOT_BIND); - } - - // 获得用户 - AdminUserDO user = userService.getUser(socialUser.getUserId()); - if (user == null) { - throw new ServiceException(ErrorCodeConstants.USER_NOT_EXISTS); - } - - Long organId = user.getOrganId(); - OrganizationDO organ = organService.getOrgan(organId); - String dataSourceCode = organ.getDataSourceCode(); - if(StringUtils.isBlank(dataSourceCode)) { - throw new ServiceException(ORGAN_DATA_CODE_NOT_EXISTS); - } - - // 创建 Token 令牌,记录登录日志 - return createTokenAfterLoginSuccess(user, user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL, dataSourceCode); - } - @VisibleForTesting void validateCaptcha(AuthLoginReqVO reqVO) { // 如果验证码关闭,则不进行校验 - if (!captchaEnable) { + if (!chenfengProperties.getCaptcha().getEnable()) { return; } // 校验验证码 @@ -352,12 +300,6 @@ 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) { // 删除访问令牌 @@ -366,18 +308,18 @@ public class AdminAuthServiceImpl implements AdminAuthService { return; } // 删除成功,则记录登出日志 - createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType); + createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getNickname(), accessTokenDO.getUserType(), logType); } - private void createLogoutLog(Long userId, Integer userType, Integer logType) { + protected void createLogoutLog(Long userId, String userName, Integer userType, Integer logType) { String clientIP = getClientIP(); LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); reqDTO.setLogType(logType); reqDTO.setTraceId(TracerUtils.getTraceId()); reqDTO.setUserId(userId); reqDTO.setUserType(userType); - reqDTO.setUsername(getUsername(userId)); - // 设置登录时通过ip云服务查询到的地域信息 + reqDTO.setUsername(userName); + // 设置登出时通过ip云服务查询到的地域信息 if (ipQueryService.serviceEnable()) { IPQueryDataRespDTO ipQueryDataRespDTO = ipQueryService.querySource(clientIP); reqDTO.setRegion(StringUtils.join(ipQueryDataRespDTO.getProv(), ipQueryDataRespDTO.getCity(), ipQueryDataRespDTO.getArea())); @@ -388,16 +330,40 @@ public class AdminAuthServiceImpl implements AdminAuthService { loginLogService.createLoginLog(reqDTO); } - private String getUsername(Long userId) { - if (userId == null) { - return null; - } - AdminUserDO user = userService.getUser(userId); - return user != null ? user.getUsername() : null; - } - private UserTypeEnum getUserType() { return UserTypeEnum.ADMIN; } + + @Override + public String getMesJwtToken() { + // 当前登录用户 + LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); + if (loginUser == null) { + throw new ServiceException(GlobalErrorCodeConstants.UNAUTHORIZED); + } + + // 计算超时时间 + Instant now = Instant.now(); + long nbf = now.getEpochSecond(); + long exp = now.plus(jwtProperties.getMesTokenTtl()).getEpochSecond(); + + // 生产token + SecretKey secretKey = Keys.hmacShaKeyFor(jwtProperties.getMesSecret().getBytes(StandardCharsets.UTF_8)); + String aud = jwtProperties.getMesAud(); + SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; + + return Jwts.builder() + .setHeaderParam(JwsHeader.ALGORITHM, signatureAlgorithm.getValue()) + .setHeaderParam(JwsHeader.KEY_ID, aud) + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .claim("userId",loginUser.getId()) + .claim("companyId", loginUser.getOrganId()) + .claim("role","") + .claim(Claims.AUDIENCE, aud) + .claim(Claims.NOT_BEFORE, nbf) + .claim(Claims.EXPIRATION, exp) + .signWith(secretKey, signatureAlgorithm) + .compact(); + } } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/organ/OrganServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/organ/OrganServiceImpl.java index 4bef85488..db846eee7 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/organ/OrganServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/organ/OrganServiceImpl.java @@ -33,7 +33,7 @@ import com.cf.imes.module.system.controller.admin.organ.vo.organ.OrganSimpleResp import com.cf.imes.module.system.controller.admin.statistics.vo.ManageOrgTotalStatisticRespVO; import com.cf.imes.module.system.controller.admin.statistics.vo.OrgStatusGroupStatisticsReqVO; import com.cf.imes.module.system.controller.admin.systemconfig.vo.ProcessSchemeConfig; -import com.cf.imes.module.system.controller.admin.tokenconfig.vo.JwtConfig; +import com.cf.imes.module.system.framework.jwt.config.JwtProperties; import com.cf.imes.module.system.controller.admin.user.vo.user.UserSaveReqVO; import com.cf.imes.module.system.convert.organ.OrganConvert; import com.cf.imes.module.system.dal.dataobject.dict.DictDataDO; @@ -127,7 +127,7 @@ public class OrganServiceImpl implements OrganService { private TokenConfigMapper tokenConfigMapper; @Resource - private JwtConfig jwtConfig; + private JwtProperties jwtProperties; @Resource private StringRedisTemplate stringRedisTemplate; @@ -290,7 +290,7 @@ public class OrganServiceImpl implements OrganService { List tokenConfigDOS = tokenConfigMapper.selectConfigByOrganId(organId); if(CollUtil.isNotEmpty(tokenConfigDOS)) { tokenConfigDOS.forEach(f -> { - f.setAppToken(generateBaseToken(organId, updateReqVO.getOrgAdminMobile(), f.getAppType(), jwtConfig.getSecret())); + f.setAppToken(generateBaseToken(organId, updateReqVO.getOrgAdminMobile(), f.getAppType(), jwtProperties.getSecret())); }); tokenConfigMapper.updateBatch(tokenConfigDOS); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/tokenconfig/TokenConfigServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/tokenconfig/TokenConfigServiceImpl.java index 04c5d1472..1547877b8 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/tokenconfig/TokenConfigServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/tokenconfig/TokenConfigServiceImpl.java @@ -10,7 +10,7 @@ import com.cf.imes.framework.common.util.Assert.AssertUtils; import com.cf.imes.framework.common.util.date.DateUtils; import com.cf.imes.framework.common.util.date.LocalDateTimeUtils; import com.cf.imes.framework.common.util.object.BeanUtils; -import com.cf.imes.module.system.controller.admin.tokenconfig.vo.JwtConfig; +import com.cf.imes.module.system.framework.jwt.config.JwtProperties; import com.cf.imes.module.system.controller.admin.tokenconfig.vo.TokenConfigPageReqVO; import com.cf.imes.module.system.controller.admin.tokenconfig.vo.TokenConfigRespVO; import com.cf.imes.module.system.controller.admin.tokenconfig.vo.TokenConfigSaveReqVO; @@ -58,7 +58,7 @@ public class TokenConfigServiceImpl implements TokenConfigService{ @Resource - private JwtConfig jwtConfig; + private JwtProperties jwtProperties; private static final String FIELD_IMES = "imes-"; @@ -123,7 +123,7 @@ public class TokenConfigServiceImpl implements TokenConfigService{ throw new ServiceException(TOKEN_APP_TYPE_EXIST); } - String token = generateBaseToken(organId, organizationDO.getContactMobile(), reqVO.getAppType(), jwtConfig.getSecret()); + String token = generateBaseToken(organId, organizationDO.getContactMobile(), reqVO.getAppType(), jwtProperties.getSecret()); LocalDateTime adjustedTime; diff --git a/cf-module-system/cf-module-system-biz/src/main/resources/application-dev.yaml b/cf-module-system/cf-module-system-biz/src/main/resources/application-dev.yaml index 683cb85f5..29887192c 100644 --- a/cf-module-system/cf-module-system-biz/src/main/resources/application-dev.yaml +++ b/cf-module-system/cf-module-system-biz/src/main/resources/application-dev.yaml @@ -333,6 +333,9 @@ justauth: # JWT 密钥配置 jwt: secret: BjEM0tnL3W5zYLQ6pllol49uYVXe+f66pHyOM/tkGWg= + mes-secret: 0d118b98602a332bd966bffa47b294c1 + mes-aud: iMES + mes-token-ttl: 10m --- #################### 验证码相关配置 #################### diff --git a/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml b/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml index 3cfec519c..6cc27883b 100644 --- a/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml +++ b/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml @@ -354,6 +354,9 @@ justauth: # JWT 密钥配置 jwt: secret: BjEM0tnL3W5zYLQ6pllol49uYVXe+f66pHyOM/tkGWg= + mes-secret: 0d118b98602a332bd966bffa47b294c1 + mes-aud: iMES + mes-token-ttl: 10m --- #################### 验证码相关配置 #################### diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/auth/AuthControllerTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/auth/AuthControllerTest.java new file mode 100644 index 000000000..9b8ae2285 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/auth/AuthControllerTest.java @@ -0,0 +1,436 @@ +package com.cf.imes.module.system.controller.admin.auth; + +import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants; +import com.cf.imes.framework.ip.core.service.IPQueryService; +import com.cf.imes.framework.ip.core.service.dto.IPQueryDataRespDTO; +import com.cf.imes.framework.security.config.SecurityProperties; +import com.cf.imes.framework.security.core.LoginUser; +import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; +import com.cf.imes.framework.test.core.ut.BaseWebUnitTest; +import com.cf.imes.framework.web.config.ChenfengWebAutoConfiguration; +import com.cf.imes.module.infra.api.logger.ApiErrorLogApi; +import com.cf.imes.module.system.api.sms.dto.code.SmsCodeSendReqDTO; +import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO; +import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginRespVO; +import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginSmsCheckReqVO; +import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsSendReqVO; +import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO; +import com.cf.imes.module.system.dal.dataobject.permission.MenuDO; +import com.cf.imes.module.system.dal.dataobject.permission.RoleDO; +import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO; +import com.cf.imes.module.system.enums.logger.LoginLogTypeEnum; +import com.cf.imes.module.system.enums.sms.SmsSceneEnum; +import com.cf.imes.module.system.service.auth.AdminAuthService; +import com.cf.imes.module.system.service.organ.OrganService; +import com.cf.imes.module.system.service.permission.MenuService; +import com.cf.imes.module.system.service.permission.PermissionService; +import com.cf.imes.module.system.service.permission.RoleService; +import com.cf.imes.module.system.service.sms.SmsCodeService; +import com.cf.imes.module.system.service.user.AdminUserService; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * + * @author Gqr + * @since 2026/1/12 16:50 + */ +@WebMvcTest(controllers = AuthController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) +@Import(ChenfengWebAutoConfiguration.class) +@TestPropertySource(properties = { + "spring.application.name=test-app" +}) +class AuthControllerTest extends BaseWebUnitTest { + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private AdminAuthService authService; + + @MockitoBean + private AdminUserService userService; + + @MockitoBean + private RoleService roleService; + + @MockitoBean + private MenuService menuService; + + @MockitoBean + private PermissionService permissionService; + + @MockitoBean + private SecurityProperties securityProperties; + + @MockitoBean + private SmsCodeService smsCodeService; + + @MockitoBean + private OrganService organService; + + @MockitoBean + private IPQueryService ipQueryService; + + @MockitoBean + private ApiErrorLogApi apiErrorLogApi; + + @Autowired + private ObjectMapper objectMapper; + + private AuthLoginReqVO reqVO; + private AuthLoginRespVO respVO; + private AuthLoginSmsCheckReqVO smsCheckReq; + private AuthSmsSendReqVO smsSendReqVO; + private SmsCodeSendReqDTO smsCodeSendReqDTO; + private LoginUser loginUser; + + @BeforeEach + void setUp() { + reqVO = new AuthLoginReqVO(); + reqVO.setUsername("testuser"); + reqVO.setPassword("12345678"); + + respVO = new AuthLoginRespVO(); + respVO.setAccessToken("fake-token"); + + smsCheckReq = new AuthLoginSmsCheckReqVO(); + smsCheckReq.setMobile("13800000000"); + smsCheckReq.setSmsCaptchaVerification("123456"); + + smsSendReqVO = new AuthSmsSendReqVO(); + smsSendReqVO.setMobile("13800000000"); + smsSendReqVO.setScene(SmsSceneEnum.USER_LOGIN_CAPTCHAVERIFICATION.getScene()); + + smsCodeSendReqDTO = new SmsCodeSendReqDTO(); + smsCodeSendReqDTO.setMobile("13800000000"); + smsCodeSendReqDTO.setScene(SmsSceneEnum.USER_LOGIN_CAPTCHAVERIFICATION.getScene()); + + loginUser = new LoginUser(); + loginUser.setId(1L); + loginUser.setOrganId(100L); + } + + @Test + void testManageEndPointLogin() throws Exception { + when(authService.login(reqVO, true)).thenReturn(respVO); + + mockMvc.perform(post("/admin-api/system/auth/manage/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.accessToken").value("fake-token")); + + verify(authService, times(1)).login(reqVO, true); + } + + @Test + void testLogin() throws Exception { + when(authService.login(reqVO, false)).thenReturn(respVO); + + mockMvc.perform(post("/admin-api/system/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.accessToken").value("fake-token")); + + verify(authService, times(1)).login(reqVO, false); + } + + @Test + void testLoginCheck() throws Exception { + when(authService.loginCheck("13800000000")).thenReturn(true); + + mockMvc.perform(get("/admin-api/system/auth/login/check") + .param("mobile", "13800000000")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + + verify(authService, times(1)).loginCheck("13800000000"); + } + + @Test + void testLoginSmsCheck() throws Exception { + when(authService.loginSmsCheck(smsCheckReq)).thenReturn(true); + + mockMvc.perform(post("/admin-api/system/auth/login/sms/check") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(smsCheckReq))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + + verify(authService, times(1)).loginSmsCheck(smsCheckReq); + } + + @Test + void testLogout_withToken() throws Exception { + // mock token header/parameter + when(securityProperties.getTokenHeader()).thenReturn("Authorization"); + when(securityProperties.getTokenParameter()).thenReturn("token"); + + // 模拟 SecurityFrameworkUtils.obtainAuthorization 静态方法 + try (MockedStatic utilities = mockStatic(SecurityFrameworkUtils.class)) { + utilities.when(() -> SecurityFrameworkUtils.obtainAuthorization(any(HttpServletRequest.class), + eq("Authorization"), eq("token"))) + .thenReturn("fake-token"); + + mockMvc.perform(post("/admin-api/system/auth/logout")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + + verify(authService, times(1)).logout("fake-token", LoginLogTypeEnum.LOGOUT_SELF.getType()); + } + } + + @Test + void testLogout_withoutToken() throws Exception { + when(securityProperties.getTokenHeader()).thenReturn("Authorization"); + when(securityProperties.getTokenParameter()).thenReturn("token"); + + try (MockedStatic utilities = mockStatic(SecurityFrameworkUtils.class)) { + utilities.when(() -> SecurityFrameworkUtils.obtainAuthorization(any(HttpServletRequest.class), + eq("Authorization"), eq("token"))) + .thenReturn(null); // 没有 token + + mockMvc.perform(post("/admin-api/system/auth/logout")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + + // 没有 token 时,不调用 logout + verify(authService, times(0)).logout(anyString(), anyInt()); + } + } + + @Test + void testSendLoginSmsCode() throws Exception { + // authService.sendSmsCode 是 void + doNothing().when(authService).sendSmsCode(smsSendReqVO); + + mockMvc.perform(post("/admin-api/system/auth/send-sms-code") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(smsSendReqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + + verify(authService, times(1)).sendSmsCode(smsSendReqVO); + } + + @Test + void testSendSmsCode() throws Exception { + // smsCodeService.sendSmsCode 是 void + doNothing().when(smsCodeService).sendSmsCode(any()); + + mockMvc.perform(put("/admin-api/system/auth/sms-code") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(smsCodeSendReqDTO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(true)); + + // 验证 sendSmsCode 被调用 + ArgumentCaptor captor = ArgumentCaptor.forClass(SmsCodeSendReqDTO.class); + verify(smsCodeService, times(1)).sendSmsCode(captor.capture()); + + SmsCodeSendReqDTO sent = captor.getValue(); + assertEquals("13800000000", sent.getMobile()); + assertNotNull(sent.getCreateIp()); // createIp 已被设置 + } + + @Test + void testGetMesJwtToken() throws Exception { + when(authService.getMesJwtToken()).thenReturn("fake-jwt-token"); + + mockMvc.perform(get("/admin-api/system/auth/mes/token")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value("fake-jwt-token")); + + verify(authService, times(1)).getMesJwtToken(); + } + + @Test + void testGetPermissionInfo_success() throws Exception { + try (MockedStatic utilities = mockStatic(SecurityFrameworkUtils.class)) { + utilities.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); + utilities.when(SecurityFrameworkUtils::isManageEndPoint).thenReturn(false); + + AdminUserDO user = new AdminUserDO(); + user.setId(1L); + user.setOrganId(100L); + when(userService.getUser(1L)).thenReturn(user); + + OrganizationDO organization = new OrganizationDO(); + when(organService.validOrgan(100L)).thenReturn(organization); + + when(ipQueryService.serviceEnable()).thenReturn(false); + + Set roleIds = new HashSet<>(Arrays.asList(10L, 20L)); + when(permissionService.getUserRoleIdListByUserId(1L)).thenReturn(roleIds); + + RoleDO role1 = new RoleDO(); role1.setId(10L); role1.setStatus(1); + RoleDO role2 = new RoleDO(); role2.setId(20L); role2.setStatus(1); + when(roleService.getRoleList1(roleIds)) + .thenReturn(new ArrayList<>(Arrays.asList(role1, role2))); + + Set menuIds = new HashSet<>(Arrays.asList(1000L, 2000L)); + when(permissionService.getRoleMenuListByRoleId2(anySet(), eq(100L))).thenReturn(menuIds); + + MenuDO menu1 = new MenuDO(); menu1.setId(1000L); menu1.setStatus(1); + MenuDO menu2 = new MenuDO(); menu2.setId(2000L); menu2.setStatus(1); + when(menuService.getCustomEndPointMenuList(menuIds)) + .thenReturn(new ArrayList<>(Arrays.asList(menu1, menu2))); + + mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").exists()); + + // 验证调用顺序/次数(可选) + verify(userService, times(1)).getUser(1L); + verify(organService, times(1)).validOrgan(100L); + verify(roleService, times(1)).getRoleList1(roleIds); + verify(menuService, times(1)).getCustomEndPointMenuList(menuIds); + } + } + + @Test + void testGetPermissionInfo_success_branch() throws Exception { + try (MockedStatic utilities = mockStatic(SecurityFrameworkUtils.class)) { + utilities.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); + utilities.when(SecurityFrameworkUtils::isManageEndPoint).thenReturn(true); + + AdminUserDO user = new AdminUserDO(); + user.setId(1L); + user.setOrganId(100L); + when(userService.getUser(1L)).thenReturn(user); + + OrganizationDO organization = new OrganizationDO(); + when(organService.validOrgan(100L)).thenReturn(organization); + + IPQueryDataRespDTO ipResp = new IPQueryDataRespDTO(); + ipResp.setProv("浙江"); + ipResp.setCity("杭州"); + ipResp.setArea("西湖"); + when(ipQueryService.serviceEnable()).thenReturn(true); + when(ipQueryService.querySource(anyString())).thenReturn(ipResp); + + Set roleIds = new HashSet<>(Arrays.asList(10L, 20L)); + when(permissionService.getUserRoleIdListByUserId(1L)).thenReturn(roleIds); + + RoleDO role1 = new RoleDO(); role1.setId(10L); role1.setStatus(1); + RoleDO role2 = new RoleDO(); role2.setId(20L); role2.setStatus(1); + when(roleService.getRoleList1(roleIds)) + .thenReturn(new ArrayList<>(Arrays.asList(role1, role2))); + + Set menuIds = new HashSet<>(Arrays.asList(1000L, 2000L)); + when(permissionService.getRoleMenuListByRoleId2(anySet(), eq(100L))).thenReturn(menuIds); + + MenuDO menu1 = new MenuDO(); menu1.setId(1000L); menu1.setStatus(1); + MenuDO menu2 = new MenuDO(); menu2.setId(2000L); menu2.setStatus(1); + when(menuService.getManageEndPointMenuList(menuIds)) + .thenReturn(new ArrayList<>(Arrays.asList(menu1, menu2))); + + mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").exists()); + + // 验证调用顺序/次数(可选) + verify(userService, times(1)).getUser(1L); + verify(organService, times(1)).validOrgan(100L); + verify(ipQueryService, times(1)).querySource(anyString()); + verify(roleService, times(1)).getRoleList1(roleIds); + verify(menuService, times(1)).getManageEndPointMenuList(menuIds); + } + } + + @Test + void testGetPermissionInfo_success_roleEmpty() throws Exception { + try (MockedStatic utilities = mockStatic(SecurityFrameworkUtils.class)) { + utilities.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); + utilities.when(SecurityFrameworkUtils::isManageEndPoint).thenReturn(true); + + AdminUserDO user = new AdminUserDO(); + user.setId(1L); + user.setOrganId(100L); + when(userService.getUser(1L)).thenReturn(user); + + OrganizationDO organization = new OrganizationDO(); + when(organService.validOrgan(100L)).thenReturn(organization); + + when(ipQueryService.serviceEnable()).thenReturn(false); + + Set roleIds = new HashSet<>(); + when(permissionService.getUserRoleIdListByUserId(1L)).thenReturn(roleIds); + + mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.roles").isEmpty()); + + // 验证调用顺序/次数(可选) + verify(userService, times(1)).getUser(1L); + verify(organService, times(1)).validOrgan(100L); + verify(ipQueryService, never()).querySource(anyString()); + verify(roleService, never()).getRoleList1(roleIds); + verify(menuService, never()).getManageEndPointMenuList(anySet()); + } + } + + @Test + void testGetPermissionInfo_userNull() throws Exception { + try (MockedStatic utilities = mockStatic(SecurityFrameworkUtils.class)) { + utilities.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); + + when(userService.getUser(1L)).thenReturn(null); + + mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").isEmpty()); // 返回 null + + verify(userService, times(1)).getUser(1L); + } + } + + @Test + void testGetPermissionInfo_noLoginUser() throws Exception { + try (MockedStatic utilities = mockStatic(SecurityFrameworkUtils.class)) { + utilities.when(SecurityFrameworkUtils::getLoginUser).thenReturn(null); + + mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(GlobalErrorCodeConstants.UNAUTHORIZED.getCode())); + } + } +} diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/auth/AdminAuthServiceImplTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/auth/AdminAuthServiceImplTest.java index 008a510bb..32351b5f3 100644 --- a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/auth/AdminAuthServiceImplTest.java +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/auth/AdminAuthServiceImplTest.java @@ -1,242 +1,531 @@ package com.cf.imes.module.system.service.auth; -import cn.hutool.core.util.ReflectUtil; import com.anji.captcha.model.common.ResponseModel; import com.anji.captcha.service.CaptchaService; import com.cf.imes.framework.common.enums.CommonStatusEnum; import com.cf.imes.framework.common.enums.UserTypeEnum; +import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants; +import com.cf.imes.framework.common.pojo.CommonResult; +import com.cf.imes.framework.ip.core.service.IPQueryService; +import com.cf.imes.framework.ip.core.service.dto.IPQueryDataRespDTO; +import com.cf.imes.framework.organ.core.service.OrganFrameworkService; +import com.cf.imes.framework.security.core.service.SecurityFrameworkService; +import com.cf.imes.framework.security.test.WithMockLoginUser; import com.cf.imes.framework.test.core.ut.BaseDbUnitTest; import com.cf.imes.module.system.api.sms.SmsCodeApi; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO; -import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginRespVO; +import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginSmsCheckReqVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsSendReqVO; import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO; import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO; import com.cf.imes.module.system.enums.logger.LoginLogTypeEnum; import com.cf.imes.module.system.enums.logger.LoginResultEnum; -import com.cf.imes.module.system.enums.sms.SmsSceneEnum; +import com.cf.imes.module.system.framework.chenfeng.config.ChenfengProperties; +import com.cf.imes.module.system.framework.jwt.config.JwtProperties; +import com.cf.imes.module.system.framework.product.ProductProperties; +import com.cf.imes.module.system.service.dept.DeptService; import com.cf.imes.module.system.service.logger.LoginLogService; import com.cf.imes.module.system.service.oauth2.OAuth2TokenService; +import com.cf.imes.module.system.service.organ.OrganService; +import com.cf.imes.module.system.service.sms.SmsCodeService; import com.cf.imes.module.system.service.user.AdminUserService; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.validation.Validator; 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 jakarta.annotation.Resource; -import jakarta.validation.ConstraintViolationException; -import jakarta.validation.Validation; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Collections; +import java.util.function.Consumer; -import static cn.hutool.core.util.RandomUtil.randomEle; -import static com.cf.imes.framework.test.core.util.AssertUtils.assertPojoEquals; import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceException; import static com.cf.imes.framework.test.core.util.RandomUtils.randomPojo; import static com.cf.imes.framework.test.core.util.RandomUtils.randomString; import static com.cf.imes.module.system.enums.ErrorCodeConstants.*; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; -@Import(AdminAuthServiceImpl.class) +@Import({AdminAuthServiceImpl.class, ChenfengProperties.class}) public class AdminAuthServiceImplTest extends BaseDbUnitTest { @Resource private AdminAuthServiceImpl authService; - @MockBean + @MockitoBean private AdminUserService userService; - @MockBean + + @MockitoBean private CaptchaService captchaService; - @MockBean + + @MockitoBean private LoginLogService loginLogService; - @MockBean + @MockitoBean private SmsCodeApi smsCodeApi; - @MockBean + + @MockitoBean private OAuth2TokenService oauth2TokenService; + @MockitoBean + private OrganService organService; + + @MockitoBean + private OrganFrameworkService organFrameworkService; + + @MockitoBean + private SmsCodeService smsCodeService; + + @MockitoBean + private IPQueryService ipQueryService; + + @MockitoBean + private TransactionTemplate transactionTemplate; + + @MockitoBean + private SecurityFrameworkService securityFrameworkService; + + @MockitoBean + private DeptService deptService; + + @MockitoBean + private ProductProperties productProperties; + + @MockitoBean + private JwtProperties jwtProperties; + + @MockitoBean + private ChenfengProperties chenfengProperties; + + @MockitoBean + private Validator validator; + + private AuthLoginReqVO reqVO; + @BeforeEach - public void setUp() { - ReflectUtil.setFieldValue(authService, "captchaEnable", true); - // 注入一个 Validator 对象 - ReflectUtil.setFieldValue(authService, "validator", - Validation.buildDefaultValidatorFactory().getValidator()); + void setUp() { + reqVO = new AuthLoginReqVO(); + reqVO.setUsername("admin"); + reqVO.setPassword("123456"); + } + + private AdminUserDO buildUser(){ + AdminUserDO user = new AdminUserDO(); + user.setId(1L); + user.setUsername("admin"); + user.setPassword("encodedPwd"); + user.setOrganId(10L); + user.setDeptId(100L); + user.setStatus(CommonStatusEnum.ENABLE.getStatus()); + return user; + } + + private AuthLoginReqVO buildLoginReqVO() { + AuthLoginReqVO reqVO = new AuthLoginReqVO(); + reqVO.setUsername("admin"); + reqVO.setPassword("123456"); + return reqVO; } @Test - public void testAuthenticate_success() { - // 准备参数 - 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), null)).thenReturn(user); - // mock password 匹配 - when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); + void authenticate_userNotExists_throwBadCredentials() { + // mock + when(userService.getUserUniqueByUserName("admin")) + .thenReturn(null); - // 调用 - AdminUserDO loginUser = authService.authenticate(AuthLoginReqVO.builder().username(username).password(password).build()); - // 校验 - assertPojoEquals(user, loginUser); - } - - @Test - public void testAuthenticate_userNotFound() { - // 准备参数 - String username = randomString(); - String password = randomString(); - - // 调用, 并断言异常 - assertServiceException(() -> authService.authenticate(AuthLoginReqVO.builder().username(username).password(password).build()), + assertServiceException(() -> authService.authenticate(reqVO), 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) - ); + + + verify(userService).getUserUniqueByUserName("admin"); } @Test - public void testAuthenticate_badCredentials() { - // 准备参数 - 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), null)).thenReturn(user); + void authenticate_passwordNotMatch_throwBadCredentials() { + AdminUserDO user = new AdminUserDO(); - // 调用, 并断言异常 - assertServiceException(() -> authService.authenticate(AuthLoginReqVO.builder().username(username).password(password).build()), + when(userService.getUserUniqueByUserName("admin")) + .thenReturn(user); + when(userService.isPasswordMatch("123456", "encodedPwd")) + .thenReturn(false); + + assertServiceException(() -> authService.authenticate(reqVO), 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 testAuthenticate_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), null)).thenReturn(user); - // mock password 匹配 - when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); + void authenticate_userDisabled_throwUserDisabled() { + AdminUserDO user = buildUser(); + user.setStatus(CommonStatusEnum.DISABLE.getStatus()); - // 调用, 并断言异常 - assertServiceException(() -> authService.authenticate(AuthLoginReqVO.builder().username(username).password(password).build()), + when(userService.getUserUniqueByUserName("admin")) + .thenReturn(user); + when(userService.isPasswordMatch("123456", "encodedPwd")) + .thenReturn(true); + + assertServiceException(() -> authService.authenticate(reqVO), 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 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); + void authenticate_success_returnUser() { + AdminUserDO user = buildUser(); + + when(userService.getUserUniqueByUserName("admin")) + .thenReturn(user); + when(userService.isPasswordMatch("123456", "encodedPwd")) + .thenReturn(true); + + AdminUserDO result = authService.authenticate(reqVO); + + assertSame(user, result); + + verify(organFrameworkService).validOrgan(10L); + verify(deptService).validUserLoginDept(100L); + } + + @Test + void validateCaptcha_disabled_returnDirectly() { + ChenfengProperties.Captcha captcha = new ChenfengProperties.Captcha(); + captcha.setEnable(false); + when(chenfengProperties.getCaptcha()).thenReturn(captcha); + + authService.validateCaptcha(reqVO); + + verifyNoInteractions(captchaService); + } + + @Test + void validateCaptcha_success() { + when(chenfengProperties.getCaptcha()).thenReturn(new ChenfengProperties.Captcha()); + + ResponseModel response = mock(ResponseModel.class); + when(response.isSuccess()).thenReturn(true); + when(captchaService.verification(any())).thenReturn(response); + + authService.validateCaptcha(reqVO); + + verify(captchaService).verification(any()); + } + + @Test + void validateCaptcha_fail_throwException() { + when(chenfengProperties.getCaptcha()).thenReturn(new ChenfengProperties.Captcha()); + when(validator.validate(any(), any())) + .thenReturn(Collections.emptySet()); + + ResponseModel response = mock(ResponseModel.class); + when(response.isSuccess()).thenReturn(false); + when(response.getRepMsg()).thenReturn("captcha error"); + when(captchaService.verification(any())).thenReturn(response); + + assertServiceException(() -> authService.validateCaptcha(reqVO), + AUTH_LOGIN_CAPTCHA_CODE_ERROR); + } + + @Test + void login_auth_manageendpoint_permission_error() { + ChenfengProperties.Captcha captcha = new ChenfengProperties.Captcha(); + captcha.setEnable(false); + when(chenfengProperties.getCaptcha()).thenReturn(captcha); + + // authenticate success + AdminUserDO user = buildUser(); + + when(userService.getUserUniqueByUserName("admin")) + .thenReturn(user); + when(userService.isPasswordMatch("123456", "encodedPwd")) + .thenReturn(true); + + assertServiceException(() -> authService.login(buildLoginReqVO(), true), + AUTH_MANAGEENDPOINT_LOGIN_PERMISSION_ERROR); + } + + @Test + void login_auth_org_data_code_not_exists() { + ChenfengProperties.Captcha captcha = new ChenfengProperties.Captcha(); + captcha.setEnable(false); + when(chenfengProperties.getCaptcha()).thenReturn(captcha); + + // authenticate success + AdminUserDO user = buildUser(); + + when(userService.getUserUniqueByUserName("admin")) + .thenReturn(user); + when(userService.isPasswordMatch("123456", "encodedPwd")) + .thenReturn(true); + when(securityFrameworkService.hasAnyRoles(anyLong(), anyString())).thenReturn(true); + + OrganizationDO organ = new OrganizationDO(); + organ.setDataSourceCode(null); + when(organService.getOrgan(anyLong())).thenReturn(organ); + + assertServiceException(() -> authService.login(buildLoginReqVO(), false), + ORGAN_DATA_CODE_NOT_EXISTS); + } + + @Test + void login_success(){ + ChenfengProperties.Captcha captcha = new ChenfengProperties.Captcha(); + captcha.setEnable(false); + ChenfengProperties.Gray gray = new ChenfengProperties.Gray(); + gray.setVersion("2.0.0"); + + when(chenfengProperties.getCaptcha()).thenReturn(captcha); + when(chenfengProperties.getGray()).thenReturn(gray); + + // authenticate success + AdminUserDO user = buildUser(); + user.setOrganId(1L); + + when(userService.getUserUniqueByUserName("admin")) + .thenReturn(user); + when(userService.isPasswordMatch("123456", "encodedPwd")) + .thenReturn(true); + + OrganizationDO organ = new OrganizationDO(); + organ.setDataSourceCode("imes_prod"); + organ.setGrayStatus(CommonStatusEnum.ENABLE.getStatus()); + when(organService.getOrgan(anyLong())).thenReturn(organ); + + authService.login(buildLoginReqVO(), true); + + assertEquals(gray.getVersion(), user.getGrayVersion()); + } + + @Test + void login_manageAndDefaultGrayVersion_success(){ + ChenfengProperties.Captcha captcha = new ChenfengProperties.Captcha(); + captcha.setEnable(false); + ChenfengProperties.Gray gray = new ChenfengProperties.Gray(); + gray.setVersion("2.0.0"); + + when(chenfengProperties.getCaptcha()).thenReturn(captcha); + when(chenfengProperties.getGray()).thenReturn(gray); + + // authenticate success + AdminUserDO user = buildUser(); + + when(userService.getUserUniqueByUserName("admin")) + .thenReturn(user); + when(userService.isPasswordMatch("123456", "encodedPwd")) + .thenReturn(true); + + OrganizationDO organ = new OrganizationDO(); + organ.setDataSourceCode("imes_prod"); + organ.setGrayStatus(CommonStatusEnum.DISABLE.getStatus()); + when(organService.getOrgan(anyLong())).thenReturn(organ); + + authService.login(buildLoginReqVO(), false); + + assertEquals(gray.getDefaultVersion(), user.getGrayDefaultVersion()); + } + + @Test + void recordLoginLog_exception_shouldRollback() { + // 1. mock TransactionStatus + TransactionStatus status = mock(TransactionStatus.class); + + // 2. 让 TransactionTemplate 直接执行回调 + doAnswer(invocation -> { + Consumer callback = invocation.getArgument(0); + callback.accept(status); + return null; + }).when(transactionTemplate).executeWithoutResult(any()); + + // 3. 让 try 内部抛异常 + doThrow(new RuntimeException("db error")) + .when(loginLogService).createLoginLog(any()); + + // 4. 调用方法 + authService.createLoginLog(1L, "测试用户", UserTypeEnum.ADMIN, 1L, + LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.SUCCESS); + + // 5. 验证回滚被标记 + verify(status).setRollbackOnly(); + } + + @Test + void recordLoginLog_success() { + // 1. mock TransactionStatus + TransactionStatus status = mock(TransactionStatus.class); + + // 2. 让 TransactionTemplate 直接执行回调 + doAnswer(invocation -> { + Consumer callback = invocation.getArgument(0); + callback.accept(status); + return null; + }).when(transactionTemplate).executeWithoutResult(any()); + + // mock 行为 + when(ipQueryService.serviceEnable()).thenReturn(true); + + IPQueryDataRespDTO ipResp = new IPQueryDataRespDTO(); + ipResp.setProv("浙江"); + ipResp.setCity("杭州"); + ipResp.setArea("西湖"); + when(ipQueryService.querySource(nullable(String.class))).thenReturn(ipResp); + + // 4. 调用方法 + authService.createLoginLog(1L, "测试用户", UserTypeEnum.ADMIN, 1L, + LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.SUCCESS); + + // 验证更新用户登录信息 + verify(userService).updateUserLogin(eq(1L), nullable(String.class)); + + // 不应回滚 + verify(status, never()).setRollbackOnly(); + } + + @Test + void recordLoginLog_errorlog_success() { + // 1. mock TransactionStatus + TransactionStatus status = mock(TransactionStatus.class); + + // 2. 让 TransactionTemplate 直接执行回调 + doAnswer(invocation -> { + Consumer callback = invocation.getArgument(0); + callback.accept(status); + return null; + }).when(transactionTemplate).executeWithoutResult(any()); + + // mock 行为 + when(ipQueryService.serviceEnable()).thenReturn(false); + + // 4. 调用方法 + authService.createLoginLog(null, "测试用户", UserTypeEnum.ADMIN, 1L, + LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.BAD_CREDENTIALS); + + // 验证更新用户登录信息 + verify(userService, never()).updateUserLogin(anyLong(), anyString()); + } + + @Test + void loginCheck_userNotExist_shouldReturnFalse_andRecordLog() { + // given + String mobile = "13800000000"; + when(userService.getUserUniqueByUserName(mobile)).thenReturn(null); + + // when + boolean result = authService.loginCheck(mobile); + + // then + assertFalse(result); + } + + @Test + void loginCheck_userExist_passwordEmpty_shouldReturnTrue() { + // given + String mobile = "13800000000"; + AdminUserDO user = new AdminUserDO(); + user.setPassword(""); // 或 null + + when(userService.getUserUniqueByUserName(mobile)).thenReturn(user); + + // when + boolean result = authService.loginCheck(mobile); + + // then + assertTrue(result); + } + + @Test + void loginCheck_userExist_passwordNotEmpty_shouldReturnFalse() { + // given + String mobile = "13800000000"; + AdminUserDO user = new AdminUserDO(); + user.setPassword("encrypted-password"); + + when(userService.getUserUniqueByUserName(mobile)).thenReturn(user); + + // when + boolean result = authService.loginCheck(mobile); + + // then + assertFalse(result); + } + + @Test + void testLoginSmsCheck_userNotExist() { + String mobile = "13800000000"; + AuthLoginSmsCheckReqVO reqVO = new AuthLoginSmsCheckReqVO(); + reqVO.setMobile(mobile); + + // 模拟用户不存在 + when(userService.getUserUniqueByUserName(mobile)).thenReturn(null); + + boolean result = authService.loginSmsCheck(reqVO); + + assertFalse(result); + } + + @Test + void testLoginSmsCheck_userExistSmsValid() { + String mobile = "13800000000"; + AuthLoginSmsCheckReqVO reqVO = new AuthLoginSmsCheckReqVO(); + reqVO.setMobile(mobile); + + // 模拟用户存在 + AdminUserDO user = new AdminUserDO(); + when(userService.getUserUniqueByUserName(mobile)).thenReturn(user); + + // 模拟验证码校验成功(不抛异常) + doNothing().when(smsCodeService).validateSmsCode(reqVO); + + boolean result = authService.loginSmsCheck(reqVO); + + assertTrue(result); + + // 验证 smsCodeService.validateSmsCode 被调用 + verify(smsCodeService, times(1)).validateSmsCode(reqVO); + } + + @Test + void testSendSmsCode_mobileNotExist() { + String mobile = "13800000000"; + AuthSmsSendReqVO reqVO = new AuthSmsSendReqVO(); + reqVO.setMobile(mobile); + + // 模拟用户不存在 + when(userService.getUserByMobile(mobile)).thenReturn(null); + + assertServiceException(() -> authService.sendSmsCode(reqVO), + AUTH_MOBILE_NOT_EXISTS); + + } + + @Test + void testSendSmsCode_mobileExists() { + String mobile = "13800000000"; + AuthSmsSendReqVO reqVO = new AuthSmsSendReqVO(); + reqVO.setMobile(mobile); + + // 模拟用户存在 + when(userService.getUserByMobile(mobile)).thenReturn(new AdminUserDO()); + + // 模拟 smsCodeApi 调用成功 + CommonResult mockResult = CommonResult.success(true); + when(smsCodeApi.sendSmsCode(any())).thenReturn(mockResult); - // 调用 authService.sendSmsCode(reqVO); - // 断言 - verify(smsCodeApi).sendSmsCode(argThat(sendReqDTO -> { - assertEquals(mobile, sendReqDTO.getMobile()); - assertEquals(scene, sendReqDTO.getScene()); - return true; - })); - } - @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); + // 验证 smsCodeApi 被调用一次 + verify(smsCodeApi, times(1)).sendSmsCode(any()); } @Test @@ -270,4 +559,54 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { verify(loginLogService, never()).createLoginLog(any()); } + @Test + void recordLogoutLog_success() { + + // mock 行为 + when(ipQueryService.serviceEnable()).thenReturn(true); + + IPQueryDataRespDTO ipResp = new IPQueryDataRespDTO(); + ipResp.setProv("浙江"); + ipResp.setCity("杭州"); + ipResp.setArea("西湖"); + when(ipQueryService.querySource(nullable(String.class))).thenReturn(ipResp); + + // 4. 调用方法 + authService.createLogoutLog(1L, "测试用户", UserTypeEnum.ADMIN.getValue(), LoginLogTypeEnum.LOGOUT_SELF.getType()); + + // 验证更新用户登录信息 + verify(loginLogService).createLoginLog(any()); + } + + @Test + @WithMockLoginUser(userId = 123L, organId = 456L) + void testGetMesJwtToken() { + + // mock jwtProperties + when(jwtProperties.getMesTokenTtl()).thenReturn(Duration.ofHours(1)); + when(jwtProperties.getMesSecret()).thenReturn("12345678901234567890123456789012"); // 至少 32 字节 + when(jwtProperties.getMesAud()).thenReturn("test-aud"); + + String token = authService.getMesJwtToken(); + assertNotNull(token); + + // 验证 token 内容 + SecretKey secretKey = Keys.hmacShaKeyFor(jwtProperties.getMesSecret().getBytes(StandardCharsets.UTF_8)); + Claims claims = Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody(); + + assertEquals(123L, claims.get("userId", Long.class)); + assertEquals(456L, claims.get("companyId", Long.class)); + assertEquals("test-aud", claims.getAudience()); + assertNotNull(claims.getExpiration()); + assertNotNull(claims.getNotBefore()); + } + + @Test + void testGetMesJwtToken_notLogin() { + assertServiceException(() -> authService.getMesJwtToken(), GlobalErrorCodeConstants.UNAUTHORIZED); + } } diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/tokenconfig/TokenConfigServiceImplTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/tokenconfig/TokenConfigServiceImplTest.java index 44992c5d6..e0e5d6961 100644 --- a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/tokenconfig/TokenConfigServiceImplTest.java +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/tokenconfig/TokenConfigServiceImplTest.java @@ -5,7 +5,7 @@ import com.cf.imes.framework.common.enums.CommonStatusEnum; import com.cf.imes.framework.common.pojo.PageResult; import com.cf.imes.framework.security.test.WithMockLoginUser; import com.cf.imes.framework.test.core.ut.BaseDbAndRedisUnitTest; -import com.cf.imes.module.system.controller.admin.tokenconfig.vo.JwtConfig; +import com.cf.imes.module.system.framework.jwt.config.JwtProperties; import com.cf.imes.module.system.controller.admin.tokenconfig.vo.TokenConfigPageReqVO; import com.cf.imes.module.system.controller.admin.tokenconfig.vo.TokenConfigRespVO; import com.cf.imes.module.system.controller.admin.tokenconfig.vo.TokenConfigSaveReqVO; @@ -58,7 +58,7 @@ class TokenConfigServiceImplTest extends BaseDbAndRedisUnitTest { private OrganService organService; @MockitoBean - private JwtConfig jwtConfig; + private JwtProperties jwtProperties; @BeforeEach void setUp() { @@ -68,7 +68,7 @@ class TokenConfigServiceImplTest extends BaseDbAndRedisUnitTest { organ.setName("测试组织"); when(organService.validOrgan(1L)).thenReturn(organ); - when(jwtConfig.getSecret()).thenReturn("BjEM0tnL3W5zYLQ6pllol49uYVXe+f66pHyOM/tkGWg="); + when(jwtProperties.getSecret()).thenReturn("BjEM0tnL3W5zYLQ6pllol49uYVXe+f66pHyOM/tkGWg="); } @Test