refactor: unify auth api and preserve legacy auth flows

This commit is contained in:
waner
2026-03-30 16:03:52 +08:00
parent b9f561cd1f
commit ba919b4aff
28 changed files with 1185 additions and 874 deletions
@@ -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,