refactor: unify auth api and preserve legacy auth flows
This commit is contained in:
parent
b9f561cd1f
commit
ba919b4aff
@ -0,0 +1,97 @@
|
||||
package com.cisd.tms.modules.auth.controller;
|
||||
|
||||
import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyBindRequest;
|
||||
import com.cisd.tms.modules.auth.security.RequireAuthLevel;
|
||||
import com.cisd.tms.modules.auth.security.RequireRole;
|
||||
import com.cisd.tms.modules.auth.service.AuthAdminService;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignResult;
|
||||
import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/auth")
|
||||
@Tag(name = "认证管理", description = "角色启用、密码重置与 UKey 绑定接口")
|
||||
public class AuthAdminController {
|
||||
|
||||
private final AuthAdminService authAdminService;
|
||||
|
||||
public AuthAdminController(AuthAdminService authAdminService) {
|
||||
this.authAdminService = authAdminService;
|
||||
}
|
||||
|
||||
@PostMapping("/roles/{roleCode}/enable")
|
||||
@Operation(summary = "启用角色", description = "仅允许 KEY_ADMIN FULL 会话启用目标角色。")
|
||||
@RequireRole("KEY_ADMIN")
|
||||
@RequireAuthLevel("FULL")
|
||||
public ApiResponse<Void> enableRole(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
|
||||
authAdminService.enableRole(
|
||||
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
||||
roleCode
|
||||
);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/roles/{roleCode}/reset-password")
|
||||
@Operation(summary = "重置角色密码", description = "仅允许 KEY_ADMIN FULL 会话重置目标角色密码。")
|
||||
@RequireRole("KEY_ADMIN")
|
||||
@RequireAuthLevel("FULL")
|
||||
public ApiResponse<Void> resetPassword(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
|
||||
authAdminService.resetPassword(
|
||||
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
||||
roleCode
|
||||
);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/roles/{roleCode}/ukeys/bind")
|
||||
@Operation(summary = "绑定角色 UKey", description = "仅允许 KEY_ADMIN FULL 会话登记目标角色的 UKey 绑定信息。")
|
||||
@RequireRole("KEY_ADMIN")
|
||||
@RequireAuthLevel("FULL")
|
||||
public ApiResponse<Void> bindUkey(
|
||||
@PathVariable("roleCode") String roleCode,
|
||||
@Valid @RequestBody UkeyBindRequest request,
|
||||
HttpServletRequest httpRequest
|
||||
) {
|
||||
authAdminService.bindIssuedUkey(
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
||||
roleCode,
|
||||
request.getSlotNo(),
|
||||
request.getUkeySerial(),
|
||||
request.getPubKey(),
|
||||
request.getUid(),
|
||||
request.getRid(),
|
||||
request.getIssuerSignature()
|
||||
);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/roles/{roleCode}/ukeys/issue-sign")
|
||||
@Operation(summary = "生成 UKey 发行签名", description = "按旧绑定流程为目标角色 UKey 材料生成发行签名。")
|
||||
@RequireRole("KEY_ADMIN")
|
||||
@RequireAuthLevel("FULL")
|
||||
public ApiResponse<UKeySignResult> issueUkeyBindingSign(
|
||||
@PathVariable("roleCode") String roleCode,
|
||||
@RequestBody UKeySignDTO request,
|
||||
HttpServletRequest httpRequest
|
||||
) {
|
||||
return ApiResponse.success(authAdminService.issueUkeyBindingSign(
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
||||
roleCode,
|
||||
request
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,15 @@
|
||||
package com.cisd.tms.modules.auth.controller;
|
||||
|
||||
import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.modules.auth.dto.CaptchaResponse;
|
||||
import com.cisd.tms.modules.auth.dto.ChangePasswordRequest;
|
||||
import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
|
||||
import com.cisd.tms.modules.auth.dto.LoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyBindRequest;
|
||||
import com.cisd.tms.modules.auth.security.RequireAuthLevel;
|
||||
import com.cisd.tms.modules.auth.security.RequireRole;
|
||||
import com.cisd.tms.modules.auth.service.AuthAdminService;
|
||||
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
|
||||
import com.cisd.tms.modules.auth.service.AuthService;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignResult;
|
||||
import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@ -20,46 +18,61 @@ import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/auth")
|
||||
@Tag(name = "认证鉴权", description = "内部用户登录与当前用户信息接口")
|
||||
@Tag(name = "认证鉴权", description = "管理端认证与当前会话接口")
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authService;
|
||||
private final AuthAdminService authAdminService;
|
||||
|
||||
public AuthController(AuthService authService, AuthAdminService authAdminService) {
|
||||
public AuthController(AuthService authService) {
|
||||
this.authService = authService;
|
||||
this.authAdminService = authAdminService;
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
@Operation(summary = "内部用户登录", description = "校验角色口令,可选校验 UKey 序列号,返回访问令牌和认证等级。")
|
||||
public ApiResponse<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||
return ApiResponse.success(authService.login(request));
|
||||
@PostMapping("/password-login")
|
||||
@Operation(summary = "口令登录", description = "按旧系统受限登录流程签发 LIMITED 会话。")
|
||||
public ApiResponse<LoginResponse> passwordLogin(@Valid @RequestBody PasswordLoginRequest request) {
|
||||
return ApiResponse.success(authService.passwordLogin(request));
|
||||
}
|
||||
|
||||
@PostMapping("/ukey-login/randoms")
|
||||
@Operation(summary = "申请 UKey 登录随机数", description = "按角色要求下发 UKey 登录签名随机数。")
|
||||
public ApiResponse<UkeyLoginRandomResponse> issueUkeyLoginRandoms(@Valid @RequestBody UkeyLoginRandomRequest request) {
|
||||
return ApiResponse.success(authService.issueUkeyLoginRandoms(request));
|
||||
}
|
||||
|
||||
@PostMapping("/ukey-login")
|
||||
@Operation(summary = "UKey 登录", description = "按旧系统标准 UKey 校验顺序签发 FULL 会话。")
|
||||
public ApiResponse<LoginResponse> ukeyLogin(@Valid @RequestBody UkeyLoginRequest request) {
|
||||
return ApiResponse.success(authService.ukeyLogin(request));
|
||||
}
|
||||
|
||||
@PostMapping("/captcha")
|
||||
@Operation(summary = "生成验证码", description = "为口令登录流程生成一次性验证码。")
|
||||
public ApiResponse<CaptchaResponse> issueCaptcha() {
|
||||
return ApiResponse.success(authService.issueCaptcha());
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
@Operation(summary = "查询当前用户", description = "根据内部鉴权链路透传的当前角色标识返回当前登录用户信息。")
|
||||
@Operation(summary = "查询当前用户", description = "根据内部鉴权上下文返回当前会话信息。")
|
||||
public ApiResponse<CurrentUserResponse> me(HttpServletRequest request) {
|
||||
String user = (String) request.getAttribute(com.cisd.tms.security.internal.InternalApiAuthInterceptor.ATTR_ROLE_CODE);
|
||||
String sessionToken = (String) request.getAttribute(com.cisd.tms.security.internal.InternalApiAuthInterceptor.ATTR_SESSION_TOKEN);
|
||||
String user = (String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE);
|
||||
String sessionToken = (String) request.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN);
|
||||
return ApiResponse.success(authService.me(user, sessionToken));
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
@Operation(summary = "退出当前会话", description = "使当前内部管理端会话失效。")
|
||||
@Operation(summary = "退出当前会话", description = "使当前会话失效。")
|
||||
public ApiResponse<Void> logout(HttpServletRequest request) {
|
||||
authService.logout((String) request.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN));
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/change-password")
|
||||
@Operation(summary = "修改当前角色口令", description = "基于当前登录会话校验并更新角色口令。")
|
||||
@Operation(summary = "修改当前角色口令", description = "基于当前会话校验并更新当前角色口令。")
|
||||
public ApiResponse<Void> changePassword(@Valid @RequestBody ChangePasswordRequest request, HttpServletRequest httpRequest) {
|
||||
authService.changePassword(
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN),
|
||||
@ -68,70 +81,4 @@ public class AuthController {
|
||||
);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/roles/{roleCode}/enable")
|
||||
@Operation(summary = "启用角色", description = "仅允许密钥管理员 FULL 会话启用目标角色。")
|
||||
@RequireRole("KEY_ADMIN")
|
||||
@RequireAuthLevel("FULL")
|
||||
public ApiResponse<Void> enableRole(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
|
||||
authAdminService.enableRole(
|
||||
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
||||
roleCode
|
||||
);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/roles/{roleCode}/reset-password")
|
||||
@Operation(summary = "重置角色密码", description = "仅允许密钥管理员 FULL 会话重置目标角色密码。")
|
||||
@RequireRole("KEY_ADMIN")
|
||||
@RequireAuthLevel("FULL")
|
||||
public ApiResponse<Void> resetPassword(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
|
||||
authAdminService.resetPassword(
|
||||
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
||||
roleCode
|
||||
);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/roles/{roleCode}/ukeys/bind")
|
||||
@Operation(summary = "绑定角色 UKey", description = "仅允许密钥管理员 FULL 会话绑定或覆盖目标角色槽位上的 UKey。")
|
||||
@RequireRole("KEY_ADMIN")
|
||||
@RequireAuthLevel("FULL")
|
||||
public ApiResponse<Void> bindUkey(
|
||||
@PathVariable("roleCode") String roleCode,
|
||||
@Valid @RequestBody UkeyBindRequest request,
|
||||
HttpServletRequest httpRequest
|
||||
) {
|
||||
authAdminService.bindIssuedUkey(
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
||||
roleCode,
|
||||
request.getSlotNo(),
|
||||
request.getUkeySerial(),
|
||||
request.getUkeyPubkey(),
|
||||
request.getUid(),
|
||||
request.getRid(),
|
||||
request.getIssuerSign()
|
||||
);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/roles/{roleCode}/ukeys/issue-sign")
|
||||
@Operation(summary = "生成绑定用 UKey 发行签名", description = "按旧管理端绑定流程为目标角色的 UKey 信息出具发行签名。")
|
||||
@RequireRole("KEY_ADMIN")
|
||||
@RequireAuthLevel("FULL")
|
||||
public ApiResponse<UKeySignResult> issueUkeyBindingSign(
|
||||
@PathVariable("roleCode") String roleCode,
|
||||
@RequestBody UKeySignDTO request,
|
||||
HttpServletRequest httpRequest
|
||||
) {
|
||||
return ApiResponse.success(authAdminService.issueUkeyBindingSign(
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
||||
roleCode,
|
||||
request
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.controller;
|
||||
|
||||
import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.modules.auth.dto.CompatPasswordLoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.CompatUkeyLoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||
import com.cisd.tms.modules.auth.service.CompatAuthService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/auth")
|
||||
@Tag(name = "认证兼容接口", description = "兼容旧管理端的口令登录、UKey 登录与随机数接口")
|
||||
public class CompatAuthController {
|
||||
|
||||
private final CompatAuthService compatAuthService;
|
||||
|
||||
public CompatAuthController(CompatAuthService compatAuthService) {
|
||||
this.compatAuthService = compatAuthService;
|
||||
}
|
||||
|
||||
@GetMapping("/ukey-login/randoms")
|
||||
@Operation(summary = "生成 UKey 登录随机数", description = "兼容旧登录流程,为指定角色生成一组待签名随机数。")
|
||||
public ApiResponse<UkeyLoginRandomResponse> issueUkeyLoginRandoms(
|
||||
@RequestParam("role") String role,
|
||||
@RequestParam(value = "count", required = false) Integer count
|
||||
) {
|
||||
return ApiResponse.success(compatAuthService.issueUkeyLoginRandoms(role, count));
|
||||
}
|
||||
|
||||
@PostMapping("/password-login")
|
||||
@Operation(summary = "兼容口令登录", description = "兼容旧管理端的角色口令登录语义,底层复用当前会话体系。")
|
||||
public ApiResponse<LoginResponse> passwordLogin(@Valid @RequestBody CompatPasswordLoginRequest request) {
|
||||
return ApiResponse.success(compatAuthService.passwordLogin(request));
|
||||
}
|
||||
|
||||
@PostMapping("/ukey-login")
|
||||
@Operation(summary = "兼容 UKey 登录", description = "兼容旧管理端的 UKey 登录语义,校验发行签名、随机数和登录签名。")
|
||||
public ApiResponse<LoginResponse> ukeyLogin(@Valid @RequestBody CompatUkeyLoginRequest request) {
|
||||
return ApiResponse.success(compatAuthService.ukeyLogin(request));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.cisd.tms.modules.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "验证码响应")
|
||||
public class CaptchaResponse {
|
||||
|
||||
@Schema(description = "验证码标识", example = "captcha-001")
|
||||
private String captchaId;
|
||||
|
||||
@Schema(description = "Base64 编码的验证码图片", example = "iVBORw0KGgoAAAANSUhEUg...")
|
||||
private String imageBase64;
|
||||
|
||||
public String getCaptchaId() {
|
||||
return captchaId;
|
||||
}
|
||||
|
||||
public void setCaptchaId(String captchaId) {
|
||||
this.captchaId = captchaId;
|
||||
}
|
||||
|
||||
public String getImageBase64() {
|
||||
return imageBase64;
|
||||
}
|
||||
|
||||
public void setImageBase64(String imageBase64) {
|
||||
this.imageBase64 = imageBase64;
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "兼容旧管理端的口令登录请求")
|
||||
public class CompatPasswordLoginRequest {
|
||||
|
||||
@NotBlank(message = "role is required")
|
||||
@Schema(description = "角色标识,兼容旧角色名称", example = "auditadmin")
|
||||
private String role;
|
||||
|
||||
@NotBlank(message = "password is required")
|
||||
@Schema(description = "登录口令", example = "12345678")
|
||||
private String password;
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
@ -1,132 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "兼容旧管理端的 UKey 登录请求")
|
||||
public class CompatUkeyLoginRequest {
|
||||
|
||||
@NotBlank(message = "role is required")
|
||||
@Schema(description = "角色标识,兼容旧角色名称", example = "keyadmin")
|
||||
private String role;
|
||||
|
||||
@NotBlank(message = "password is required")
|
||||
@Schema(description = "登录口令", example = "12345678")
|
||||
private String password;
|
||||
|
||||
@Valid
|
||||
@NotEmpty(message = "authInfo is required")
|
||||
@Schema(description = "UKey 登录认证材料")
|
||||
private List<LoginAuthInfo> authInfo;
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public List<LoginAuthInfo> getAuthInfo() {
|
||||
return authInfo;
|
||||
}
|
||||
|
||||
public void setAuthInfo(List<LoginAuthInfo> authInfo) {
|
||||
this.authInfo = authInfo;
|
||||
}
|
||||
|
||||
@Schema(description = "单个 UKey 登录认证项")
|
||||
public static class LoginAuthInfo {
|
||||
@NotBlank(message = "pubKey is required")
|
||||
private String pubKey;
|
||||
@NotBlank(message = "uid is required")
|
||||
private String uid;
|
||||
@NotBlank(message = "rid is required")
|
||||
private String rid;
|
||||
@NotBlank(message = "ra is required")
|
||||
private String ra;
|
||||
@NotBlank(message = "rb is required")
|
||||
private String rb;
|
||||
@NotBlank(message = "issueSign is required")
|
||||
private String issueSign;
|
||||
@NotBlank(message = "loginSignData is required")
|
||||
private String loginSignData;
|
||||
@NotBlank(message = "loginSign is required")
|
||||
private String loginSign;
|
||||
|
||||
public String getPubKey() {
|
||||
return pubKey;
|
||||
}
|
||||
|
||||
public void setPubKey(String pubKey) {
|
||||
this.pubKey = pubKey;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(String uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String getRid() {
|
||||
return rid;
|
||||
}
|
||||
|
||||
public void setRid(String rid) {
|
||||
this.rid = rid;
|
||||
}
|
||||
|
||||
public String getRa() {
|
||||
return ra;
|
||||
}
|
||||
|
||||
public void setRa(String ra) {
|
||||
this.ra = ra;
|
||||
}
|
||||
|
||||
public String getRb() {
|
||||
return rb;
|
||||
}
|
||||
|
||||
public void setRb(String rb) {
|
||||
this.rb = rb;
|
||||
}
|
||||
|
||||
public String getIssueSign() {
|
||||
return issueSign;
|
||||
}
|
||||
|
||||
public void setIssueSign(String issueSign) {
|
||||
this.issueSign = issueSign;
|
||||
}
|
||||
|
||||
public String getLoginSignData() {
|
||||
return loginSignData;
|
||||
}
|
||||
|
||||
public void setLoginSignData(String loginSignData) {
|
||||
this.loginSignData = loginSignData;
|
||||
}
|
||||
|
||||
public String getLoginSign() {
|
||||
return loginSign;
|
||||
}
|
||||
|
||||
public void setLoginSign(String loginSign) {
|
||||
this.loginSign = loginSign;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package com.cisd.tms.modules.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "口令登录请求")
|
||||
public class PasswordLoginRequest {
|
||||
|
||||
/**
|
||||
* 统一使用主角色编码,不再暴露旧系统影子角色名。
|
||||
*/
|
||||
@NotBlank(message = "roleCode is required")
|
||||
@Schema(description = "角色编码", example = "AUDIT_ADMIN")
|
||||
private String roleCode;
|
||||
|
||||
@NotBlank(message = "password is required")
|
||||
@Schema(description = "登录口令", example = "12345678")
|
||||
private String password;
|
||||
|
||||
@NotBlank(message = "captchaCode is required")
|
||||
@Schema(description = "图形验证码", example = "ABCD")
|
||||
private String captchaCode;
|
||||
|
||||
@NotBlank(message = "captchaId is required")
|
||||
@Schema(description = "验证码标识", example = "captcha-001")
|
||||
private String captchaId;
|
||||
|
||||
public String getRoleCode() {
|
||||
return roleCode;
|
||||
}
|
||||
|
||||
public void setRoleCode(String roleCode) {
|
||||
this.roleCode = roleCode;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getCaptchaCode() {
|
||||
return captchaCode;
|
||||
}
|
||||
|
||||
public void setCaptchaCode(String captchaCode) {
|
||||
this.captchaCode = captchaCode;
|
||||
}
|
||||
|
||||
public String getCaptchaId() {
|
||||
return captchaId;
|
||||
}
|
||||
|
||||
public void setCaptchaId(String captchaId) {
|
||||
this.captchaId = captchaId;
|
||||
}
|
||||
}
|
||||
@ -15,9 +15,9 @@ public class UkeyBindRequest {
|
||||
@Schema(description = "UKey 序列号", example = "UK-001")
|
||||
private String ukeySerial;
|
||||
|
||||
@NotBlank(message = "ukeyPubkey is required")
|
||||
@NotBlank(message = "pubKey is required")
|
||||
@Schema(description = "UKey 公钥")
|
||||
private String ukeyPubkey;
|
||||
private String pubKey;
|
||||
|
||||
@NotBlank(message = "uid is required")
|
||||
@Schema(description = "UKey 认证信息 uid", example = "1")
|
||||
@ -27,9 +27,9 @@ public class UkeyBindRequest {
|
||||
@Schema(description = "UKey 认证信息 rid", example = "RID-001")
|
||||
private String rid;
|
||||
|
||||
@NotBlank(message = "issuerSign is required")
|
||||
@NotBlank(message = "issuerSignature is required")
|
||||
@Schema(description = "认证公钥签名值")
|
||||
private String issuerSign;
|
||||
private String issuerSignature;
|
||||
|
||||
public Integer getSlotNo() {
|
||||
return slotNo;
|
||||
@ -47,20 +47,20 @@ public class UkeyBindRequest {
|
||||
this.ukeySerial = ukeySerial;
|
||||
}
|
||||
|
||||
public String getUkeyPubkey() {
|
||||
return ukeyPubkey;
|
||||
public String getPubKey() {
|
||||
return pubKey;
|
||||
}
|
||||
|
||||
public void setUkeyPubkey(String ukeyPubkey) {
|
||||
this.ukeyPubkey = ukeyPubkey;
|
||||
public void setPubKey(String pubKey) {
|
||||
this.pubKey = pubKey;
|
||||
}
|
||||
|
||||
public String getIssuerSign() {
|
||||
return issuerSign;
|
||||
public String getIssuerSignature() {
|
||||
return issuerSignature;
|
||||
}
|
||||
|
||||
public void setIssuerSign(String issuerSign) {
|
||||
this.issuerSign = issuerSign;
|
||||
public void setIssuerSignature(String issuerSignature) {
|
||||
this.issuerSignature = issuerSignature;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
package com.cisd.tms.modules.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "单个 UKey 登录证明")
|
||||
public class UkeyLoginProof {
|
||||
|
||||
@NotBlank(message = "pubKey is required")
|
||||
private String pubKey;
|
||||
|
||||
@NotBlank(message = "uid is required")
|
||||
private String uid;
|
||||
|
||||
@NotBlank(message = "rid is required")
|
||||
private String rid;
|
||||
|
||||
/**
|
||||
* 对应旧系统下发的 rb,命名改为更明确的服务端随机数。
|
||||
*/
|
||||
@NotBlank(message = "serverRandom is required")
|
||||
private String serverRandom;
|
||||
|
||||
@NotBlank(message = "issueSignature is required")
|
||||
private String issueSignature;
|
||||
|
||||
@NotBlank(message = "loginPayload is required")
|
||||
private String loginPayload;
|
||||
|
||||
@NotBlank(message = "loginSignature is required")
|
||||
private String loginSignature;
|
||||
|
||||
public String getPubKey() {
|
||||
return pubKey;
|
||||
}
|
||||
|
||||
public void setPubKey(String pubKey) {
|
||||
this.pubKey = pubKey;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(String uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String getRid() {
|
||||
return rid;
|
||||
}
|
||||
|
||||
public void setRid(String rid) {
|
||||
this.rid = rid;
|
||||
}
|
||||
|
||||
public String getServerRandom() {
|
||||
return serverRandom;
|
||||
}
|
||||
|
||||
public void setServerRandom(String serverRandom) {
|
||||
this.serverRandom = serverRandom;
|
||||
}
|
||||
|
||||
public String getIssueSignature() {
|
||||
return issueSignature;
|
||||
}
|
||||
|
||||
public void setIssueSignature(String issueSignature) {
|
||||
this.issueSignature = issueSignature;
|
||||
}
|
||||
|
||||
public String getLoginPayload() {
|
||||
return loginPayload;
|
||||
}
|
||||
|
||||
public void setLoginPayload(String loginPayload) {
|
||||
this.loginPayload = loginPayload;
|
||||
}
|
||||
|
||||
public String getLoginSignature() {
|
||||
return loginSignature;
|
||||
}
|
||||
|
||||
public void setLoginSignature(String loginSignature) {
|
||||
this.loginSignature = loginSignature;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.cisd.tms.modules.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "申请 UKey 登录随机数")
|
||||
public class UkeyLoginRandomRequest {
|
||||
|
||||
@NotBlank(message = "roleCode is required")
|
||||
@Schema(description = "角色编码", example = "SUPER_ADMIN")
|
||||
private String roleCode;
|
||||
|
||||
public String getRoleCode() {
|
||||
return roleCode;
|
||||
}
|
||||
|
||||
public void setRoleCode(String roleCode) {
|
||||
this.roleCode = roleCode;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package com.cisd.tms.modules.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "UKey 登录请求")
|
||||
public class UkeyLoginRequest {
|
||||
|
||||
@NotBlank(message = "roleCode is required")
|
||||
@Schema(description = "角色编码", example = "KEY_ADMIN")
|
||||
private String roleCode;
|
||||
|
||||
@NotBlank(message = "password is required")
|
||||
@Schema(description = "角色口令", example = "12345678")
|
||||
private String password;
|
||||
|
||||
@Valid
|
||||
@NotEmpty(message = "ukeyProofs is required")
|
||||
@Schema(description = "UKey 登录证明列表")
|
||||
private List<UkeyLoginProof> ukeyProofs;
|
||||
|
||||
public String getRoleCode() {
|
||||
return roleCode;
|
||||
}
|
||||
|
||||
public void setRoleCode(String roleCode) {
|
||||
this.roleCode = roleCode;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public List<UkeyLoginProof> getUkeyProofs() {
|
||||
return ukeyProofs;
|
||||
}
|
||||
|
||||
public void setUkeyProofs(List<UkeyLoginProof> ukeyProofs) {
|
||||
this.ukeyProofs = ukeyProofs;
|
||||
}
|
||||
}
|
||||
@ -7,8 +7,21 @@ import java.time.LocalDateTime;
|
||||
@TableName("tms_auth_session")
|
||||
public class AuthSessionEntity extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 当前会话令牌。
|
||||
*/
|
||||
private String sessionToken;
|
||||
/**
|
||||
* 当前会话所属角色编码。
|
||||
*/
|
||||
private String roleCode;
|
||||
/**
|
||||
* 当前会话的认证方式,区分口令登录和 UKey 登录。
|
||||
*/
|
||||
private String authMethod;
|
||||
/**
|
||||
* 当前会话的认证等级,和具体接口权限直接关联。
|
||||
*/
|
||||
private String authLevel;
|
||||
private LocalDateTime issuedAt;
|
||||
private LocalDateTime lastActiveAt;
|
||||
@ -31,6 +44,14 @@ public class AuthSessionEntity extends BaseEntity {
|
||||
this.roleCode = roleCode;
|
||||
}
|
||||
|
||||
public String getAuthMethod() {
|
||||
return authMethod;
|
||||
}
|
||||
|
||||
public void setAuthMethod(String authMethod) {
|
||||
this.authMethod = authMethod;
|
||||
}
|
||||
|
||||
public String getAuthLevel() {
|
||||
return authLevel;
|
||||
}
|
||||
|
||||
@ -7,8 +7,22 @@ import java.time.LocalDateTime;
|
||||
@TableName("tms_role_ukey_binding")
|
||||
public class RoleUkeyBindingEntity extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 绑定所属角色。
|
||||
*/
|
||||
private String roleCode;
|
||||
/**
|
||||
* 角色下的 UKey 槽位,从 1 开始,和旧系统卡位模型保持一致。
|
||||
*/
|
||||
private Integer slotNo;
|
||||
/**
|
||||
* 旧系统 auth 文件中的 uid,登录时要参与认证匹配。
|
||||
*/
|
||||
private String uid;
|
||||
/**
|
||||
* 旧系统 auth 文件中的 rid,登录时要参与角色槽位校验。
|
||||
*/
|
||||
private String rid;
|
||||
private String ukeySerial;
|
||||
private String ukeyPubkey;
|
||||
private String issuerSign;
|
||||
@ -32,6 +46,22 @@ public class RoleUkeyBindingEntity extends BaseEntity {
|
||||
this.slotNo = slotNo;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(String uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String getRid() {
|
||||
return rid;
|
||||
}
|
||||
|
||||
public void setRid(String rid) {
|
||||
this.rid = rid;
|
||||
}
|
||||
|
||||
public String getUkeySerial() {
|
||||
return ukeySerial;
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
package com.cisd.tms.modules.auth.enums;
|
||||
|
||||
/**
|
||||
* 认证方式。
|
||||
* PASSWORD 对应旧项目的受限口令登录,
|
||||
* UKEY 对应旧项目的完整 UKey 登录。
|
||||
*/
|
||||
public enum AuthMethod {
|
||||
PASSWORD,
|
||||
UKEY
|
||||
}
|
||||
@ -144,6 +144,8 @@ public class AuthAdminService {
|
||||
Integer slotNo,
|
||||
String ukeySerial,
|
||||
String ukeyPubkey,
|
||||
String uid,
|
||||
String rid,
|
||||
String issuerSign
|
||||
) {
|
||||
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
|
||||
@ -160,12 +162,12 @@ public class AuthAdminService {
|
||||
binding.setRoleCode(targetRoleCode);
|
||||
binding.setSlotNo(slotNo);
|
||||
binding.setStatus("ACTIVE");
|
||||
applyBinding(binding, ukeySerial, ukeyPubkey, issuerSign);
|
||||
applyBinding(binding, ukeySerial, ukeyPubkey, uid, rid, issuerSign);
|
||||
roleUkeyBindingRepository.save(binding);
|
||||
return;
|
||||
}
|
||||
|
||||
applyBinding(binding, ukeySerial, ukeyPubkey, issuerSign);
|
||||
applyBinding(binding, ukeySerial, ukeyPubkey, uid, rid, issuerSign);
|
||||
roleUkeyBindingRepository.update(binding);
|
||||
}
|
||||
|
||||
@ -183,7 +185,7 @@ public class AuthAdminService {
|
||||
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
|
||||
resolveRoleCode(targetRoleCode);
|
||||
verifyIssuerSignature(targetRoleCode, ukeyPubkey, uid, rid, issuerSign);
|
||||
bindUkey(operatorRoleCode, operatorAuthLevel, targetRoleCode, slotNo, ukeySerial, ukeyPubkey, issuerSign);
|
||||
bindUkey(operatorRoleCode, operatorAuthLevel, targetRoleCode, slotNo, ukeySerial, ukeyPubkey, uid, rid, issuerSign);
|
||||
}
|
||||
|
||||
public UKeySignResult issueUkeyBindingSign(
|
||||
@ -205,13 +207,25 @@ public class AuthAdminService {
|
||||
dto.setExtra(request.getExtra());
|
||||
|
||||
String signValue = lmkService.signIk(toIssuePayload(dto, authKeyPair));
|
||||
String extra = "";
|
||||
// 旧系统要求超级管理员发卡时返回主密钥分量,其他角色则透传调用方附带信息。
|
||||
String extra = RoleCode.SUPER_ADMIN == targetRole
|
||||
? lmkService.getComponent(Integer.parseInt(request.getUid()))
|
||||
: request.getExtra();
|
||||
return UKeySignResult.builder().sign(signValue).component(extra).build();
|
||||
}
|
||||
|
||||
private void applyBinding(RoleUkeyBindingEntity binding, String ukeySerial, String ukeyPubkey, String issuerSign) {
|
||||
private void applyBinding(
|
||||
RoleUkeyBindingEntity binding,
|
||||
String ukeySerial,
|
||||
String ukeyPubkey,
|
||||
String uid,
|
||||
String rid,
|
||||
String issuerSign
|
||||
) {
|
||||
binding.setUkeySerial(ukeySerial);
|
||||
binding.setUkeyPubkey(ukeyPubkey);
|
||||
binding.setUid(uid);
|
||||
binding.setRid(rid);
|
||||
binding.setIssuerSign(issuerSign);
|
||||
binding.setStatus("ACTIVE");
|
||||
binding.setBoundAt(now());
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package com.cisd.tms.modules.auth.service;
|
||||
|
||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.AuthMethod;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AuthPolicyService {
|
||||
|
||||
/**
|
||||
* 统一根据认证方式推导本次登录会话的认证等级。
|
||||
*/
|
||||
public AuthLevel resolveAuthLevel(AuthMethod authMethod) {
|
||||
return authMethod == AuthMethod.UKEY ? AuthLevel.FULL : AuthLevel.LIMITED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一读取角色要求的 UKey 数量,避免控制器和服务层散落硬编码。
|
||||
*/
|
||||
public int requiredUkeyCount(RoleCode roleCode) {
|
||||
return roleCode.getRequiredUkeyCount();
|
||||
}
|
||||
}
|
||||
@ -2,28 +2,45 @@ package com.cisd.tms.modules.auth.service;
|
||||
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.exception.BizException;
|
||||
import com.cisd.tms.modules.auth.dto.CaptchaResponse;
|
||||
import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
|
||||
import com.cisd.tms.modules.auth.dto.LoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginProof;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
|
||||
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
|
||||
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
||||
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.AuthMethod;
|
||||
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.repository.AuthUserRepository;
|
||||
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
|
||||
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
||||
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignEntity;
|
||||
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
|
||||
import com.cisd.tms.modules.mk.service.LmkService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.Base64;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -38,6 +55,11 @@ public class AuthService {
|
||||
private final AuthSessionRepository authSessionRepository;
|
||||
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
|
||||
private final PasswordHasher passwordHasher;
|
||||
private final LmkService lmkService;
|
||||
private final UkeyLoginRandomService ukeyLoginRandomService;
|
||||
private final CompatUkeyVerifier compatUkeyVerifier;
|
||||
private final CaptchaService captchaService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final Clock clock;
|
||||
private final Supplier<String> tokenSupplier;
|
||||
|
||||
@ -55,6 +77,11 @@ public class AuthService {
|
||||
authSessionRepository,
|
||||
roleUkeyBindingRepository,
|
||||
passwordHasher,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new ObjectMapper(),
|
||||
Clock.systemUTC(),
|
||||
() -> "tms-" + UUID.randomUUID()
|
||||
);
|
||||
@ -68,12 +95,47 @@ public class AuthService {
|
||||
PasswordHasher passwordHasher,
|
||||
Clock clock,
|
||||
Supplier<String> tokenSupplier
|
||||
) {
|
||||
this(
|
||||
roleAccountRepository,
|
||||
authUserRepository,
|
||||
authSessionRepository,
|
||||
roleUkeyBindingRepository,
|
||||
passwordHasher,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new ObjectMapper(),
|
||||
clock,
|
||||
tokenSupplier
|
||||
);
|
||||
}
|
||||
|
||||
AuthService(
|
||||
RoleAccountRepository roleAccountRepository,
|
||||
AuthUserRepository authUserRepository,
|
||||
AuthSessionRepository authSessionRepository,
|
||||
RoleUkeyBindingRepository roleUkeyBindingRepository,
|
||||
PasswordHasher passwordHasher,
|
||||
LmkService lmkService,
|
||||
UkeyLoginRandomService ukeyLoginRandomService,
|
||||
CompatUkeyVerifier compatUkeyVerifier,
|
||||
CaptchaService captchaService,
|
||||
ObjectMapper objectMapper,
|
||||
Clock clock,
|
||||
Supplier<String> tokenSupplier
|
||||
) {
|
||||
this.roleAccountRepository = roleAccountRepository;
|
||||
this.authUserRepository = authUserRepository;
|
||||
this.authSessionRepository = authSessionRepository;
|
||||
this.roleUkeyBindingRepository = roleUkeyBindingRepository;
|
||||
this.passwordHasher = passwordHasher;
|
||||
this.lmkService = lmkService;
|
||||
this.ukeyLoginRandomService = ukeyLoginRandomService;
|
||||
this.compatUkeyVerifier = compatUkeyVerifier;
|
||||
this.captchaService = captchaService;
|
||||
this.objectMapper = objectMapper;
|
||||
this.clock = clock;
|
||||
this.tokenSupplier = tokenSupplier;
|
||||
}
|
||||
@ -88,8 +150,9 @@ public class AuthService {
|
||||
}
|
||||
|
||||
resetFailureState(roleAccount);
|
||||
AuthMethod authMethod = resolveAuthMethod(request.getUkeySerials());
|
||||
AuthLevel authLevel = resolveAuthLevel(roleAccount, request.getUkeySerials());
|
||||
AuthSessionEntity session = buildSession(roleAccount.getRoleCode(), authLevel);
|
||||
AuthSessionEntity session = buildSession(roleAccount.getRoleCode(), authMethod, authLevel);
|
||||
authSessionRepository.save(session);
|
||||
|
||||
LoginResponse response = new LoginResponse();
|
||||
@ -101,6 +164,71 @@ public class AuthService {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化口令登录入口,当前先复用统一登录实现并签发 LIMITED 会话。
|
||||
*/
|
||||
public LoginResponse passwordLogin(PasswordLoginRequest request) {
|
||||
ensureMasterKeyReady();
|
||||
requireCaptchaService().verify(request.getCaptchaId(), request.getCaptchaCode());
|
||||
LoginRequest loginRequest = new LoginRequest();
|
||||
loginRequest.setRoleCode(request.getRoleCode());
|
||||
loginRequest.setPassword(request.getPassword());
|
||||
return login(loginRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化 UKey 随机数申请入口。当前先按角色要求生成占位随机数,后续再接旧系统完整校验链路。
|
||||
*/
|
||||
public UkeyLoginRandomResponse issueUkeyLoginRandoms(UkeyLoginRandomRequest request) {
|
||||
RoleCode roleCode = RoleCode.valueOf(request.getRoleCode());
|
||||
UkeyLoginRandomResponse response = new UkeyLoginRandomResponse();
|
||||
response.setRoleCode(roleCode.getCode());
|
||||
response.setRandoms(requireUkeyLoginRandomService().issue(roleCode.getCode(), roleCode.getRequiredUkeyCount()));
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化 UKey 登录入口,当前先按证明数量映射到完整登录。
|
||||
*/
|
||||
public LoginResponse ukeyLogin(UkeyLoginRequest request) {
|
||||
ensureMasterKeyReady();
|
||||
RoleCode roleCode = RoleCode.valueOf(request.getRoleCode());
|
||||
List<RoleUkeyBindingEntity> activeBindings = roleUkeyBindingRepository.findActiveByRoleCode(roleCode.getCode());
|
||||
validateUkeyCount(roleCode, activeBindings, request.getUkeyProofs());
|
||||
Map<String, RoleUkeyBindingEntity> bindingsByPubKey = activeBindings.stream()
|
||||
.collect(Collectors.toMap(RoleUkeyBindingEntity::getUkeyPubkey, item -> item, (left, right) -> left, java.util.LinkedHashMap::new));
|
||||
Set<String> requestPubKeys = request.getUkeyProofs().stream()
|
||||
.map(UkeyLoginProof::getPubKey)
|
||||
.collect(Collectors.toSet());
|
||||
if (requestPubKeys.size() != request.getUkeyProofs().size() || !bindingsByPubKey.keySet().containsAll(requestPubKeys)) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey auth info does not match bound role");
|
||||
}
|
||||
requireUkeyLoginRandomService().assertIssued(
|
||||
roleCode.getCode(),
|
||||
request.getUkeyProofs().stream().map(UkeyLoginProof::getServerRandom).toList()
|
||||
);
|
||||
String authKeyPair = requireLmkService().exportIkPublicKeyHex();
|
||||
List<String> matchedSerials = new java.util.ArrayList<>();
|
||||
for (UkeyLoginProof proof : request.getUkeyProofs()) {
|
||||
RoleUkeyBindingEntity binding = bindingsByPubKey.get(proof.getPubKey());
|
||||
requireCompatUkeyVerifier().verifyIssuedBinding(buildIssuePayload(request.getRoleCode(), proof, authKeyPair), proof.getIssueSignature());
|
||||
requireCompatUkeyVerifier().verifyLoginSignature(proof.getPubKey(), proof.getLoginPayload(), proof.getLoginSignature());
|
||||
matchedSerials.add(binding.getUkeySerial());
|
||||
}
|
||||
LoginRequest loginRequest = new LoginRequest();
|
||||
loginRequest.setRoleCode(request.getRoleCode());
|
||||
loginRequest.setPassword(request.getPassword());
|
||||
loginRequest.setUkeySerials(matchedSerials);
|
||||
return login(loginRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化验证码生成入口。当前先返回可用于联调的占位图像内容。
|
||||
*/
|
||||
public CaptchaResponse issueCaptcha() {
|
||||
return requireCaptchaService().issueCaptcha();
|
||||
}
|
||||
|
||||
public CurrentUserResponse me(String username) {
|
||||
return me(username, null);
|
||||
}
|
||||
@ -198,6 +326,18 @@ public class AuthService {
|
||||
roleAccountRepository.update(roleAccount);
|
||||
}
|
||||
|
||||
private AuthMethod resolveAuthMethod(List<String> ukeySerials) {
|
||||
return (ukeySerials == null || ukeySerials.isEmpty()) ? AuthMethod.PASSWORD : AuthMethod.UKEY;
|
||||
}
|
||||
|
||||
private void ensureMasterKeyReady() {
|
||||
LmkService service = requireLmkService();
|
||||
MasterKeyStatus.StatusDetail status = service.getMasterKeyStatus();
|
||||
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "master key is not ready");
|
||||
}
|
||||
}
|
||||
|
||||
private AuthLevel resolveAuthLevel(RoleAccountEntity roleAccount, List<String> ukeySerials) {
|
||||
if (ukeySerials == null || ukeySerials.isEmpty()) {
|
||||
return AuthLevel.LIMITED;
|
||||
@ -222,10 +362,46 @@ public class AuthService {
|
||||
return AuthLevel.FULL;
|
||||
}
|
||||
|
||||
private AuthSessionEntity buildSession(String roleCode, AuthLevel authLevel) {
|
||||
private void validateUkeyCount(
|
||||
RoleCode roleCode,
|
||||
List<RoleUkeyBindingEntity> activeBindings,
|
||||
List<UkeyLoginProof> proofs
|
||||
) {
|
||||
if (proofs == null || proofs.size() != roleCode.getRequiredUkeyCount()) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
||||
}
|
||||
if (activeBindings.size() != roleCode.getRequiredUkeyCount()) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
||||
}
|
||||
}
|
||||
|
||||
private String buildIssuePayload(String roleCode, UkeyLoginProof proof, String authKeyPair) {
|
||||
UKeySignDTO dto = new UKeySignDTO();
|
||||
dto.setPubKey(proof.getPubKey());
|
||||
dto.setRole(toLegacyRole(RoleCode.valueOf(roleCode)));
|
||||
dto.setUid(proof.getUid());
|
||||
dto.setRid(proof.getRid());
|
||||
try {
|
||||
return objectMapper.writeValueAsString(UKeySignEntity.getInstance(dto, authKeyPair));
|
||||
} catch (JsonProcessingException ex) {
|
||||
throw new IllegalStateException("serialize ukey issue payload failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String toLegacyRole(RoleCode roleCode) {
|
||||
return switch (roleCode) {
|
||||
case SUPER_ADMIN -> "superadmin";
|
||||
case KEY_ADMIN -> "keyadmin";
|
||||
case AUDIT_ADMIN -> "auditadmin";
|
||||
case OPS_ADMIN -> "configadmin";
|
||||
};
|
||||
}
|
||||
|
||||
private AuthSessionEntity buildSession(String roleCode, AuthMethod authMethod, AuthLevel authLevel) {
|
||||
LocalDateTime issuedAt = now();
|
||||
AuthSessionEntity entity = new AuthSessionEntity();
|
||||
entity.setRoleCode(roleCode);
|
||||
entity.setAuthMethod(authMethod.name());
|
||||
entity.setAuthLevel(authLevel.name());
|
||||
entity.setSessionToken(tokenSupplier.get());
|
||||
entity.setIssuedAt(issuedAt);
|
||||
@ -246,4 +422,32 @@ public class AuthService {
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
private LmkService requireLmkService() {
|
||||
if (lmkService == null) {
|
||||
throw new IllegalStateException("lmkService is required for this operation");
|
||||
}
|
||||
return lmkService;
|
||||
}
|
||||
|
||||
private UkeyLoginRandomService requireUkeyLoginRandomService() {
|
||||
if (ukeyLoginRandomService == null) {
|
||||
throw new IllegalStateException("ukeyLoginRandomService is required for this operation");
|
||||
}
|
||||
return ukeyLoginRandomService;
|
||||
}
|
||||
|
||||
private CompatUkeyVerifier requireCompatUkeyVerifier() {
|
||||
if (compatUkeyVerifier == null) {
|
||||
throw new IllegalStateException("compatUkeyVerifier is required for this operation");
|
||||
}
|
||||
return compatUkeyVerifier;
|
||||
}
|
||||
|
||||
private CaptchaService requireCaptchaService() {
|
||||
if (captchaService == null) {
|
||||
throw new IllegalStateException("captchaService is required for this operation");
|
||||
}
|
||||
return captchaService;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package com.cisd.tms.modules.auth.service;
|
||||
|
||||
import com.cisd.tms.modules.auth.dto.CaptchaResponse;
|
||||
|
||||
public interface CaptchaService {
|
||||
|
||||
CaptchaResponse issueCaptcha();
|
||||
|
||||
void verify(String captchaId, String captchaCode);
|
||||
}
|
||||
@ -1,158 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.service;
|
||||
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.exception.BizException;
|
||||
import com.cisd.tms.modules.auth.dto.CompatPasswordLoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.CompatUkeyLoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.LoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignEntity;
|
||||
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
|
||||
import com.cisd.tms.modules.mk.service.LmkService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class CompatAuthService {
|
||||
|
||||
private final AuthService authService;
|
||||
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
|
||||
private final LmkService lmkService;
|
||||
private final UkeyLoginRandomService ukeyLoginRandomService;
|
||||
private final CompatUkeyVerifier compatUkeyVerifier;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public CompatAuthService(
|
||||
AuthService authService,
|
||||
RoleUkeyBindingRepository roleUkeyBindingRepository,
|
||||
LmkService lmkService,
|
||||
UkeyLoginRandomService ukeyLoginRandomService,
|
||||
CompatUkeyVerifier compatUkeyVerifier,
|
||||
ObjectMapper objectMapper
|
||||
) {
|
||||
this.authService = authService;
|
||||
this.roleUkeyBindingRepository = roleUkeyBindingRepository;
|
||||
this.lmkService = lmkService;
|
||||
this.ukeyLoginRandomService = ukeyLoginRandomService;
|
||||
this.compatUkeyVerifier = compatUkeyVerifier;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
public UkeyLoginRandomResponse issueUkeyLoginRandoms(String legacyRole, Integer count) {
|
||||
RoleCode roleCode = resolveRoleCode(legacyRole);
|
||||
int resolvedCount = count == null ? roleCode.getRequiredUkeyCount() : count;
|
||||
UkeyLoginRandomResponse response = new UkeyLoginRandomResponse();
|
||||
response.setRoleCode(roleCode.getCode());
|
||||
response.setRandoms(ukeyLoginRandomService.issue(roleCode.getCode(), resolvedCount));
|
||||
return response;
|
||||
}
|
||||
|
||||
public LoginResponse passwordLogin(CompatPasswordLoginRequest request) {
|
||||
ensureMasterKeyReady();
|
||||
RoleCode roleCode = resolveRoleCode(request.getRole());
|
||||
LoginRequest loginRequest = new LoginRequest();
|
||||
loginRequest.setRoleCode(roleCode.getCode());
|
||||
loginRequest.setPassword(request.getPassword());
|
||||
return authService.login(loginRequest);
|
||||
}
|
||||
|
||||
public LoginResponse ukeyLogin(CompatUkeyLoginRequest request) {
|
||||
ensureMasterKeyReady();
|
||||
RoleCode roleCode = resolveRoleCode(request.getRole());
|
||||
List<RoleUkeyBindingEntity> activeBindings = roleUkeyBindingRepository.findActiveByRoleCode(roleCode.getCode());
|
||||
validateUkeyCount(roleCode, activeBindings, request.getAuthInfo());
|
||||
|
||||
Map<String, RoleUkeyBindingEntity> bindingsByPubKey = activeBindings.stream()
|
||||
.collect(Collectors.toMap(RoleUkeyBindingEntity::getUkeyPubkey, item -> item, (left, right) -> left, LinkedHashMap::new));
|
||||
Set<String> requestPubKeys = request.getAuthInfo().stream()
|
||||
.map(CompatUkeyLoginRequest.LoginAuthInfo::getPubKey)
|
||||
.collect(Collectors.toSet());
|
||||
if (requestPubKeys.size() != request.getAuthInfo().size() || !bindingsByPubKey.keySet().containsAll(requestPubKeys)) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey auth info does not match bound role");
|
||||
}
|
||||
|
||||
ukeyLoginRandomService.assertIssued(
|
||||
roleCode.getCode(),
|
||||
request.getAuthInfo().stream().map(CompatUkeyLoginRequest.LoginAuthInfo::getRb).toList()
|
||||
);
|
||||
|
||||
String authKeyPair = lmkService.exportIkPublicKeyHex();
|
||||
List<String> matchedSerials = new ArrayList<>();
|
||||
for (CompatUkeyLoginRequest.LoginAuthInfo loginAuthInfo : request.getAuthInfo()) {
|
||||
RoleUkeyBindingEntity binding = bindingsByPubKey.get(loginAuthInfo.getPubKey());
|
||||
compatUkeyVerifier.verifyIssuedBinding(buildIssuePayload(request.getRole(), loginAuthInfo, authKeyPair), loginAuthInfo.getIssueSign());
|
||||
compatUkeyVerifier.verifyLoginSignature(loginAuthInfo.getPubKey(), loginAuthInfo.getLoginSignData(), loginAuthInfo.getLoginSign());
|
||||
matchedSerials.add(binding.getUkeySerial());
|
||||
}
|
||||
|
||||
LoginRequest loginRequest = new LoginRequest();
|
||||
loginRequest.setRoleCode(roleCode.getCode());
|
||||
loginRequest.setPassword(request.getPassword());
|
||||
loginRequest.setUkeySerials(matchedSerials);
|
||||
return authService.login(loginRequest);
|
||||
}
|
||||
|
||||
private void ensureMasterKeyReady() {
|
||||
MasterKeyStatus.StatusDetail status = lmkService.getMasterKeyStatus();
|
||||
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "master key is not ready");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateUkeyCount(
|
||||
RoleCode roleCode,
|
||||
List<RoleUkeyBindingEntity> activeBindings,
|
||||
List<CompatUkeyLoginRequest.LoginAuthInfo> authInfos
|
||||
) {
|
||||
if (authInfos == null || authInfos.size() != roleCode.getRequiredUkeyCount()) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
||||
}
|
||||
if (activeBindings.size() != roleCode.getRequiredUkeyCount()) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
||||
}
|
||||
}
|
||||
|
||||
private String buildIssuePayload(String legacyRole, CompatUkeyLoginRequest.LoginAuthInfo loginAuthInfo, String authKeyPair) {
|
||||
UKeySignDTO uKeySignDTO = new UKeySignDTO();
|
||||
uKeySignDTO.setPubKey(loginAuthInfo.getPubKey());
|
||||
uKeySignDTO.setRole(normalizeLegacyRole(legacyRole));
|
||||
uKeySignDTO.setUid(loginAuthInfo.getUid());
|
||||
uKeySignDTO.setRid(loginAuthInfo.getRid());
|
||||
try {
|
||||
return objectMapper.writeValueAsString(UKeySignEntity.getInstance(uKeySignDTO, authKeyPair));
|
||||
} catch (JsonProcessingException ex) {
|
||||
throw new IllegalStateException("serialize ukey issue payload failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private RoleCode resolveRoleCode(String role) {
|
||||
return switch (normalizeLegacyRole(role)) {
|
||||
case "superadmin" -> RoleCode.SUPER_ADMIN;
|
||||
case "keyadmin" -> RoleCode.KEY_ADMIN;
|
||||
case "auditadmin" -> RoleCode.AUDIT_ADMIN;
|
||||
case "configadmin", "opsadmin" -> RoleCode.OPS_ADMIN;
|
||||
case "super_admin" -> RoleCode.SUPER_ADMIN;
|
||||
case "key_admin" -> RoleCode.KEY_ADMIN;
|
||||
case "audit_admin" -> RoleCode.AUDIT_ADMIN;
|
||||
case "ops_admin" -> RoleCode.OPS_ADMIN;
|
||||
default -> throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "invalid role");
|
||||
};
|
||||
}
|
||||
|
||||
private String normalizeLegacyRole(String role) {
|
||||
return role == null ? "" : role.trim().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
ALTER TABLE tms_auth_session
|
||||
ADD COLUMN auth_method VARCHAR(32) NULL AFTER role_code;
|
||||
|
||||
UPDATE tms_auth_session
|
||||
SET auth_method = CASE
|
||||
WHEN auth_level = 'FULL' THEN 'UKEY'
|
||||
ELSE 'PASSWORD'
|
||||
END
|
||||
WHERE auth_method IS NULL;
|
||||
|
||||
ALTER TABLE tms_auth_session
|
||||
MODIFY COLUMN auth_method VARCHAR(32) NOT NULL;
|
||||
|
||||
ALTER TABLE tms_role_ukey_binding
|
||||
ADD COLUMN uid VARCHAR(64) NULL AFTER slot_no,
|
||||
ADD COLUMN rid VARCHAR(64) NULL AFTER uid;
|
||||
@ -8,6 +8,7 @@
|
||||
<id property="id" column="id"/>
|
||||
<result property="sessionToken" column="session_token"/>
|
||||
<result property="roleCode" column="role_code"/>
|
||||
<result property="authMethod" column="auth_method"/>
|
||||
<result property="authLevel" column="auth_level"/>
|
||||
<result property="issuedAt" column="issued_at"/>
|
||||
<result property="lastActiveAt" column="last_active_at"/>
|
||||
@ -21,6 +22,7 @@
|
||||
SELECT id,
|
||||
session_token,
|
||||
role_code,
|
||||
auth_method,
|
||||
auth_level,
|
||||
issued_at,
|
||||
last_active_at,
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
<id property="id" column="id"/>
|
||||
<result property="roleCode" column="role_code"/>
|
||||
<result property="slotNo" column="slot_no"/>
|
||||
<result property="uid" column="uid"/>
|
||||
<result property="rid" column="rid"/>
|
||||
<result property="ukeySerial" column="ukey_serial"/>
|
||||
<result property="ukeyPubkey" column="ukey_pubkey"/>
|
||||
<result property="issuerSign" column="issuer_sign"/>
|
||||
@ -22,6 +24,8 @@
|
||||
SELECT id,
|
||||
role_code,
|
||||
slot_no,
|
||||
uid,
|
||||
rid,
|
||||
ukey_serial,
|
||||
ukey_pubkey,
|
||||
issuer_sign,
|
||||
@ -41,6 +45,8 @@
|
||||
SELECT id,
|
||||
role_code,
|
||||
slot_no,
|
||||
uid,
|
||||
rid,
|
||||
ukey_serial,
|
||||
ukey_pubkey,
|
||||
issuer_sign,
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package com.cisd.tms.modules.auth.controller;
|
||||
|
||||
import com.cisd.tms.common.exception.GlobalExceptionHandler;
|
||||
import com.cisd.tms.modules.auth.dto.CaptchaResponse;
|
||||
import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
|
||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||
import com.cisd.tms.modules.auth.service.AuthAdminService;
|
||||
import com.cisd.tms.modules.auth.service.AuthService;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignResult;
|
||||
@ -14,6 +16,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
@ -22,27 +25,195 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
class AuthControllerTest {
|
||||
|
||||
@Test
|
||||
void shouldLoginWithRoleBasedPayload() throws Exception {
|
||||
void shouldLoginWithPasswordPayload() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setRoleCode("AUDIT_ADMIN");
|
||||
response.setAuthLevel("LIMITED");
|
||||
response.setToken("token-password-001");
|
||||
Mockito.when(authService.passwordLogin(ArgumentMatchers.any())).thenReturn(response);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/password-login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"roleCode": "AUDIT_ADMIN",
|
||||
"password": "12345678",
|
||||
"captchaCode": "ABCD",
|
||||
"captchaId": "captcha-001"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"token\":\"token-password-001\"")))
|
||||
.andExpect(content().string(containsString("\"authLevel\":\"LIMITED\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIssueUkeyLoginRandoms() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
UkeyLoginRandomResponse response = new UkeyLoginRandomResponse();
|
||||
response.setRoleCode("SUPER_ADMIN");
|
||||
response.setRandoms(java.util.List.of("RB-1", "RB-2", "RB-3"));
|
||||
Mockito.when(authService.issueUkeyLoginRandoms(ArgumentMatchers.any())).thenReturn(response);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/ukey-login/randoms")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"roleCode": "SUPER_ADMIN"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"roleCode\":\"SUPER_ADMIN\"")))
|
||||
.andExpect(content().string(containsString("\"RB-1\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLoginWithUkeyPayload() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setRoleCode("KEY_ADMIN");
|
||||
response.setAuthLevel("FULL");
|
||||
response.setToken("token-001");
|
||||
response.setExpiresAt("2026-03-23T03:10Z");
|
||||
response.setNeedChangePassword(false);
|
||||
Mockito.when(authService.login(ArgumentMatchers.any())).thenReturn(response);
|
||||
response.setToken("token-ukey-001");
|
||||
Mockito.when(authService.ukeyLogin(ArgumentMatchers.any())).thenReturn(response);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new AuthController(authService, authAdminService))
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/login")
|
||||
mockMvc.perform(post("/api/v1/auth/ukey-login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"roleCode\":\"KEY_ADMIN\",\"password\":\"12345678\",\"ukeySerials\":[\"UK-1\",\"UK-2\"]}"))
|
||||
.content("""
|
||||
{
|
||||
"roleCode": "KEY_ADMIN",
|
||||
"password": "12345678",
|
||||
"ukeyProofs": [
|
||||
{
|
||||
"pubKey": "PUB-1",
|
||||
"uid": "4",
|
||||
"rid": "4",
|
||||
"serverRandom": "RB-1",
|
||||
"issueSignature": "ISSUE-1",
|
||||
"loginPayload": "LOGIN-DATA-1",
|
||||
"loginSignature": "LOGIN-SIGN-1"
|
||||
},
|
||||
{
|
||||
"pubKey": "PUB-2",
|
||||
"uid": "5",
|
||||
"rid": "5",
|
||||
"serverRandom": "RB-2",
|
||||
"issueSignature": "ISSUE-2",
|
||||
"loginPayload": "LOGIN-DATA-2",
|
||||
"loginSignature": "LOGIN-SIGN-2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"roleCode\":\"KEY_ADMIN\"")))
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"authLevel\":\"FULL\"")));
|
||||
.andExpect(content().string(containsString("\"token\":\"token-ukey-001\"")))
|
||||
.andExpect(content().string(containsString("\"authLevel\":\"FULL\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIssueCaptchaPayload() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
CaptchaResponse response = new CaptchaResponse();
|
||||
response.setCaptchaId("captcha-001");
|
||||
response.setImageBase64("IMAGE-BASE64");
|
||||
Mockito.when(authService.issueCaptcha()).thenReturn(response);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/captcha"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"captchaId\":\"captcha-001\"")))
|
||||
.andExpect(content().string(containsString("\"imageBase64\":\"IMAGE-BASE64\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReadCurrentRoleFromRequestContextForMeEndpoint() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
CurrentUserResponse response = new CurrentUserResponse();
|
||||
response.setUsername("AUDIT_ADMIN");
|
||||
response.setDisplayName("审计管理员");
|
||||
response.setRole("AUDIT_ADMIN");
|
||||
response.setAuthLevel("LIMITED");
|
||||
response.setNeedChangePassword(false);
|
||||
Mockito.when(authService.me("AUDIT_ADMIN", "token-me-001")).thenReturn(response);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(get("/api/v1/auth/me")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN, "token-me-001")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "AUDIT_ADMIN"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"role\":\"AUDIT_ADMIN\"")))
|
||||
.andExpect(content().string(containsString("\"authLevel\":\"LIMITED\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLogoutThroughCurrentSessionEndpoint() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/logout")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN, "token-logout-001"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"success\":true")));
|
||||
|
||||
Mockito.verify(authService).logout("token-logout-001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangePasswordThroughCurrentSessionEndpoint() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/change-password")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN, "token-change-001")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"currentPassword": "12345678",
|
||||
"newPassword": "87654321"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"success\":true")));
|
||||
|
||||
Mockito.verify(authService).changePassword("token-change-001", "12345678", "87654321");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -50,7 +221,8 @@ class AuthControllerTest {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new AuthController(authService, authAdminService))
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
@ -58,7 +230,7 @@ class AuthControllerTest {
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "KEY_ADMIN")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL, "FULL"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"success\":true")));
|
||||
.andExpect(content().string(containsString("\"success\":true")));
|
||||
|
||||
Mockito.verify(authAdminService).enableRole("KEY_ADMIN", "FULL", "AUDIT_ADMIN");
|
||||
}
|
||||
@ -68,7 +240,8 @@ class AuthControllerTest {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new AuthController(authService, authAdminService))
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
@ -80,14 +253,14 @@ class AuthControllerTest {
|
||||
{
|
||||
"slotNo": 1,
|
||||
"ukeySerial": "UK-NEW",
|
||||
"ukeyPubkey": "PUB-NEW",
|
||||
"pubKey": "PUB-NEW",
|
||||
"uid": "1",
|
||||
"rid": "RID-001",
|
||||
"issuerSign": "SIG-NEW"
|
||||
"issuerSignature": "SIG-NEW"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"success\":true")));
|
||||
.andExpect(content().string(containsString("\"success\":true")));
|
||||
|
||||
Mockito.verify(authAdminService).bindIssuedUkey("KEY_ADMIN", "FULL", "SUPER_ADMIN", 1, "UK-NEW", "PUB-NEW", "1", "RID-001", "SIG-NEW");
|
||||
}
|
||||
@ -104,7 +277,8 @@ class AuthControllerTest {
|
||||
ArgumentMatchers.any()
|
||||
)).thenReturn(response);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new AuthController(authService, authAdminService))
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new AuthController(authService), new AuthAdminController(authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
@ -122,72 +296,7 @@ class AuthControllerTest {
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"sign\":\"ISSUE-SIGN-001\"")))
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"extra\":\"COMP-001\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReadCurrentRoleFromRequestContextForMeEndpoint() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
CurrentUserResponse response = new CurrentUserResponse();
|
||||
response.setUsername("AUDIT_ADMIN");
|
||||
response.setDisplayName("审计管理员");
|
||||
response.setRole("AUDIT_ADMIN");
|
||||
response.setAuthLevel("LIMITED");
|
||||
response.setNeedChangePassword(false);
|
||||
Mockito.when(authService.me("AUDIT_ADMIN", "token-me-001")).thenReturn(response);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new AuthController(authService, authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(get("/api/v1/auth/me")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN, "token-me-001")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "AUDIT_ADMIN"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"role\":\"AUDIT_ADMIN\"")))
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"authLevel\":\"LIMITED\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLogoutThroughCurrentSessionEndpoint() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new AuthController(authService, authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/logout")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN, "token-logout-001"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"success\":true")));
|
||||
|
||||
Mockito.verify(authService).logout("token-logout-001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangePasswordThroughCurrentSessionEndpoint() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new AuthController(authService, authAdminService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/change-password")
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN, "token-change-001")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"currentPassword": "12345678",
|
||||
"newPassword": "87654321"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("\"success\":true")));
|
||||
|
||||
Mockito.verify(authService).changePassword("token-change-001", "12345678", "87654321");
|
||||
.andExpect(content().string(containsString("\"sign\":\"ISSUE-SIGN-001\"")))
|
||||
.andExpect(content().string(containsString("\"component\":\"COMP-001\"")));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,114 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.controller;
|
||||
|
||||
import com.cisd.tms.common.exception.GlobalExceptionHandler;
|
||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||
import com.cisd.tms.modules.auth.service.CompatAuthService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
class CompatAuthControllerTest {
|
||||
|
||||
@Test
|
||||
void shouldIssueUkeyLoginRandoms() throws Exception {
|
||||
CompatAuthService compatAuthService = Mockito.mock(CompatAuthService.class);
|
||||
UkeyLoginRandomResponse response = new UkeyLoginRandomResponse();
|
||||
response.setRoleCode("SUPER_ADMIN");
|
||||
response.setRandoms(java.util.List.of("RB-1", "RB-2", "RB-3"));
|
||||
Mockito.when(compatAuthService.issueUkeyLoginRandoms("superadmin", null)).thenReturn(response);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new CompatAuthController(compatAuthService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(get("/api/v1/auth/ukey-login/randoms")
|
||||
.param("role", "superadmin"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"roleCode\":\"SUPER_ADMIN\"")))
|
||||
.andExpect(content().string(containsString("\"RB-1\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLoginWithCompatiblePasswordPayload() throws Exception {
|
||||
CompatAuthService compatAuthService = Mockito.mock(CompatAuthService.class);
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setRoleCode("AUDIT_ADMIN");
|
||||
response.setAuthLevel("LIMITED");
|
||||
response.setToken("token-password-001");
|
||||
Mockito.when(compatAuthService.passwordLogin(ArgumentMatchers.any())).thenReturn(response);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new CompatAuthController(compatAuthService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/password-login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"role": "auditadmin",
|
||||
"password": "12345678"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"token\":\"token-password-001\"")))
|
||||
.andExpect(content().string(containsString("\"authLevel\":\"LIMITED\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLoginWithCompatibleUkeyPayload() throws Exception {
|
||||
CompatAuthService compatAuthService = Mockito.mock(CompatAuthService.class);
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setRoleCode("KEY_ADMIN");
|
||||
response.setAuthLevel("FULL");
|
||||
response.setToken("token-ukey-001");
|
||||
Mockito.when(compatAuthService.ukeyLogin(ArgumentMatchers.any())).thenReturn(response);
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new CompatAuthController(compatAuthService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/ukey-login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"role": "keyadmin",
|
||||
"password": "12345678",
|
||||
"authInfo": [
|
||||
{
|
||||
"pubKey": "PUB-1",
|
||||
"uid": "1",
|
||||
"rid": "RID-1",
|
||||
"ra": "RA-1",
|
||||
"rb": "RB-1",
|
||||
"issueSign": "ISSUE-1",
|
||||
"loginSignData": "LOGIN-DATA-1",
|
||||
"loginSign": "LOGIN-SIGN-1"
|
||||
},
|
||||
{
|
||||
"pubKey": "PUB-2",
|
||||
"uid": "2",
|
||||
"rid": "RID-2",
|
||||
"ra": "RA-2",
|
||||
"rb": "RB-2",
|
||||
"issueSign": "ISSUE-2",
|
||||
"loginSignData": "LOGIN-DATA-2",
|
||||
"loginSign": "LOGIN-SIGN-2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"token\":\"token-ukey-001\"")))
|
||||
.andExpect(content().string(containsString("\"authLevel\":\"FULL\"")));
|
||||
}
|
||||
}
|
||||
@ -126,6 +126,8 @@ class AuthAdminServiceTest {
|
||||
RoleUkeyBindingEntity updated = bindings.findActiveByRoleCodeAndSlotNo(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow();
|
||||
Assertions.assertEquals("UK-NEW", updated.getUkeySerial());
|
||||
Assertions.assertEquals("PUB-NEW", updated.getUkeyPubkey());
|
||||
Assertions.assertEquals("1", updated.getUid());
|
||||
Assertions.assertEquals("RID-001", updated.getRid());
|
||||
Assertions.assertEquals("SIG-NEW", updated.getIssuerSign());
|
||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), updated.getBoundAt());
|
||||
org.mockito.Mockito.verify(lmkService).verifyIk(
|
||||
|
||||
@ -21,6 +21,29 @@ class AuthDomainModelTest {
|
||||
Assertions.assertEquals("FULL", com.cisd.tms.modules.auth.enums.AuthLevel.FULL.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposePasswordAndUkeyAuthMethodsOnly() {
|
||||
Assertions.assertEquals(2, com.cisd.tms.modules.auth.enums.AuthMethod.values().length);
|
||||
Assertions.assertEquals("PASSWORD", com.cisd.tms.modules.auth.enums.AuthMethod.PASSWORD.name());
|
||||
Assertions.assertEquals("UKEY", com.cisd.tms.modules.auth.enums.AuthMethod.UKEY.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveExpectedAuthLevelByAuthMethod() {
|
||||
Assertions.assertEquals(
|
||||
com.cisd.tms.modules.auth.enums.AuthLevel.LIMITED,
|
||||
new com.cisd.tms.modules.auth.service.AuthPolicyService().resolveAuthLevel(
|
||||
com.cisd.tms.modules.auth.enums.AuthMethod.PASSWORD
|
||||
)
|
||||
);
|
||||
Assertions.assertEquals(
|
||||
com.cisd.tms.modules.auth.enums.AuthLevel.FULL,
|
||||
new com.cisd.tms.modules.auth.service.AuthPolicyService().resolveAuthLevel(
|
||||
com.cisd.tms.modules.auth.enums.AuthMethod.UKEY
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeExpectedRoleAccountStatuses() {
|
||||
Assertions.assertEquals(3, com.cisd.tms.modules.auth.enums.RoleAccountStatus.values().length);
|
||||
|
||||
@ -1,19 +1,28 @@
|
||||
package com.cisd.tms.modules.auth.service;
|
||||
|
||||
import com.cisd.tms.common.exception.BizException;
|
||||
import com.cisd.tms.modules.auth.dto.CaptchaResponse;
|
||||
import com.cisd.tms.modules.auth.dto.LoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginProof;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
|
||||
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
|
||||
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
|
||||
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
||||
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.AuthMethod;
|
||||
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.repository.AuthUserRepository;
|
||||
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
|
||||
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
||||
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
|
||||
import com.cisd.tms.modules.mk.service.LmkService;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
@ -24,6 +33,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -62,6 +72,7 @@ class AuthServiceTest {
|
||||
Assertions.assertEquals(1, sessions.count());
|
||||
AuthSessionEntity session = sessions.findBySessionToken("token-limited-001").orElseThrow();
|
||||
Assertions.assertEquals(AuthLevel.LIMITED.name(), session.getAuthLevel());
|
||||
Assertions.assertEquals(AuthMethod.PASSWORD.name(), session.getAuthMethod());
|
||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 0), session.getIssuedAt());
|
||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 10), session.getExpiresAt());
|
||||
}
|
||||
@ -95,6 +106,8 @@ class AuthServiceTest {
|
||||
Assertions.assertEquals(AuthLevel.FULL.name(), response.getAuthLevel());
|
||||
Assertions.assertEquals("token-full-001", response.getToken());
|
||||
Assertions.assertFalse(response.getNeedChangePassword());
|
||||
AuthSessionEntity session = sessions.findBySessionToken("token-full-001").orElseThrow();
|
||||
Assertions.assertEquals(AuthMethod.UKEY.name(), session.getAuthMethod());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -254,6 +267,167 @@ class AuthServiceTest {
|
||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 0), session.getExpiresAt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIssueRoleScopedRandomsForUkeyLogin() {
|
||||
UkeyLoginRandomService randomService = org.mockito.Mockito.mock(UkeyLoginRandomService.class);
|
||||
org.mockito.Mockito.when(randomService.issue(RoleCode.SUPER_ADMIN.getCode(), 3)).thenReturn(List.of("RB-1", "RB-2", "RB-3"));
|
||||
|
||||
AuthService service = new AuthService(
|
||||
new InMemoryRoleAccountRepository(),
|
||||
new InMemoryLegacyAuthUserRepository(),
|
||||
new InMemoryAuthSessionRepository(),
|
||||
new InMemoryRoleUkeyBindingRepository(),
|
||||
new FakePasswordHasher(),
|
||||
org.mockito.Mockito.mock(LmkService.class),
|
||||
randomService,
|
||||
org.mockito.Mockito.mock(CompatUkeyVerifier.class),
|
||||
new InMemoryCaptchaService(),
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
() -> "unused"
|
||||
);
|
||||
|
||||
UkeyLoginRandomRequest request = new UkeyLoginRandomRequest();
|
||||
request.setRoleCode(RoleCode.SUPER_ADMIN.getCode());
|
||||
|
||||
UkeyLoginRandomResponse response = service.issueUkeyLoginRandoms(request);
|
||||
|
||||
Assertions.assertEquals(RoleCode.SUPER_ADMIN.getCode(), response.getRoleCode());
|
||||
Assertions.assertEquals(List.of("RB-1", "RB-2", "RB-3"), response.getRandoms());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectPasswordLoginWhenMasterKeyIsNotReady() {
|
||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||
roleAccounts.save(activeRole(RoleCode.AUDIT_ADMIN, "HASH:12345678:SALT-A", "SALT-A", 0, false));
|
||||
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
|
||||
org.mockito.Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.ABNORMAL.getDetail());
|
||||
InMemoryCaptchaService captchaService = new InMemoryCaptchaService();
|
||||
String captchaId = captchaService.issue("ABCD").getCaptchaId();
|
||||
|
||||
AuthService service = new AuthService(
|
||||
roleAccounts,
|
||||
new InMemoryLegacyAuthUserRepository(),
|
||||
new InMemoryAuthSessionRepository(),
|
||||
new InMemoryRoleUkeyBindingRepository(),
|
||||
new FakePasswordHasher(),
|
||||
lmkService,
|
||||
org.mockito.Mockito.mock(UkeyLoginRandomService.class),
|
||||
org.mockito.Mockito.mock(CompatUkeyVerifier.class),
|
||||
captchaService,
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
() -> "unused"
|
||||
);
|
||||
|
||||
PasswordLoginRequest request = new PasswordLoginRequest();
|
||||
request.setRoleCode(RoleCode.AUDIT_ADMIN.getCode());
|
||||
request.setPassword("12345678");
|
||||
request.setCaptchaId(captchaId);
|
||||
request.setCaptchaCode("ABCD");
|
||||
|
||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.passwordLogin(request));
|
||||
|
||||
Assertions.assertEquals("master key is not ready", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIssueCaptchaAndAcceptPasswordLoginWithValidCaptcha() {
|
||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||
roleAccounts.save(activeRole(RoleCode.OPS_ADMIN, "HASH:12345678:SALT-O", "SALT-O", 0, false));
|
||||
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
|
||||
org.mockito.Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.NORMAL.getDetail("MAC-001"));
|
||||
InMemoryCaptchaService captchaService = new InMemoryCaptchaService();
|
||||
|
||||
AuthService service = new AuthService(
|
||||
roleAccounts,
|
||||
new InMemoryLegacyAuthUserRepository(),
|
||||
new InMemoryAuthSessionRepository(),
|
||||
new InMemoryRoleUkeyBindingRepository(),
|
||||
new FakePasswordHasher(),
|
||||
lmkService,
|
||||
org.mockito.Mockito.mock(UkeyLoginRandomService.class),
|
||||
org.mockito.Mockito.mock(CompatUkeyVerifier.class),
|
||||
captchaService,
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
() -> "token-password-002"
|
||||
);
|
||||
|
||||
CaptchaResponse captcha = service.issueCaptcha();
|
||||
|
||||
PasswordLoginRequest request = new PasswordLoginRequest();
|
||||
request.setRoleCode(RoleCode.OPS_ADMIN.getCode());
|
||||
request.setPassword("12345678");
|
||||
request.setCaptchaId(captcha.getCaptchaId());
|
||||
request.setCaptchaCode("ABCD");
|
||||
|
||||
LoginResponse response = service.passwordLogin(request);
|
||||
|
||||
Assertions.assertEquals("token-password-002", response.getToken());
|
||||
Assertions.assertEquals(AuthLevel.LIMITED.name(), response.getAuthLevel());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldVerifyIssuedUkeysRandomsAndLoginSignsBeforeCreatingFullSession() {
|
||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
||||
InMemoryRoleUkeyBindingRepository ukeyBindings = new InMemoryRoleUkeyBindingRepository();
|
||||
roleAccounts.save(activeRole(RoleCode.KEY_ADMIN, "HASH:12345678:SALT-K", "SALT-K", 0, false));
|
||||
ukeyBindings.save(activeBinding(RoleCode.KEY_ADMIN, 1, "UK-1", "PUB-1", "4", "4"));
|
||||
ukeyBindings.save(activeBinding(RoleCode.KEY_ADMIN, 2, "UK-2", "PUB-2", "5", "5"));
|
||||
|
||||
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
|
||||
UkeyLoginRandomService randomService = org.mockito.Mockito.mock(UkeyLoginRandomService.class);
|
||||
CompatUkeyVerifier verifier = org.mockito.Mockito.mock(CompatUkeyVerifier.class);
|
||||
org.mockito.Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.NORMAL.getDetail("MAC-001"));
|
||||
org.mockito.Mockito.when(lmkService.exportIkPublicKeyHex()).thenReturn("IK-PUB-001");
|
||||
org.mockito.Mockito.when(randomService.issue(RoleCode.KEY_ADMIN.getCode(), 2)).thenReturn(List.of("RB-1", "RB-2"));
|
||||
|
||||
AuthService service = new AuthService(
|
||||
roleAccounts,
|
||||
new InMemoryLegacyAuthUserRepository(),
|
||||
sessions,
|
||||
ukeyBindings,
|
||||
new FakePasswordHasher(),
|
||||
lmkService,
|
||||
randomService,
|
||||
verifier,
|
||||
new InMemoryCaptchaService(),
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
() -> "token-full-ukey-002"
|
||||
);
|
||||
|
||||
UkeyLoginRandomRequest randomRequest = new UkeyLoginRandomRequest();
|
||||
randomRequest.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
||||
service.issueUkeyLoginRandoms(randomRequest);
|
||||
|
||||
UkeyLoginRequest request = new UkeyLoginRequest();
|
||||
request.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
||||
request.setPassword("12345678");
|
||||
request.setUkeyProofs(List.of(
|
||||
proof("PUB-1", "4", "4", "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1"),
|
||||
proof("PUB-2", "5", "5", "RB-2", "ISSUE-2", "LOGIN-DATA-2", "LOGIN-SIGN-2")
|
||||
));
|
||||
|
||||
LoginResponse response = service.ukeyLogin(request);
|
||||
|
||||
Assertions.assertEquals("token-full-ukey-002", response.getToken());
|
||||
Assertions.assertEquals(AuthLevel.FULL.name(), response.getAuthLevel());
|
||||
org.mockito.Mockito.verify(randomService).assertIssued(RoleCode.KEY_ADMIN.getCode(), List.of("RB-1", "RB-2"));
|
||||
org.mockito.Mockito.verify(verifier).verifyIssuedBinding(
|
||||
"{\"pubKey\":\"PUB-1\",\"authKeyPair\":\"IK-PUB-001\",\"role\":\"keyadmin\",\"uid\":\"4\",\"rid\":\"4\"}",
|
||||
"ISSUE-1"
|
||||
);
|
||||
org.mockito.Mockito.verify(verifier).verifyIssuedBinding(
|
||||
"{\"pubKey\":\"PUB-2\",\"authKeyPair\":\"IK-PUB-001\",\"role\":\"keyadmin\",\"uid\":\"5\",\"rid\":\"5\"}",
|
||||
"ISSUE-2"
|
||||
);
|
||||
org.mockito.Mockito.verify(verifier).verifyLoginSignature("PUB-1", "LOGIN-DATA-1", "LOGIN-SIGN-1");
|
||||
org.mockito.Mockito.verify(verifier).verifyLoginSignature("PUB-2", "LOGIN-DATA-2", "LOGIN-SIGN-2");
|
||||
}
|
||||
|
||||
private static RoleAccountEntity activeRole(
|
||||
RoleCode roleCode,
|
||||
String passwordHash,
|
||||
@ -297,6 +471,21 @@ class AuthServiceTest {
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static RoleUkeyBindingEntity activeBinding(
|
||||
RoleCode roleCode,
|
||||
int slotNo,
|
||||
String serial,
|
||||
String pubKey,
|
||||
String uid,
|
||||
String rid
|
||||
) {
|
||||
RoleUkeyBindingEntity entity = activeBinding(roleCode, slotNo, serial);
|
||||
entity.setUkeyPubkey(pubKey);
|
||||
entity.setUid(uid);
|
||||
entity.setRid(rid);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static class FakePasswordHasher implements PasswordHasher {
|
||||
|
||||
@Override
|
||||
@ -403,4 +592,50 @@ class AuthServiceTest {
|
||||
entity.setExpiresAt(LocalDateTime.of(2026, 3, 23, 2, 8));
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static UkeyLoginProof proof(
|
||||
String pubKey,
|
||||
String uid,
|
||||
String rid,
|
||||
String serverRandom,
|
||||
String issueSignature,
|
||||
String loginPayload,
|
||||
String loginSignature
|
||||
) {
|
||||
UkeyLoginProof proof = new UkeyLoginProof();
|
||||
proof.setPubKey(pubKey);
|
||||
proof.setUid(uid);
|
||||
proof.setRid(rid);
|
||||
proof.setServerRandom(serverRandom);
|
||||
proof.setIssueSignature(issueSignature);
|
||||
proof.setLoginPayload(loginPayload);
|
||||
proof.setLoginSignature(loginSignature);
|
||||
return proof;
|
||||
}
|
||||
|
||||
private static class InMemoryCaptchaService implements CaptchaService {
|
||||
private final Map<String, String> store = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public CaptchaResponse issueCaptcha() {
|
||||
return issue("ABCD");
|
||||
}
|
||||
|
||||
private CaptchaResponse issue(String code) {
|
||||
String captchaId = "captcha-" + store.size();
|
||||
store.put(captchaId, code);
|
||||
CaptchaResponse response = new CaptchaResponse();
|
||||
response.setCaptchaId(captchaId);
|
||||
response.setImageBase64("mock-image");
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(String captchaId, String captchaCode) {
|
||||
String expected = store.remove(captchaId);
|
||||
if (expected == null || !expected.equalsIgnoreCase(captchaCode)) {
|
||||
throw new BizException(com.cisd.tms.common.enums.ErrorCode.UNAUTHORIZED.getCode(), "captcha verification failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,198 +0,0 @@
|
||||
package com.cisd.tms.modules.auth.service;
|
||||
|
||||
import com.cisd.tms.common.exception.BizException;
|
||||
import com.cisd.tms.modules.auth.dto.CompatPasswordLoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.CompatUkeyLoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
|
||||
import com.cisd.tms.modules.mk.service.LmkService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class CompatAuthServiceTest {
|
||||
|
||||
@Test
|
||||
void shouldIssueRoleScopedRandomsForUkeyLogin() {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
RoleUkeyBindingRepository bindings = Mockito.mock(RoleUkeyBindingRepository.class);
|
||||
LmkService lmkService = Mockito.mock(LmkService.class);
|
||||
UkeyLoginRandomService randomService = Mockito.mock(UkeyLoginRandomService.class);
|
||||
CompatUkeyVerifier verifier = Mockito.mock(CompatUkeyVerifier.class);
|
||||
Mockito.when(randomService.issue(RoleCode.SUPER_ADMIN.getCode(), 3)).thenReturn(List.of("RB-1", "RB-2", "RB-3"));
|
||||
|
||||
CompatAuthService service = new CompatAuthService(authService, bindings, lmkService, randomService, verifier, new ObjectMapper());
|
||||
|
||||
UkeyLoginRandomResponse response = service.issueUkeyLoginRandoms("superadmin", null);
|
||||
|
||||
Assertions.assertEquals(RoleCode.SUPER_ADMIN.getCode(), response.getRoleCode());
|
||||
Assertions.assertEquals(List.of("RB-1", "RB-2", "RB-3"), response.getRandoms());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDelegateCompatiblePasswordLoginIntoCurrentRoleLoginFlow() {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
RoleUkeyBindingRepository bindings = Mockito.mock(RoleUkeyBindingRepository.class);
|
||||
LmkService lmkService = Mockito.mock(LmkService.class);
|
||||
UkeyLoginRandomService randomService = Mockito.mock(UkeyLoginRandomService.class);
|
||||
CompatUkeyVerifier verifier = Mockito.mock(CompatUkeyVerifier.class);
|
||||
Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.NORMAL.getDetail("MAC-001"));
|
||||
LoginResponse loginResponse = new LoginResponse();
|
||||
loginResponse.setRoleCode(RoleCode.AUDIT_ADMIN.getCode());
|
||||
loginResponse.setAuthLevel("LIMITED");
|
||||
loginResponse.setToken("token-password-001");
|
||||
Mockito.when(authService.login(ArgumentMatchers.any())).thenReturn(loginResponse);
|
||||
|
||||
CompatAuthService service = new CompatAuthService(authService, bindings, lmkService, randomService, verifier, new ObjectMapper());
|
||||
CompatPasswordLoginRequest request = new CompatPasswordLoginRequest();
|
||||
request.setRole("auditadmin");
|
||||
request.setPassword("12345678");
|
||||
|
||||
LoginResponse response = service.passwordLogin(request);
|
||||
|
||||
Assertions.assertEquals("token-password-001", response.getToken());
|
||||
ArgumentCaptor<com.cisd.tms.modules.auth.dto.LoginRequest> captor =
|
||||
ArgumentCaptor.forClass(com.cisd.tms.modules.auth.dto.LoginRequest.class);
|
||||
Mockito.verify(authService).login(captor.capture());
|
||||
Assertions.assertEquals(RoleCode.AUDIT_ADMIN.getCode(), captor.getValue().getRoleCode());
|
||||
Assertions.assertEquals("12345678", captor.getValue().getPassword());
|
||||
Assertions.assertNull(captor.getValue().getUkeySerials());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectCompatiblePasswordLoginWhenMasterKeyIsNotReady() {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
RoleUkeyBindingRepository bindings = Mockito.mock(RoleUkeyBindingRepository.class);
|
||||
LmkService lmkService = Mockito.mock(LmkService.class);
|
||||
UkeyLoginRandomService randomService = Mockito.mock(UkeyLoginRandomService.class);
|
||||
CompatUkeyVerifier verifier = Mockito.mock(CompatUkeyVerifier.class);
|
||||
Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.ABNORMAL.getDetail());
|
||||
|
||||
CompatAuthService service = new CompatAuthService(authService, bindings, lmkService, randomService, verifier, new ObjectMapper());
|
||||
CompatPasswordLoginRequest request = new CompatPasswordLoginRequest();
|
||||
request.setRole("auditadmin");
|
||||
request.setPassword("12345678");
|
||||
|
||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.passwordLogin(request));
|
||||
|
||||
Assertions.assertEquals("master key is not ready", exception.getMessage());
|
||||
Mockito.verifyNoInteractions(authService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldVerifyIssuedUkeysRandomsAndLoginSignsBeforeCreatingFullSession() {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
RoleUkeyBindingRepository bindings = Mockito.mock(RoleUkeyBindingRepository.class);
|
||||
LmkService lmkService = Mockito.mock(LmkService.class);
|
||||
UkeyLoginRandomService randomService = Mockito.mock(UkeyLoginRandomService.class);
|
||||
CompatUkeyVerifier verifier = Mockito.mock(CompatUkeyVerifier.class);
|
||||
Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.NORMAL.getDetail("MAC-001"));
|
||||
Mockito.when(lmkService.exportIkPublicKeyHex()).thenReturn("IK-PUB-001");
|
||||
Mockito.when(bindings.findActiveByRoleCode(RoleCode.KEY_ADMIN.getCode())).thenReturn(List.of(
|
||||
binding(1, "UK-1", "PUB-1"),
|
||||
binding(2, "UK-2", "PUB-2")
|
||||
));
|
||||
LoginResponse loginResponse = new LoginResponse();
|
||||
loginResponse.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
||||
loginResponse.setAuthLevel("FULL");
|
||||
loginResponse.setToken("token-full-001");
|
||||
Mockito.when(authService.login(ArgumentMatchers.any())).thenReturn(loginResponse);
|
||||
|
||||
CompatAuthService service = new CompatAuthService(authService, bindings, lmkService, randomService, verifier, new ObjectMapper());
|
||||
CompatUkeyLoginRequest request = new CompatUkeyLoginRequest();
|
||||
request.setRole("keyadmin");
|
||||
request.setPassword("12345678");
|
||||
request.setAuthInfo(List.of(
|
||||
authInfo("PUB-1", "1", "RID-1", "RA-1", "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1"),
|
||||
authInfo("PUB-2", "2", "RID-2", "RA-2", "RB-2", "ISSUE-2", "LOGIN-DATA-2", "LOGIN-SIGN-2")
|
||||
));
|
||||
|
||||
LoginResponse response = service.ukeyLogin(request);
|
||||
|
||||
Assertions.assertEquals("token-full-001", response.getToken());
|
||||
Mockito.verify(randomService).assertIssued(RoleCode.KEY_ADMIN.getCode(), List.of("RB-1", "RB-2"));
|
||||
Mockito.verify(verifier).verifyIssuedBinding(
|
||||
"{\"pubKey\":\"PUB-1\",\"authKeyPair\":\"IK-PUB-001\",\"role\":\"keyadmin\",\"uid\":\"1\",\"rid\":\"RID-1\"}",
|
||||
"ISSUE-1"
|
||||
);
|
||||
Mockito.verify(verifier).verifyIssuedBinding(
|
||||
"{\"pubKey\":\"PUB-2\",\"authKeyPair\":\"IK-PUB-001\",\"role\":\"keyadmin\",\"uid\":\"2\",\"rid\":\"RID-2\"}",
|
||||
"ISSUE-2"
|
||||
);
|
||||
Mockito.verify(verifier).verifyLoginSignature("PUB-1", "LOGIN-DATA-1", "LOGIN-SIGN-1");
|
||||
Mockito.verify(verifier).verifyLoginSignature("PUB-2", "LOGIN-DATA-2", "LOGIN-SIGN-2");
|
||||
|
||||
ArgumentCaptor<com.cisd.tms.modules.auth.dto.LoginRequest> captor =
|
||||
ArgumentCaptor.forClass(com.cisd.tms.modules.auth.dto.LoginRequest.class);
|
||||
Mockito.verify(authService).login(captor.capture());
|
||||
Assertions.assertEquals(RoleCode.KEY_ADMIN.getCode(), captor.getValue().getRoleCode());
|
||||
Assertions.assertEquals("12345678", captor.getValue().getPassword());
|
||||
Assertions.assertEquals(List.of("UK-1", "UK-2"), captor.getValue().getUkeySerials());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectUkeyLoginWhenRequestPubkeyIsNotBoundToRole() {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
RoleUkeyBindingRepository bindings = Mockito.mock(RoleUkeyBindingRepository.class);
|
||||
LmkService lmkService = Mockito.mock(LmkService.class);
|
||||
UkeyLoginRandomService randomService = Mockito.mock(UkeyLoginRandomService.class);
|
||||
CompatUkeyVerifier verifier = Mockito.mock(CompatUkeyVerifier.class);
|
||||
Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.NORMAL.getDetail("MAC-001"));
|
||||
Mockito.when(bindings.findActiveByRoleCode(RoleCode.AUDIT_ADMIN.getCode())).thenReturn(List.of(
|
||||
binding(1, "UK-1", "PUB-BOUND")
|
||||
));
|
||||
|
||||
CompatAuthService service = new CompatAuthService(authService, bindings, lmkService, randomService, verifier, new ObjectMapper());
|
||||
CompatUkeyLoginRequest request = new CompatUkeyLoginRequest();
|
||||
request.setRole("auditadmin");
|
||||
request.setPassword("12345678");
|
||||
request.setAuthInfo(List.of(authInfo("PUB-OTHER", "1", "RID-1", "RA-1", "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1")));
|
||||
|
||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.ukeyLogin(request));
|
||||
|
||||
Assertions.assertEquals("ukey auth info does not match bound role", exception.getMessage());
|
||||
Mockito.verifyNoInteractions(authService);
|
||||
}
|
||||
|
||||
private static RoleUkeyBindingEntity binding(int slotNo, String serial, String pubkey) {
|
||||
RoleUkeyBindingEntity entity = new RoleUkeyBindingEntity();
|
||||
entity.setId((long) slotNo);
|
||||
entity.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
||||
entity.setSlotNo(slotNo);
|
||||
entity.setUkeySerial(serial);
|
||||
entity.setUkeyPubkey(pubkey);
|
||||
entity.setStatus("ACTIVE");
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static CompatUkeyLoginRequest.LoginAuthInfo authInfo(
|
||||
String pubKey,
|
||||
String uid,
|
||||
String rid,
|
||||
String ra,
|
||||
String rb,
|
||||
String issueSign,
|
||||
String loginSignData,
|
||||
String loginSign
|
||||
) {
|
||||
CompatUkeyLoginRequest.LoginAuthInfo info = new CompatUkeyLoginRequest.LoginAuthInfo();
|
||||
info.setPubKey(pubKey);
|
||||
info.setUid(uid);
|
||||
info.setRid(rid);
|
||||
info.setRa(ra);
|
||||
info.setRb(rb);
|
||||
info.setIssueSign(issueSign);
|
||||
info.setLoginSignData(loginSignData);
|
||||
info.setLoginSign(loginSign);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user