refactor: unify auth api and preserve legacy auth flows
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user