角色和登录改动
This commit is contained in:
@@ -2,8 +2,9 @@ package com.cisd.tms.modules.auth.controller;
|
|||||||
|
|
||||||
import com.cisd.tms.common.api.ApiResponse;
|
import com.cisd.tms.common.api.ApiResponse;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyBindRequest;
|
import com.cisd.tms.modules.auth.dto.UkeyBindRequest;
|
||||||
import com.cisd.tms.modules.auth.security.RequireAuthLevel;
|
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||||
import com.cisd.tms.modules.auth.security.RequireRole;
|
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||||
|
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
|
||||||
import com.cisd.tms.modules.auth.service.AuthAdminService;
|
import com.cisd.tms.modules.auth.service.AuthAdminService;
|
||||||
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
||||||
import com.cisd.tms.modules.mk.dto.UKeySignResult;
|
import com.cisd.tms.modules.mk.dto.UKeySignResult;
|
||||||
@@ -31,9 +32,8 @@ public class AuthAdminController {
|
|||||||
private final AuthAdminService authAdminService;
|
private final AuthAdminService authAdminService;
|
||||||
|
|
||||||
@PostMapping("/roles/{roleCode}/enable")
|
@PostMapping("/roles/{roleCode}/enable")
|
||||||
@Operation(summary = "启用角色", description = "仅允许 SUPER_ADMIN FULL 会话启用目标角色。")
|
@Operation(summary = "启用角色", description = "仅允许 KEY_ADMIN FULL 会话启用目标角色。")
|
||||||
@RequireRole("SUPER_ADMIN")
|
@RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.FULL)
|
||||||
@RequireAuthLevel("FULL")
|
|
||||||
public ApiResponse<Void> enableRole(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
|
public ApiResponse<Void> enableRole(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
|
||||||
authAdminService.enableRole(
|
authAdminService.enableRole(
|
||||||
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||||
@@ -44,9 +44,8 @@ public class AuthAdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/roles/{roleCode}/reset-password")
|
@PostMapping("/roles/{roleCode}/reset-password")
|
||||||
@Operation(summary = "重置角色密码", description = "仅允许 SUPER_ADMIN FULL 会话重置目标角色密码。")
|
@Operation(summary = "重置角色密码", description = "仅允许 KEY_ADMIN FULL 会话重置目标角色密码。")
|
||||||
@RequireRole("SUPER_ADMIN")
|
@RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.FULL)
|
||||||
@RequireAuthLevel("FULL")
|
|
||||||
public ApiResponse<Void> resetPassword(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
|
public ApiResponse<Void> resetPassword(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
|
||||||
authAdminService.resetPassword(
|
authAdminService.resetPassword(
|
||||||
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||||
@@ -57,9 +56,8 @@ public class AuthAdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/roles/{roleCode}/ukeys/bind")
|
@PostMapping("/roles/{roleCode}/ukeys/bind")
|
||||||
@Operation(summary = "绑定角色 UKey", description = "仅允许 SUPER_ADMIN FULL 会话登记目标角色的 UKey 绑定信息。")
|
@Operation(summary = "绑定角色 UKey", description = "仅允许 KEY_ADMIN FULL 会话登记目标角色的 UKey 绑定信息。")
|
||||||
@RequireRole("SUPER_ADMIN")
|
@RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.FULL)
|
||||||
@RequireAuthLevel("FULL")
|
|
||||||
public ApiResponse<Void> bindUkey(
|
public ApiResponse<Void> bindUkey(
|
||||||
@PathVariable("roleCode") String roleCode,
|
@PathVariable("roleCode") String roleCode,
|
||||||
@Valid @RequestBody UkeyBindRequest request,
|
@Valid @RequestBody UkeyBindRequest request,
|
||||||
@@ -69,11 +67,9 @@ public class AuthAdminController {
|
|||||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
|
||||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
|
||||||
roleCode,
|
roleCode,
|
||||||
request.getSlotNo(),
|
request.getUid(),
|
||||||
request.getUkeySerial(),
|
request.getUkeySerial(),
|
||||||
request.getPubKey(),
|
request.getPubKey(),
|
||||||
request.getUid(),
|
|
||||||
request.getRid(),
|
|
||||||
request.getIssuerSignature()
|
request.getIssuerSignature()
|
||||||
);
|
);
|
||||||
return ApiResponse.success();
|
return ApiResponse.success();
|
||||||
@@ -81,8 +77,7 @@ public class AuthAdminController {
|
|||||||
|
|
||||||
@PostMapping("/roles/{roleCode}/ukeys/issue-sign")
|
@PostMapping("/roles/{roleCode}/ukeys/issue-sign")
|
||||||
@Operation(summary = "生成 UKey 发行签名", description = "按旧绑定流程为目标角色 UKey 材料生成发行签名。")
|
@Operation(summary = "生成 UKey 发行签名", description = "按旧绑定流程为目标角色 UKey 材料生成发行签名。")
|
||||||
// @RequireRole("SUPER_ADMIN")
|
@RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.LIMITED)
|
||||||
@RequireAuthLevel("FULL")
|
|
||||||
public ApiResponse<UKeySignResult> issueUkeyBindingSign(
|
public ApiResponse<UKeySignResult> issueUkeyBindingSign(
|
||||||
@PathVariable("roleCode") String roleCode,
|
@PathVariable("roleCode") String roleCode,
|
||||||
@RequestBody UKeySignDTO request,
|
@RequestBody UKeySignDTO request,
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ public class LoginRequest {
|
|||||||
@Schema(description = "角色编码", example = "KEY_ADMIN")
|
@Schema(description = "角色编码", example = "KEY_ADMIN")
|
||||||
private String roleCode;
|
private String roleCode;
|
||||||
|
|
||||||
@NotBlank(message = "password is required")
|
@Schema(description = "参与角色口令校验的账号列表")
|
||||||
@Schema(description = "登录密码", example = "P@ssw0rd")
|
private List<PasswordLoginAccountRequest> accounts;
|
||||||
private String password;
|
|
||||||
|
@Schema(description = "角色口令")
|
||||||
|
private String rolePassword;
|
||||||
|
|
||||||
@Schema(description = "UKey 序列号列表,完整登录时需传入角色要求数量的序列号")
|
@Schema(description = "UKey 序列号列表,完整登录时需传入角色要求数量的序列号")
|
||||||
private List<@NotBlank(message = "ukey serial must not be blank") String> ukeySerials;
|
private List<@NotBlank(message = "ukey serial must not be blank") String> ukeySerials;
|
||||||
@@ -26,12 +28,20 @@ public class LoginRequest {
|
|||||||
this.roleCode = roleCode;
|
this.roleCode = roleCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPassword() {
|
public List<PasswordLoginAccountRequest> getAccounts() {
|
||||||
return password;
|
return accounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPassword(String password) {
|
public void setAccounts(List<PasswordLoginAccountRequest> accounts) {
|
||||||
this.password = password;
|
this.accounts = accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRolePassword() {
|
||||||
|
return rolePassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRolePassword(String rolePassword) {
|
||||||
|
this.rolePassword = rolePassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getUkeySerials() {
|
public List<String> getUkeySerials() {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.cisd.tms.modules.auth.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
@Schema(description = "角色口令校验账号")
|
||||||
|
public class PasswordLoginAccountRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "username is required")
|
||||||
|
@Schema(description = "账号名", example = "super-admin-01")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@NotBlank(message = "password is required")
|
||||||
|
@Schema(description = "账号口令", example = "12345678")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.cisd.tms.modules.auth.dto;
|
package com.cisd.tms.modules.auth.dto;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Schema(description = "口令登录请求")
|
@Schema(description = "口令登录请求")
|
||||||
public class PasswordLoginRequest {
|
public class PasswordLoginRequest {
|
||||||
@@ -13,9 +16,10 @@ public class PasswordLoginRequest {
|
|||||||
@Schema(description = "角色编码", example = "AUDIT_ADMIN")
|
@Schema(description = "角色编码", example = "AUDIT_ADMIN")
|
||||||
private String roleCode;
|
private String roleCode;
|
||||||
|
|
||||||
@NotBlank(message = "password is required")
|
@Valid
|
||||||
@Schema(description = "登录口令", example = "12345678")
|
@NotEmpty(message = "accounts is required")
|
||||||
private String password;
|
@Schema(description = "参与本次角色口令校验的账号列表")
|
||||||
|
private List<PasswordLoginAccountRequest> accounts;
|
||||||
|
|
||||||
@NotBlank(message = "captchaCode is required")
|
@NotBlank(message = "captchaCode is required")
|
||||||
@Schema(description = "图形验证码", example = "ABCD")
|
@Schema(description = "图形验证码", example = "ABCD")
|
||||||
@@ -33,12 +37,12 @@ public class PasswordLoginRequest {
|
|||||||
this.roleCode = roleCode;
|
this.roleCode = roleCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPassword() {
|
public List<PasswordLoginAccountRequest> getAccounts() {
|
||||||
return password;
|
return accounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPassword(String password) {
|
public void setAccounts(List<PasswordLoginAccountRequest> accounts) {
|
||||||
this.password = password;
|
this.accounts = accounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCaptchaCode() {
|
public String getCaptchaCode() {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
package com.cisd.tms.modules.auth.dto;
|
package com.cisd.tms.modules.auth.dto;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
@Schema(description = "UKey 绑定请求")
|
@Schema(description = "UKey 绑定请求")
|
||||||
public class UkeyBindRequest {
|
public class UkeyBindRequest {
|
||||||
|
|
||||||
@NotNull(message = "slotNo is required")
|
@NotNull(message = "uid is required")
|
||||||
@Schema(description = "角色 UKey 槽位号", example = "1")
|
@Schema(description = "角色内固定席位编号", example = "1")
|
||||||
private Integer slotNo;
|
private Integer uid;
|
||||||
|
|
||||||
@NotBlank(message = "ukeySerial is required")
|
@NotBlank(message = "ukeySerial is required")
|
||||||
@Schema(description = "UKey 序列号", example = "UK-001")
|
@Schema(description = "UKey 序列号", example = "UK-001")
|
||||||
@@ -19,24 +19,16 @@ public class UkeyBindRequest {
|
|||||||
@Schema(description = "UKey 公钥")
|
@Schema(description = "UKey 公钥")
|
||||||
private String pubKey;
|
private String pubKey;
|
||||||
|
|
||||||
@NotBlank(message = "uid is required")
|
|
||||||
@Schema(description = "UKey 认证信息 uid", example = "1")
|
|
||||||
private String uid;
|
|
||||||
|
|
||||||
@NotBlank(message = "rid is required")
|
|
||||||
@Schema(description = "UKey 认证信息 rid", example = "RID-001")
|
|
||||||
private String rid;
|
|
||||||
|
|
||||||
@NotBlank(message = "issuerSignature is required")
|
@NotBlank(message = "issuerSignature is required")
|
||||||
@Schema(description = "认证公钥签名值")
|
@Schema(description = "认证公钥签名值")
|
||||||
private String issuerSignature;
|
private String issuerSignature;
|
||||||
|
|
||||||
public Integer getSlotNo() {
|
public Integer getUid() {
|
||||||
return slotNo;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSlotNo(Integer slotNo) {
|
public void setUid(Integer uid) {
|
||||||
this.slotNo = slotNo;
|
this.uid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUkeySerial() {
|
public String getUkeySerial() {
|
||||||
@@ -62,20 +54,4 @@ public class UkeyBindRequest {
|
|||||||
public void setIssuerSignature(String issuerSignature) {
|
public void setIssuerSignature(String issuerSignature) {
|
||||||
this.issuerSignature = issuerSignature;
|
this.issuerSignature = issuerSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.cisd.tms.modules.auth.dto;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
@Schema(description = "单个 UKey 登录证明")
|
@Schema(description = "单个 UKey 登录证明")
|
||||||
public class UkeyLoginProof {
|
public class UkeyLoginProof {
|
||||||
@@ -9,11 +10,8 @@ public class UkeyLoginProof {
|
|||||||
@NotBlank(message = "pubKey is required")
|
@NotBlank(message = "pubKey is required")
|
||||||
private String pubKey;
|
private String pubKey;
|
||||||
|
|
||||||
@NotBlank(message = "uid is required")
|
@NotNull(message = "uid is required")
|
||||||
private String uid;
|
private Integer uid;
|
||||||
|
|
||||||
@NotBlank(message = "rid is required")
|
|
||||||
private String rid;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对应旧系统下发的 rb,命名改为更明确的服务端随机数。
|
* 对应旧系统下发的 rb,命名改为更明确的服务端随机数。
|
||||||
@@ -38,22 +36,14 @@ public class UkeyLoginProof {
|
|||||||
this.pubKey = pubKey;
|
this.pubKey = pubKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUid() {
|
public Integer getUid() {
|
||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUid(String uid) {
|
public void setUid(Integer uid) {
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRid() {
|
|
||||||
return rid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRid(String rid) {
|
|
||||||
this.rid = rid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getServerRandom() {
|
public String getServerRandom() {
|
||||||
return serverRandom;
|
return serverRandom;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ public class UkeyLoginRequest {
|
|||||||
@Schema(description = "角色编码", example = "KEY_ADMIN")
|
@Schema(description = "角色编码", example = "KEY_ADMIN")
|
||||||
private String roleCode;
|
private String roleCode;
|
||||||
|
|
||||||
@NotBlank(message = "password is required")
|
@NotBlank(message = "rolePassword is required")
|
||||||
@Schema(description = "角色口令", example = "12345678")
|
@Schema(description = "角色口令", example = "12345678")
|
||||||
private String password;
|
private String rolePassword;
|
||||||
|
|
||||||
@Valid
|
@Valid
|
||||||
@NotEmpty(message = "ukeyProofs is required")
|
@NotEmpty(message = "ukeyProofs is required")
|
||||||
@@ -30,12 +30,12 @@ public class UkeyLoginRequest {
|
|||||||
this.roleCode = roleCode;
|
this.roleCode = roleCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPassword() {
|
public String getRolePassword() {
|
||||||
return password;
|
return rolePassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPassword(String password) {
|
public void setRolePassword(String rolePassword) {
|
||||||
this.password = password;
|
this.rolePassword = rolePassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<UkeyLoginProof> getUkeyProofs() {
|
public List<UkeyLoginProof> getUkeyProofs() {
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ public class AuthSessionEntity extends BaseEntity {
|
|||||||
* 当前会话所属角色编码。
|
* 当前会话所属角色编码。
|
||||||
*/
|
*/
|
||||||
private String roleCode;
|
private String roleCode;
|
||||||
|
/**
|
||||||
|
* 本次角色会话在后端侧记录的认证账号列表。
|
||||||
|
*/
|
||||||
|
private String authenticatedUsersJson;
|
||||||
/**
|
/**
|
||||||
* 当前会话的认证方式,区分口令登录和 UKey 登录。
|
* 当前会话的认证方式,区分口令登录和 UKey 登录。
|
||||||
*/
|
*/
|
||||||
@@ -44,6 +48,14 @@ public class AuthSessionEntity extends BaseEntity {
|
|||||||
this.roleCode = roleCode;
|
this.roleCode = roleCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAuthenticatedUsersJson() {
|
||||||
|
return authenticatedUsersJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticatedUsersJson(String authenticatedUsersJson) {
|
||||||
|
this.authenticatedUsersJson = authenticatedUsersJson;
|
||||||
|
}
|
||||||
|
|
||||||
public String getAuthMethod() {
|
public String getAuthMethod() {
|
||||||
return authMethod;
|
return authMethod;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.cisd.tms.modules.auth.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.cisd.tms.infrastructure.persistence.entity.BaseEntity;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@TableName("tms_auth_user_account")
|
||||||
|
public class AuthUserAccountEntity extends BaseEntity {
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
private String displayName;
|
||||||
|
private String roleCode;
|
||||||
|
private String passwordHash;
|
||||||
|
private String passwordSalt;
|
||||||
|
private String status;
|
||||||
|
private Integer failedCount;
|
||||||
|
private LocalDateTime lockedUntil;
|
||||||
|
private LocalDateTime lastLoginAt;
|
||||||
|
private LocalDateTime lastActiveAt;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayName(String displayName) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoleCode() {
|
||||||
|
return roleCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleCode(String roleCode) {
|
||||||
|
this.roleCode = roleCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPasswordHash() {
|
||||||
|
return passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordHash(String passwordHash) {
|
||||||
|
this.passwordHash = passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPasswordSalt() {
|
||||||
|
return passwordSalt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordSalt(String passwordSalt) {
|
||||||
|
this.passwordSalt = passwordSalt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getFailedCount() {
|
||||||
|
return failedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFailedCount(Integer failedCount) {
|
||||||
|
this.failedCount = failedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getLockedUntil() {
|
||||||
|
return lockedUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLockedUntil(LocalDateTime lockedUntil) {
|
||||||
|
this.lockedUntil = lockedUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getLastLoginAt() {
|
||||||
|
return lastLoginAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastLoginAt(LocalDateTime lastLoginAt) {
|
||||||
|
this.lastLoginAt = lastLoginAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getLastActiveAt() {
|
||||||
|
return lastActiveAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastActiveAt(LocalDateTime lastActiveAt) {
|
||||||
|
this.lastActiveAt = lastActiveAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,17 +12,9 @@ public class RoleUkeyBindingEntity extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private String roleCode;
|
private String roleCode;
|
||||||
/**
|
/**
|
||||||
* 角色下的 UKey 槽位,从 1 开始,和旧系统卡位模型保持一致。
|
* 角色内固定席位编号,从 1 开始。
|
||||||
*/
|
*/
|
||||||
private Integer slotNo;
|
private Integer uid;
|
||||||
/**
|
|
||||||
* 旧系统 auth 文件中的 uid,登录时要参与认证匹配。
|
|
||||||
*/
|
|
||||||
private String uid;
|
|
||||||
/**
|
|
||||||
* 旧系统 auth 文件中的 rid,登录时要参与角色槽位校验。
|
|
||||||
*/
|
|
||||||
private String rid;
|
|
||||||
private String ukeySerial;
|
private String ukeySerial;
|
||||||
private String ukeyPubkey;
|
private String ukeyPubkey;
|
||||||
private String issuerSign;
|
private String issuerSign;
|
||||||
@@ -38,30 +30,14 @@ public class RoleUkeyBindingEntity extends BaseEntity {
|
|||||||
this.roleCode = roleCode;
|
this.roleCode = roleCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getSlotNo() {
|
public Integer getUid() {
|
||||||
return slotNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSlotNo(Integer slotNo) {
|
|
||||||
this.slotNo = slotNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUid() {
|
|
||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUid(String uid) {
|
public void setUid(Integer uid) {
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRid() {
|
|
||||||
return rid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRid(String rid) {
|
|
||||||
this.rid = rid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUkeySerial() {
|
public String getUkeySerial() {
|
||||||
return ukeySerial;
|
return ukeySerial;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.cisd.tms.modules.auth.mapper;
|
||||||
|
|
||||||
|
import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX;
|
||||||
|
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface AuthUserAccountMapper extends BaseMapperX<AuthUserAccountEntity> {
|
||||||
|
|
||||||
|
AuthUserAccountEntity selectByUsername(@Param("username") String username);
|
||||||
|
|
||||||
|
List<AuthUserAccountEntity> selectByRoleCode(@Param("roleCode") String roleCode);
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import org.apache.ibatis.annotations.Param;
|
|||||||
@Mapper
|
@Mapper
|
||||||
public interface RoleUkeyBindingMapper extends BaseMapperX<RoleUkeyBindingEntity> {
|
public interface RoleUkeyBindingMapper extends BaseMapperX<RoleUkeyBindingEntity> {
|
||||||
|
|
||||||
RoleUkeyBindingEntity selectActiveByRoleCodeAndSlotNo(@Param("roleCode") String roleCode, @Param("slotNo") Integer slotNo);
|
RoleUkeyBindingEntity selectActiveByRoleCodeAndUid(@Param("roleCode") String roleCode, @Param("uid") Integer uid);
|
||||||
|
|
||||||
List<RoleUkeyBindingEntity> selectActiveByRoleCode(@Param("roleCode") String roleCode);
|
List<RoleUkeyBindingEntity> selectActiveByRoleCode(@Param("roleCode") String roleCode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.cisd.tms.modules.auth.repository;
|
||||||
|
|
||||||
|
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface AuthUserAccountRepository {
|
||||||
|
|
||||||
|
Optional<AuthUserAccountEntity> findByUsername(String username);
|
||||||
|
|
||||||
|
List<AuthUserAccountEntity> findByRoleCode(String roleCode);
|
||||||
|
|
||||||
|
void save(AuthUserAccountEntity entity);
|
||||||
|
|
||||||
|
void update(AuthUserAccountEntity entity);
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
public interface RoleUkeyBindingRepository {
|
public interface RoleUkeyBindingRepository {
|
||||||
|
|
||||||
Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndSlotNo(String roleCode, Integer slotNo);
|
Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndUid(String roleCode, Integer uid);
|
||||||
|
|
||||||
List<RoleUkeyBindingEntity> findActiveByRoleCode(String roleCode);
|
List<RoleUkeyBindingEntity> findActiveByRoleCode(String roleCode);
|
||||||
|
|
||||||
|
|||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package com.cisd.tms.modules.auth.repository.impl;
|
||||||
|
|
||||||
|
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||||
|
import com.cisd.tms.modules.auth.mapper.AuthUserAccountMapper;
|
||||||
|
import com.cisd.tms.modules.auth.repository.AuthUserAccountRepository;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public class AuthUserAccountRepositoryImpl implements AuthUserAccountRepository {
|
||||||
|
|
||||||
|
private final AuthUserAccountMapper authUserAccountMapper;
|
||||||
|
|
||||||
|
public AuthUserAccountRepositoryImpl(AuthUserAccountMapper authUserAccountMapper) {
|
||||||
|
this.authUserAccountMapper = authUserAccountMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<AuthUserAccountEntity> findByUsername(String username) {
|
||||||
|
return Optional.ofNullable(authUserAccountMapper.selectByUsername(username));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AuthUserAccountEntity> findByRoleCode(String roleCode) {
|
||||||
|
return authUserAccountMapper.selectByRoleCode(roleCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(AuthUserAccountEntity entity) {
|
||||||
|
authUserAccountMapper.insert(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(AuthUserAccountEntity entity) {
|
||||||
|
authUserAccountMapper.updateById(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -17,8 +17,8 @@ public class RoleUkeyBindingRepositoryImpl implements RoleUkeyBindingRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndSlotNo(String roleCode, Integer slotNo) {
|
public Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndUid(String roleCode, Integer uid) {
|
||||||
return Optional.ofNullable(roleUkeyBindingMapper.selectActiveByRoleCodeAndSlotNo(roleCode, slotNo));
|
return Optional.ofNullable(roleUkeyBindingMapper.selectActiveByRoleCodeAndUid(roleCode, uid));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+6
-2
@@ -1,5 +1,7 @@
|
|||||||
package com.cisd.tms.modules.auth.security;
|
package com.cisd.tms.modules.auth.security;
|
||||||
|
|
||||||
|
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||||
|
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@@ -7,7 +9,9 @@ import java.lang.annotation.Target;
|
|||||||
|
|
||||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface RequireAuthLevel {
|
public @interface RequireInternalAuth {
|
||||||
|
|
||||||
String value();
|
RoleCode role();
|
||||||
|
|
||||||
|
AuthLevel authLevel();
|
||||||
}
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.cisd.tms.modules.auth.security;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface RequireRole {
|
|
||||||
|
|
||||||
String value();
|
|
||||||
}
|
|
||||||
@@ -13,11 +13,9 @@ public interface AuthAdminService {
|
|||||||
String operatorRoleCode,
|
String operatorRoleCode,
|
||||||
String operatorAuthLevel,
|
String operatorAuthLevel,
|
||||||
String targetRoleCode,
|
String targetRoleCode,
|
||||||
Integer slotNo,
|
Integer uid,
|
||||||
String ukeySerial,
|
String ukeySerial,
|
||||||
String ukeyPubkey,
|
String ukeyPubkey,
|
||||||
String uid,
|
|
||||||
String rid,
|
|
||||||
String issuerSign
|
String issuerSign
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -25,11 +23,9 @@ public interface AuthAdminService {
|
|||||||
String operatorRoleCode,
|
String operatorRoleCode,
|
||||||
String operatorAuthLevel,
|
String operatorAuthLevel,
|
||||||
String targetRoleCode,
|
String targetRoleCode,
|
||||||
Integer slotNo,
|
Integer uid,
|
||||||
String ukeySerial,
|
String ukeySerial,
|
||||||
String ukeyPubkey,
|
String ukeyPubkey,
|
||||||
String uid,
|
|
||||||
String rid,
|
|
||||||
String issuerSign
|
String issuerSign
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ package com.cisd.tms.modules.auth.service.impl;
|
|||||||
import com.cisd.tms.common.enums.ErrorCode;
|
import com.cisd.tms.common.enums.ErrorCode;
|
||||||
import com.cisd.tms.common.exception.BizException;
|
import com.cisd.tms.common.exception.BizException;
|
||||||
import com.cisd.tms.common.util.TraceIdUtil;
|
import com.cisd.tms.common.util.TraceIdUtil;
|
||||||
|
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||||
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
||||||
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
||||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
|
||||||
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
||||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||||
|
import com.cisd.tms.modules.auth.repository.AuthUserAccountRepository;
|
||||||
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
||||||
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||||
import com.cisd.tms.modules.auth.service.AuthAdminService;
|
import com.cisd.tms.modules.auth.service.AuthAdminService;
|
||||||
@@ -35,6 +36,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
static final String DEFAULT_PASSWORD = "12345678";
|
static final String DEFAULT_PASSWORD = "12345678";
|
||||||
|
|
||||||
private final RoleAccountRepository roleAccountRepository;
|
private final RoleAccountRepository roleAccountRepository;
|
||||||
|
private final AuthUserAccountRepository authUserAccountRepository;
|
||||||
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
|
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
|
||||||
private final PasswordHasher passwordHasher;
|
private final PasswordHasher passwordHasher;
|
||||||
private final LmkService lmkService;
|
private final LmkService lmkService;
|
||||||
@@ -44,7 +46,6 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableRole(String operatorRoleCode, String operatorAuthLevel, String targetRoleCode) {
|
public void enableRole(String operatorRoleCode, String operatorAuthLevel, String targetRoleCode) {
|
||||||
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
|
|
||||||
RoleAccountEntity target = loadRole(targetRoleCode);
|
RoleAccountEntity target = loadRole(targetRoleCode);
|
||||||
target.setStatus(RoleAccountStatus.ACTIVE.name());
|
target.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
target.setNeedChangePassword(Boolean.TRUE);
|
target.setNeedChangePassword(Boolean.TRUE);
|
||||||
@@ -55,16 +56,31 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetPassword(String operatorRoleCode, String operatorAuthLevel, String targetRoleCode) {
|
public void resetPassword(String operatorRoleCode, String operatorAuthLevel, String targetRoleCode) {
|
||||||
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
|
|
||||||
RoleAccountEntity target = loadRole(targetRoleCode);
|
RoleAccountEntity target = loadRole(targetRoleCode);
|
||||||
String newSalt = passwordSaltGenerator.nextSalt();
|
String roleSalt = passwordSaltGenerator.nextSalt();
|
||||||
target.setPasswordSalt(newSalt);
|
target.setPasswordSalt(roleSalt);
|
||||||
target.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD, newSalt));
|
target.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD, roleSalt));
|
||||||
target.setStatus(RoleAccountStatus.ACTIVE.name());
|
target.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
target.setNeedChangePassword(Boolean.TRUE);
|
target.setNeedChangePassword(Boolean.TRUE);
|
||||||
target.setFailedCount(0);
|
target.setFailedCount(0);
|
||||||
target.setLockedUntil(null);
|
target.setLockedUntil(null);
|
||||||
roleAccountRepository.update(target);
|
roleAccountRepository.update(target);
|
||||||
|
|
||||||
|
List<AuthUserAccountEntity> accounts = authUserAccountRepository.findByRoleCode(targetRoleCode);
|
||||||
|
if (accounts.isEmpty()) {
|
||||||
|
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role user accounts not found");
|
||||||
|
}
|
||||||
|
for (AuthUserAccountEntity account : accounts) {
|
||||||
|
String newSalt = passwordSaltGenerator.nextSalt();
|
||||||
|
account.setPasswordSalt(newSalt);
|
||||||
|
account.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD, newSalt));
|
||||||
|
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
|
account.setFailedCount(0);
|
||||||
|
account.setLockedUntil(null);
|
||||||
|
account.setLastActiveAt(null);
|
||||||
|
account.setLastLoginAt(null);
|
||||||
|
authUserAccountRepository.update(account);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -72,33 +88,30 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
String operatorRoleCode,
|
String operatorRoleCode,
|
||||||
String operatorAuthLevel,
|
String operatorAuthLevel,
|
||||||
String targetRoleCode,
|
String targetRoleCode,
|
||||||
Integer slotNo,
|
Integer uid,
|
||||||
String ukeySerial,
|
String ukeySerial,
|
||||||
String ukeyPubkey,
|
String ukeyPubkey,
|
||||||
String uid,
|
|
||||||
String rid,
|
|
||||||
String issuerSign
|
String issuerSign
|
||||||
) {
|
) {
|
||||||
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
|
|
||||||
RoleCode targetRole = resolveRoleCode(targetRoleCode);
|
RoleCode targetRole = resolveRoleCode(targetRoleCode);
|
||||||
if (slotNo == null || slotNo < 1 || slotNo > targetRole.getRequiredUkeyCount()) {
|
if (uid == null || uid < 1 || uid > targetRole.getRequiredUkeyCount()) {
|
||||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "slotNo exceeds role ukey requirement");
|
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "uid exceeds role ukey requirement");
|
||||||
}
|
}
|
||||||
|
|
||||||
RoleUkeyBindingEntity binding = roleUkeyBindingRepository
|
RoleUkeyBindingEntity binding = roleUkeyBindingRepository
|
||||||
.findActiveByRoleCodeAndSlotNo(targetRoleCode, slotNo)
|
.findActiveByRoleCodeAndUid(targetRoleCode, uid)
|
||||||
.orElseGet(RoleUkeyBindingEntity::new);
|
.orElseGet(RoleUkeyBindingEntity::new);
|
||||||
if (binding.getId() == null) {
|
if (binding.getId() == null) {
|
||||||
binding.setId((long) Math.abs(Objects.hash(targetRoleCode, slotNo, ukeySerial, TraceIdUtil.newTraceId())));
|
binding.setId((long) Math.abs(Objects.hash(targetRoleCode, uid, ukeySerial, TraceIdUtil.newTraceId())));
|
||||||
binding.setRoleCode(targetRoleCode);
|
binding.setRoleCode(targetRoleCode);
|
||||||
binding.setSlotNo(slotNo);
|
binding.setUid(uid);
|
||||||
binding.setStatus("ACTIVE");
|
binding.setStatus("ACTIVE");
|
||||||
applyBinding(binding, ukeySerial, ukeyPubkey, uid, rid, issuerSign);
|
applyBinding(binding, ukeySerial, ukeyPubkey, issuerSign);
|
||||||
roleUkeyBindingRepository.save(binding);
|
roleUkeyBindingRepository.save(binding);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyBinding(binding, ukeySerial, ukeyPubkey, uid, rid, issuerSign);
|
applyBinding(binding, ukeySerial, ukeyPubkey, issuerSign);
|
||||||
roleUkeyBindingRepository.update(binding);
|
roleUkeyBindingRepository.update(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,17 +120,14 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
String operatorRoleCode,
|
String operatorRoleCode,
|
||||||
String operatorAuthLevel,
|
String operatorAuthLevel,
|
||||||
String targetRoleCode,
|
String targetRoleCode,
|
||||||
Integer slotNo,
|
Integer uid,
|
||||||
String ukeySerial,
|
String ukeySerial,
|
||||||
String ukeyPubkey,
|
String ukeyPubkey,
|
||||||
String uid,
|
|
||||||
String rid,
|
|
||||||
String issuerSign
|
String issuerSign
|
||||||
) {
|
) {
|
||||||
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
|
|
||||||
resolveRoleCode(targetRoleCode);
|
resolveRoleCode(targetRoleCode);
|
||||||
verifyIssuerSignature(targetRoleCode, ukeyPubkey, uid, rid, issuerSign);
|
verifyIssuerSignature(targetRoleCode, ukeyPubkey, uid, issuerSign);
|
||||||
bindUkey(operatorRoleCode, operatorAuthLevel, targetRoleCode, slotNo, ukeySerial, ukeyPubkey, uid, rid, issuerSign);
|
bindUkey(operatorRoleCode, operatorAuthLevel, targetRoleCode, uid, ukeySerial, ukeyPubkey, issuerSign);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -127,7 +137,6 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
String targetRoleCode,
|
String targetRoleCode,
|
||||||
UKeySignDTO request
|
UKeySignDTO request
|
||||||
) {
|
) {
|
||||||
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
|
|
||||||
RoleCode targetRole = resolveRoleCode(targetRoleCode);
|
RoleCode targetRole = resolveRoleCode(targetRoleCode);
|
||||||
String authKeyPair = lmkService.exportIkPublicKeyHex();
|
String authKeyPair = lmkService.exportIkPublicKeyHex();
|
||||||
|
|
||||||
@@ -135,12 +144,11 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
dto.setPubKey(request.getPubKey());
|
dto.setPubKey(request.getPubKey());
|
||||||
dto.setRole(targetRole.getCode());
|
dto.setRole(targetRole.getCode());
|
||||||
dto.setUid(request.getUid());
|
dto.setUid(request.getUid());
|
||||||
dto.setRid(request.getRid());
|
|
||||||
|
|
||||||
String signValue = lmkService.signIk(toIssuePayload(dto, authKeyPair));
|
String signValue = lmkService.signIk(toIssuePayload(dto, authKeyPair));
|
||||||
MasterKeyBackupPacket backupPacket = null;
|
MasterKeyBackupPacket backupPacket = null;
|
||||||
if (RoleCode.SUPER_ADMIN == targetRole) {
|
if (RoleCode.SUPER_ADMIN == targetRole) {
|
||||||
backupPacket = lmkService.buildBackupPacket(Integer.parseInt(request.getUid()));
|
backupPacket = lmkService.buildBackupPacket(request.getUid());
|
||||||
}
|
}
|
||||||
return UKeySignResult.builder()
|
return UKeySignResult.builder()
|
||||||
.sign(signValue)
|
.sign(signValue)
|
||||||
@@ -152,26 +160,16 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
RoleUkeyBindingEntity binding,
|
RoleUkeyBindingEntity binding,
|
||||||
String ukeySerial,
|
String ukeySerial,
|
||||||
String ukeyPubkey,
|
String ukeyPubkey,
|
||||||
String uid,
|
|
||||||
String rid,
|
|
||||||
String issuerSign
|
String issuerSign
|
||||||
) {
|
) {
|
||||||
binding.setUkeySerial(ukeySerial);
|
binding.setUkeySerial(ukeySerial);
|
||||||
binding.setUkeyPubkey(ukeyPubkey);
|
binding.setUkeyPubkey(ukeyPubkey);
|
||||||
binding.setUid(uid);
|
|
||||||
binding.setRid(rid);
|
|
||||||
binding.setIssuerSign(issuerSign);
|
binding.setIssuerSign(issuerSign);
|
||||||
binding.setStatus("ACTIVE");
|
binding.setStatus("ACTIVE");
|
||||||
binding.setBoundAt(now());
|
binding.setBoundAt(now());
|
||||||
binding.setUnboundAt(null);
|
binding.setUnboundAt(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requireKeyAdminFull(String operatorRoleCode, String operatorAuthLevel) {
|
|
||||||
if (!RoleCode.KEY_ADMIN.getCode().equals(operatorRoleCode) || !AuthLevel.FULL.name().equals(operatorAuthLevel)) {
|
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "operation requires KEY_ADMIN FULL session");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private RoleAccountEntity loadRole(String roleCode) {
|
private RoleAccountEntity loadRole(String roleCode) {
|
||||||
resolveRoleCode(roleCode);
|
resolveRoleCode(roleCode);
|
||||||
return roleAccountRepository.findByRoleCode(roleCode)
|
return roleAccountRepository.findByRoleCode(roleCode)
|
||||||
@@ -189,13 +187,12 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
return LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC);
|
return LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyIssuerSignature(String targetRoleCode, String ukeyPubkey, String uid, String rid, String issuerSign) {
|
private void verifyIssuerSignature(String targetRoleCode, String ukeyPubkey, Integer uid, String issuerSign) {
|
||||||
try {
|
try {
|
||||||
UKeySignDTO dto = new UKeySignDTO();
|
UKeySignDTO dto = new UKeySignDTO();
|
||||||
dto.setPubKey(ukeyPubkey);
|
dto.setPubKey(ukeyPubkey);
|
||||||
dto.setRole(resolveRoleCode(targetRoleCode).getCode());
|
dto.setRole(resolveRoleCode(targetRoleCode).getCode());
|
||||||
dto.setUid(uid);
|
dto.setUid(uid);
|
||||||
dto.setRid(rid);
|
|
||||||
String authKeyPair = lmkService.exportIkPublicKeyHex();
|
String authKeyPair = lmkService.exportIkPublicKeyHex();
|
||||||
lmkService.verifyIk(toIssuePayload(dto, authKeyPair), issuerSign);
|
lmkService.verifyIk(toIssuePayload(dto, authKeyPair), issuerSign);
|
||||||
} catch (BizException ex) {
|
} catch (BizException ex) {
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import com.cisd.tms.modules.auth.dto.CaptchaResponse;
|
|||||||
import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
|
import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
|
||||||
import com.cisd.tms.modules.auth.dto.LoginRequest;
|
import com.cisd.tms.modules.auth.dto.LoginRequest;
|
||||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||||
|
import com.cisd.tms.modules.auth.dto.PasswordLoginAccountRequest;
|
||||||
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginProof;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginProof;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
|
||||||
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
|
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
|
||||||
|
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||||
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
|
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
|
||||||
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
||||||
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
||||||
@@ -20,10 +22,18 @@ import com.cisd.tms.modules.auth.enums.AuthMethod;
|
|||||||
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
||||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||||
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
|
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
|
||||||
|
import com.cisd.tms.modules.auth.repository.AuthUserAccountRepository;
|
||||||
import com.cisd.tms.modules.auth.repository.AuthUserRepository;
|
import com.cisd.tms.modules.auth.repository.AuthUserRepository;
|
||||||
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
||||||
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||||
import com.cisd.tms.modules.auth.service.*;
|
import com.cisd.tms.modules.auth.service.AuthPolicyService;
|
||||||
|
import com.cisd.tms.modules.auth.service.AuthService;
|
||||||
|
import com.cisd.tms.modules.auth.service.CaptchaService;
|
||||||
|
import com.cisd.tms.modules.auth.service.CompatUkeyVerifier;
|
||||||
|
import com.cisd.tms.modules.auth.service.PasswordHasher;
|
||||||
|
import com.cisd.tms.modules.auth.service.PasswordSaltGenerator;
|
||||||
|
import com.cisd.tms.modules.auth.service.SessionTokenGenerator;
|
||||||
|
import com.cisd.tms.modules.auth.service.UkeyLoginRandomService;
|
||||||
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
||||||
import com.cisd.tms.modules.mk.dto.UKeySignEntity;
|
import com.cisd.tms.modules.mk.dto.UKeySignEntity;
|
||||||
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
|
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
|
||||||
@@ -34,6 +44,7 @@ import java.time.Clock;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -51,6 +62,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
|
|
||||||
private final RoleAccountRepository roleAccountRepository;
|
private final RoleAccountRepository roleAccountRepository;
|
||||||
private final AuthUserRepository authUserRepository;
|
private final AuthUserRepository authUserRepository;
|
||||||
|
private final AuthUserAccountRepository authUserAccountRepository;
|
||||||
private final AuthSessionRepository authSessionRepository;
|
private final AuthSessionRepository authSessionRepository;
|
||||||
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
|
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
|
||||||
private final PasswordHasher passwordHasher;
|
private final PasswordHasher passwordHasher;
|
||||||
@@ -70,14 +82,22 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account not found"));
|
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account not found"));
|
||||||
|
|
||||||
validateRoleStatus(roleAccount);
|
validateRoleStatus(roleAccount);
|
||||||
if (!passwordHasher.matches(request.getPassword(), roleAccount.getPasswordSalt(), roleAccount.getPasswordHash())) {
|
|
||||||
onPasswordFailed(roleAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
resetFailureState(roleAccount);
|
|
||||||
AuthMethod authMethod = resolveAuthMethod(request.getUkeySerials());
|
AuthMethod authMethod = resolveAuthMethod(request.getUkeySerials());
|
||||||
|
List<AuthUserAccountEntity> validatedAccounts = new ArrayList<>();
|
||||||
|
if (AuthMethod.PASSWORD == authMethod) {
|
||||||
|
validatedAccounts = validateRoleAccounts(roleAccount, request.getAccounts());
|
||||||
|
resetValidatedAccounts(validatedAccounts);
|
||||||
|
} else {
|
||||||
|
validateRolePassword(roleAccount, request.getRolePassword());
|
||||||
|
resetRoleFailureState(roleAccount);
|
||||||
|
}
|
||||||
AuthLevel authLevel = resolveAuthLevel(roleAccount, request.getUkeySerials());
|
AuthLevel authLevel = resolveAuthLevel(roleAccount, request.getUkeySerials());
|
||||||
AuthSessionEntity session = buildSession(roleAccount.getRoleCode(), authMethod, authLevel);
|
AuthSessionEntity session = buildSession(
|
||||||
|
roleAccount.getRoleCode(),
|
||||||
|
authMethod,
|
||||||
|
authLevel,
|
||||||
|
validatedAccounts.isEmpty() ? null : validatedAccounts.stream().map(AuthUserAccountEntity::getUsername).toList()
|
||||||
|
);
|
||||||
authSessionRepository.save(session);
|
authSessionRepository.save(session);
|
||||||
|
|
||||||
LoginResponse response = new LoginResponse();
|
LoginResponse response = new LoginResponse();
|
||||||
@@ -85,7 +105,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
response.setAuthLevel(authLevel.name());
|
response.setAuthLevel(authLevel.name());
|
||||||
response.setToken(session.getSessionToken());
|
response.setToken(session.getSessionToken());
|
||||||
response.setExpiresAt(OffsetDateTime.of(session.getExpiresAt(), ZoneOffset.UTC).toString());
|
response.setExpiresAt(OffsetDateTime.of(session.getExpiresAt(), ZoneOffset.UTC).toString());
|
||||||
response.setNeedChangePassword(Boolean.TRUE.equals(roleAccount.getNeedChangePassword()));
|
response.setNeedChangePassword(needsPasswordChange(roleAccount));
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +115,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
captchaService.verify(request.getCaptchaId(), request.getCaptchaCode());
|
captchaService.verify(request.getCaptchaId(), request.getCaptchaCode());
|
||||||
LoginRequest loginRequest = new LoginRequest();
|
LoginRequest loginRequest = new LoginRequest();
|
||||||
loginRequest.setRoleCode(request.getRoleCode());
|
loginRequest.setRoleCode(request.getRoleCode());
|
||||||
loginRequest.setPassword(request.getPassword());
|
loginRequest.setAccounts(request.getAccounts());
|
||||||
return login(loginRequest);
|
return login(loginRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,12 +134,12 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
RoleCode roleCode = RoleCode.valueOf(request.getRoleCode());
|
RoleCode roleCode = RoleCode.valueOf(request.getRoleCode());
|
||||||
List<RoleUkeyBindingEntity> activeBindings = roleUkeyBindingRepository.findActiveByRoleCode(roleCode.getCode());
|
List<RoleUkeyBindingEntity> activeBindings = roleUkeyBindingRepository.findActiveByRoleCode(roleCode.getCode());
|
||||||
validateUkeyCount(roleCode, activeBindings, request.getUkeyProofs());
|
validateUkeyCount(roleCode, activeBindings, request.getUkeyProofs());
|
||||||
Map<String, RoleUkeyBindingEntity> bindingsByPubKey = activeBindings.stream()
|
Map<Integer, RoleUkeyBindingEntity> bindingsByUid = activeBindings.stream()
|
||||||
.collect(Collectors.toMap(RoleUkeyBindingEntity::getUkeyPubkey, item -> item, (left, right) -> left, java.util.LinkedHashMap::new));
|
.collect(Collectors.toMap(RoleUkeyBindingEntity::getUid, item -> item, (left, right) -> left, java.util.LinkedHashMap::new));
|
||||||
Set<String> requestPubKeys = request.getUkeyProofs().stream()
|
Set<Integer> requestUids = request.getUkeyProofs().stream()
|
||||||
.map(UkeyLoginProof::getPubKey)
|
.map(UkeyLoginProof::getUid)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
if (requestPubKeys.size() != request.getUkeyProofs().size() || !bindingsByPubKey.keySet().containsAll(requestPubKeys)) {
|
if (requestUids.size() != request.getUkeyProofs().size() || !bindingsByUid.keySet().containsAll(requestUids)) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey auth info does not match bound role");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey auth info does not match bound role");
|
||||||
}
|
}
|
||||||
ukeyLoginRandomService.assertIssued(
|
ukeyLoginRandomService.assertIssued(
|
||||||
@@ -127,16 +147,19 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
request.getUkeyProofs().stream().map(UkeyLoginProof::getServerRandom).toList()
|
request.getUkeyProofs().stream().map(UkeyLoginProof::getServerRandom).toList()
|
||||||
);
|
);
|
||||||
String authKeyPair = lmkService.exportIkPublicKeyHex();
|
String authKeyPair = lmkService.exportIkPublicKeyHex();
|
||||||
List<String> matchedSerials = new java.util.ArrayList<>();
|
List<String> matchedSerials = new ArrayList<>();
|
||||||
for (UkeyLoginProof proof : request.getUkeyProofs()) {
|
for (UkeyLoginProof proof : request.getUkeyProofs()) {
|
||||||
RoleUkeyBindingEntity binding = bindingsByPubKey.get(proof.getPubKey());
|
RoleUkeyBindingEntity binding = bindingsByUid.get(proof.getUid());
|
||||||
|
if (binding == null || !binding.getUkeyPubkey().equals(proof.getPubKey())) {
|
||||||
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey auth info does not match bound role");
|
||||||
|
}
|
||||||
compatUkeyVerifier.verifyIssuedBinding(buildIssuePayload(request.getRoleCode(), proof, authKeyPair), proof.getIssueSignature());
|
compatUkeyVerifier.verifyIssuedBinding(buildIssuePayload(request.getRoleCode(), proof, authKeyPair), proof.getIssueSignature());
|
||||||
compatUkeyVerifier.verifyLoginSignature(proof.getPubKey(), proof.getLoginPayload(), proof.getLoginSignature());
|
compatUkeyVerifier.verifyLoginSignature(proof.getPubKey(), proof.getLoginPayload(), proof.getLoginSignature());
|
||||||
matchedSerials.add(binding.getUkeySerial());
|
matchedSerials.add(binding.getUkeySerial());
|
||||||
}
|
}
|
||||||
LoginRequest loginRequest = new LoginRequest();
|
LoginRequest loginRequest = new LoginRequest();
|
||||||
loginRequest.setRoleCode(request.getRoleCode());
|
loginRequest.setRoleCode(request.getRoleCode());
|
||||||
loginRequest.setPassword(request.getPassword());
|
loginRequest.setRolePassword(request.getRolePassword());
|
||||||
loginRequest.setUkeySerials(matchedSerials);
|
loginRequest.setUkeySerials(matchedSerials);
|
||||||
return login(loginRequest);
|
return login(loginRequest);
|
||||||
}
|
}
|
||||||
@@ -164,23 +187,24 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
if (session != null) {
|
if (session != null) {
|
||||||
response.setAuthLevel(session.getAuthLevel());
|
response.setAuthLevel(session.getAuthLevel());
|
||||||
}
|
}
|
||||||
response.setNeedChangePassword(Boolean.TRUE.equals(roleAccount.getNeedChangePassword()));
|
response.setNeedChangePassword(needsPasswordChange(roleAccount));
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthUserEntity entity = authUserRepository.findByUsername(normalized).orElse(null);
|
AuthUserEntity entity = authUserRepository.findByUsername(normalized).orElse(null);
|
||||||
|
|
||||||
CurrentUserResponse response = new CurrentUserResponse();
|
CurrentUserResponse response = new CurrentUserResponse();
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
response.setUsername(normalized);
|
response.setUsername(normalized);
|
||||||
response.setDisplayName("TMS Administrator");
|
response.setDisplayName("TMS Administrator");
|
||||||
response.setRole("ADMIN");
|
response.setRole("ADMIN");
|
||||||
|
response.setNeedChangePassword(Boolean.FALSE);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
response.setUsername(entity.getUsername());
|
response.setUsername(entity.getUsername());
|
||||||
response.setDisplayName(entity.getDisplayName());
|
response.setDisplayName(entity.getDisplayName());
|
||||||
response.setRole(entity.getRole());
|
response.setRole(entity.getRole());
|
||||||
|
response.setNeedChangePassword(Boolean.FALSE);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,34 +221,107 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
AuthSessionEntity session = requireActiveSession(sessionToken);
|
AuthSessionEntity session = requireActiveSession(sessionToken);
|
||||||
RoleAccountEntity roleAccount = roleAccountRepository.findByRoleCode(session.getRoleCode())
|
RoleAccountEntity roleAccount = roleAccountRepository.findByRoleCode(session.getRoleCode())
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account not found"));
|
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account not found"));
|
||||||
|
validateRoleStatus(roleAccount);
|
||||||
if (!passwordHasher.matches(currentPassword, roleAccount.getPasswordSalt(), roleAccount.getPasswordHash())) {
|
if (!passwordHasher.matches(currentPassword, roleAccount.getPasswordSalt(), roleAccount.getPasswordHash())) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "current password is incorrect");
|
onRolePasswordFailed(roleAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
String newSalt = passwordSaltGenerator.nextSalt();
|
String salt = passwordSaltGenerator.nextSalt();
|
||||||
roleAccount.setPasswordSalt(newSalt);
|
LocalDateTime current = now();
|
||||||
roleAccount.setPasswordHash(passwordHasher.hash(newPassword, newSalt));
|
roleAccount.setPasswordSalt(salt);
|
||||||
|
roleAccount.setPasswordHash(passwordHasher.hash(newPassword, salt));
|
||||||
roleAccount.setNeedChangePassword(Boolean.FALSE);
|
roleAccount.setNeedChangePassword(Boolean.FALSE);
|
||||||
roleAccount.setFailedCount(0);
|
roleAccount.setFailedCount(0);
|
||||||
roleAccount.setLockedUntil(null);
|
roleAccount.setLockedUntil(null);
|
||||||
roleAccount.setStatus(RoleAccountStatus.ACTIVE.name());
|
roleAccount.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
|
roleAccount.setLastActiveAt(current);
|
||||||
roleAccountRepository.update(roleAccount);
|
roleAccountRepository.update(roleAccount);
|
||||||
|
|
||||||
|
session.setLastActiveAt(current);
|
||||||
|
session.setExpiresAt(current.plusMinutes(IDLE_TIMEOUT_MINUTES));
|
||||||
|
authSessionRepository.update(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateRoleStatus(RoleAccountEntity roleAccount) {
|
private void validateRoleStatus(RoleAccountEntity roleAccount) {
|
||||||
if (RoleAccountStatus.UNENABLED.name().equals(roleAccount.getStatus())) {
|
if (RoleAccountStatus.UNENABLED.name().equals(roleAccount.getStatus())) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role is not enabled");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role is not enabled");
|
||||||
}
|
}
|
||||||
if (RoleAccountStatus.LOCKED.name().equals(roleAccount.getStatus()) && isStillLocked(roleAccount)) {
|
if (RoleAccountStatus.LOCKED.name().equals(roleAccount.getStatus())
|
||||||
|
&& (roleAccount.getLockedUntil() == null || roleAccount.getLockedUntil().isAfter(now()))) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account is locked");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account is locked");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isStillLocked(RoleAccountEntity roleAccount) {
|
private void validateRolePassword(RoleAccountEntity roleAccount, String rolePassword) {
|
||||||
return roleAccount.getLockedUntil() == null || roleAccount.getLockedUntil().isAfter(now());
|
if (rolePassword == null || rolePassword.isBlank()) {
|
||||||
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role password is required");
|
||||||
|
}
|
||||||
|
if (!passwordHasher.matches(rolePassword, roleAccount.getPasswordSalt(), roleAccount.getPasswordHash())) {
|
||||||
|
onRolePasswordFailed(roleAccount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPasswordFailed(RoleAccountEntity roleAccount) {
|
private List<AuthUserAccountEntity> validateRoleAccounts(
|
||||||
|
RoleAccountEntity roleAccount,
|
||||||
|
List<PasswordLoginAccountRequest> accounts
|
||||||
|
) {
|
||||||
|
int expectedCount = requiredPasswordAccountCount(roleAccount.getRoleCode());
|
||||||
|
if (accounts == null || accounts.size() != expectedCount) {
|
||||||
|
throw new BizException(
|
||||||
|
ErrorCode.UNAUTHORIZED.getCode(),
|
||||||
|
roleAccount.getRoleCode() + " requires exactly " + expectedCount + " account passwords"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> uniqueUsernames = new LinkedHashSet<>();
|
||||||
|
List<AuthUserAccountEntity> validatedAccounts = new ArrayList<>();
|
||||||
|
for (PasswordLoginAccountRequest account : accounts) {
|
||||||
|
if (!uniqueUsernames.add(account.getUsername())) {
|
||||||
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "duplicate account username submitted");
|
||||||
|
}
|
||||||
|
AuthUserAccountEntity userAccount = authUserAccountRepository.findByUsername(account.getUsername())
|
||||||
|
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account not found"));
|
||||||
|
validateUserAccountRole(roleAccount.getRoleCode(), userAccount);
|
||||||
|
validateUserAccountStatus(userAccount);
|
||||||
|
if (!passwordHasher.matches(account.getPassword(), userAccount.getPasswordSalt(), userAccount.getPasswordHash())) {
|
||||||
|
onPasswordFailed(userAccount);
|
||||||
|
}
|
||||||
|
validatedAccounts.add(userAccount);
|
||||||
|
}
|
||||||
|
return validatedAccounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateUserAccountRole(String expectedRoleCode, AuthUserAccountEntity userAccount) {
|
||||||
|
if (!expectedRoleCode.equals(userAccount.getRoleCode())) {
|
||||||
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account does not belong to target role");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateUserAccountStatus(AuthUserAccountEntity userAccount) {
|
||||||
|
if (RoleAccountStatus.UNENABLED.name().equals(userAccount.getStatus())) {
|
||||||
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account is not enabled");
|
||||||
|
}
|
||||||
|
if (RoleAccountStatus.LOCKED.name().equals(userAccount.getStatus())
|
||||||
|
&& (userAccount.getLockedUntil() == null || userAccount.getLockedUntil().isAfter(now()))) {
|
||||||
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account is locked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPasswordFailed(AuthUserAccountEntity userAccount) {
|
||||||
|
int failedCount = userAccount.getFailedCount() == null ? 0 : userAccount.getFailedCount();
|
||||||
|
failedCount++;
|
||||||
|
userAccount.setFailedCount(failedCount);
|
||||||
|
if (failedCount >= MAX_FAILED_ATTEMPTS) {
|
||||||
|
userAccount.setStatus(RoleAccountStatus.LOCKED.name());
|
||||||
|
userAccount.setLockedUntil(now().plusMinutes(IDLE_TIMEOUT_MINUTES));
|
||||||
|
authUserAccountRepository.update(userAccount);
|
||||||
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account is locked");
|
||||||
|
}
|
||||||
|
authUserAccountRepository.update(userAccount);
|
||||||
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "password is incorrect");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRolePasswordFailed(RoleAccountEntity roleAccount) {
|
||||||
int failedCount = roleAccount.getFailedCount() == null ? 0 : roleAccount.getFailedCount();
|
int failedCount = roleAccount.getFailedCount() == null ? 0 : roleAccount.getFailedCount();
|
||||||
failedCount++;
|
failedCount++;
|
||||||
roleAccount.setFailedCount(failedCount);
|
roleAccount.setFailedCount(failedCount);
|
||||||
@@ -238,7 +335,19 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "password is incorrect");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "password is incorrect");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetFailureState(RoleAccountEntity roleAccount) {
|
private void resetValidatedAccounts(List<AuthUserAccountEntity> validatedAccounts) {
|
||||||
|
LocalDateTime current = now();
|
||||||
|
for (AuthUserAccountEntity account : validatedAccounts) {
|
||||||
|
account.setFailedCount(0);
|
||||||
|
account.setLockedUntil(null);
|
||||||
|
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
|
account.setLastLoginAt(current);
|
||||||
|
account.setLastActiveAt(current);
|
||||||
|
authUserAccountRepository.update(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetRoleFailureState(RoleAccountEntity roleAccount) {
|
||||||
roleAccount.setFailedCount(0);
|
roleAccount.setFailedCount(0);
|
||||||
roleAccount.setLockedUntil(null);
|
roleAccount.setLockedUntil(null);
|
||||||
roleAccount.setStatus(RoleAccountStatus.ACTIVE.name());
|
roleAccount.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
@@ -247,6 +356,10 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
roleAccountRepository.update(roleAccount);
|
roleAccountRepository.update(roleAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int requiredPasswordAccountCount(String roleCode) {
|
||||||
|
return RoleCode.valueOf(roleCode).getRequiredUkeyCount();
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureMasterKeyReady() {
|
private void ensureMasterKeyReady() {
|
||||||
MasterKeyStatus.StatusDetail status = lmkService.getMasterKeyStatus();
|
MasterKeyStatus.StatusDetail status = lmkService.getMasterKeyStatus();
|
||||||
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
|
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
|
||||||
@@ -264,18 +377,19 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Set<String> uniqueSerials = new LinkedHashSet<>(ukeySerials);
|
Set<String> uniqueSerials = new LinkedHashSet<>(ukeySerials);
|
||||||
if (uniqueSerials.size() != roleAccount.getRequiredUkeyCount()) {
|
int requiredUkeyCount = authPolicyService.requiredUkeyCount(RoleCode.valueOf(roleAccount.getRoleCode()));
|
||||||
|
if (uniqueSerials.size() != requiredUkeyCount) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<RoleUkeyBindingEntity> bindings = roleUkeyBindingRepository.findActiveByRoleCode(roleAccount.getRoleCode());
|
List<RoleUkeyBindingEntity> bindings = roleUkeyBindingRepository.findActiveByRoleCode(roleAccount.getRoleCode());
|
||||||
if (bindings.size() != roleAccount.getRequiredUkeyCount()) {
|
if (bindings.size() != requiredUkeyCount) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> boundSerials = bindings.stream()
|
Set<String> boundSerials = bindings.stream()
|
||||||
.map(RoleUkeyBindingEntity::getUkeySerial)
|
.map(RoleUkeyBindingEntity::getUkeySerial)
|
||||||
.collect(java.util.stream.Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
if (!boundSerials.containsAll(uniqueSerials)) {
|
if (!boundSerials.containsAll(uniqueSerials)) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey verification failed");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey verification failed");
|
||||||
}
|
}
|
||||||
@@ -300,7 +414,6 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
dto.setPubKey(proof.getPubKey());
|
dto.setPubKey(proof.getPubKey());
|
||||||
dto.setRole(RoleCode.valueOf(roleCode).getCode());
|
dto.setRole(RoleCode.valueOf(roleCode).getCode());
|
||||||
dto.setUid(proof.getUid());
|
dto.setUid(proof.getUid());
|
||||||
dto.setRid(proof.getRid());
|
|
||||||
try {
|
try {
|
||||||
return objectMapper.writeValueAsString(UKeySignEntity.getInstance(dto, authKeyPair));
|
return objectMapper.writeValueAsString(UKeySignEntity.getInstance(dto, authKeyPair));
|
||||||
} catch (JsonProcessingException ex) {
|
} catch (JsonProcessingException ex) {
|
||||||
@@ -308,10 +421,16 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthSessionEntity buildSession(String roleCode, AuthMethod authMethod, AuthLevel authLevel) {
|
private AuthSessionEntity buildSession(
|
||||||
|
String roleCode,
|
||||||
|
AuthMethod authMethod,
|
||||||
|
AuthLevel authLevel,
|
||||||
|
List<String> authenticatedUsers
|
||||||
|
) {
|
||||||
LocalDateTime issuedAt = now();
|
LocalDateTime issuedAt = now();
|
||||||
AuthSessionEntity entity = new AuthSessionEntity();
|
AuthSessionEntity entity = new AuthSessionEntity();
|
||||||
entity.setRoleCode(roleCode);
|
entity.setRoleCode(roleCode);
|
||||||
|
entity.setAuthenticatedUsersJson(writeAuthenticatedUsers(authenticatedUsers));
|
||||||
entity.setAuthMethod(authMethod.name());
|
entity.setAuthMethod(authMethod.name());
|
||||||
entity.setAuthLevel(authLevel.name());
|
entity.setAuthLevel(authLevel.name());
|
||||||
entity.setSessionToken(sessionTokenGenerator.nextToken());
|
entity.setSessionToken(sessionTokenGenerator.nextToken());
|
||||||
@@ -321,10 +440,25 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String writeAuthenticatedUsers(List<String> authenticatedUsers) {
|
||||||
|
if (authenticatedUsers == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsString(authenticatedUsers);
|
||||||
|
} catch (JsonProcessingException ex) {
|
||||||
|
throw new IllegalStateException("serialize authenticated users failed", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private LocalDateTime now() {
|
private LocalDateTime now() {
|
||||||
return LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC);
|
return LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean needsPasswordChange(RoleAccountEntity roleAccount) {
|
||||||
|
return Boolean.TRUE.equals(roleAccount.getNeedChangePassword());
|
||||||
|
}
|
||||||
|
|
||||||
private AuthSessionEntity requireActiveSession(String sessionToken) {
|
private AuthSessionEntity requireActiveSession(String sessionToken) {
|
||||||
AuthSessionEntity session = authSessionRepository.findBySessionToken(sessionToken)
|
AuthSessionEntity session = authSessionRepository.findBySessionToken(sessionToken)
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "session not found"));
|
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "session not found"));
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.nio.file.Path;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -18,6 +19,7 @@ public class RunnerStallDetector {
|
|||||||
private final ResourceBackupProperties resourceBackupProperties;
|
private final ResourceBackupProperties resourceBackupProperties;
|
||||||
private final Supplier<LocalDateTime> nowSupplier;
|
private final Supplier<LocalDateTime> nowSupplier;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
public RunnerStallDetector(
|
public RunnerStallDetector(
|
||||||
ResourceRestoreTaskRepository resourceRestoreTaskRepository,
|
ResourceRestoreTaskRepository resourceRestoreTaskRepository,
|
||||||
ResourceRestoreStateFileStore resourceRestoreStateFileStore,
|
ResourceRestoreStateFileStore resourceRestoreStateFileStore,
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ public class MasterKeyRestoreDTO {
|
|||||||
this.iDevPriKeyLen = iDevPriKeyLen;
|
this.iDevPriKeyLen = iDevPriKeyLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String,String> getComponents() {
|
public Map<Integer,String> getComponents() {
|
||||||
Map<String,String> componentMap = new HashMap<>();
|
Map<Integer,String> componentMap = new HashMap<>();
|
||||||
for(UKeySignVerifyDTO uKeySignVerifyDTO : uKeySignVerifyDTOList){
|
for(UKeySignVerifyDTO uKeySignVerifyDTO : uKeySignVerifyDTOList){
|
||||||
if(null == uKeySignVerifyDTO.getKeyComponent()){
|
if(null == uKeySignVerifyDTO.getKeyComponent()){
|
||||||
throw new IllegalArgumentException("UKey无主密钥分量,UID:" + uKeySignVerifyDTO.getuKeySignDTO().getUid());
|
throw new IllegalArgumentException("UKey无主密钥分量,UID:" + uKeySignVerifyDTO.getuKeySignDTO().getUid());
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ public class UKey {
|
|||||||
*/
|
*/
|
||||||
private String uid;
|
private String uid;
|
||||||
|
|
||||||
/**
|
|
||||||
* 角色 ID
|
|
||||||
*/
|
|
||||||
private String rid;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备公钥 HEX(32 字节 x + 32 字节 y)
|
* 设备公钥 HEX(32 字节 x + 32 字节 y)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,13 +18,8 @@ public class UKeySignDTO {
|
|||||||
private String role;
|
private String role;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UKey 中的认证信息
|
* 角色内固定席位编号。
|
||||||
*/
|
*/
|
||||||
private String uid;
|
private Integer uid;
|
||||||
|
|
||||||
/**
|
|
||||||
* UKey 中的认证信息
|
|
||||||
*/
|
|
||||||
private String rid;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,7 @@ public class UKeySignEntity {
|
|||||||
/**
|
/**
|
||||||
* 登录时传输
|
* 登录时传输
|
||||||
*/
|
*/
|
||||||
private String uid;
|
private Integer uid;
|
||||||
/**
|
|
||||||
* 登录时传输
|
|
||||||
*/
|
|
||||||
private String rid;
|
|
||||||
|
|
||||||
public static UKeySignEntity getInstance(UKeySignDTO uKeySignDTO,String authKeyPair){
|
public static UKeySignEntity getInstance(UKeySignDTO uKeySignDTO,String authKeyPair){
|
||||||
return UKeySignEntity.builder()
|
return UKeySignEntity.builder()
|
||||||
@@ -37,7 +33,6 @@ public class UKeySignEntity {
|
|||||||
.authKeyPair(authKeyPair)
|
.authKeyPair(authKeyPair)
|
||||||
.role(uKeySignDTO.getRole())
|
.role(uKeySignDTO.getRole())
|
||||||
.uid(uKeySignDTO.getUid())
|
.uid(uKeySignDTO.getUid())
|
||||||
.rid(uKeySignDTO.getRid())
|
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,9 +260,7 @@ CREATE TABLE IF NOT EXISTS tms_role_account (
|
|||||||
CREATE TABLE IF NOT EXISTS tms_role_ukey_binding (
|
CREATE TABLE IF NOT EXISTS tms_role_ukey_binding (
|
||||||
id BIGINT PRIMARY KEY,
|
id BIGINT PRIMARY KEY,
|
||||||
role_code VARCHAR(64) NOT NULL,
|
role_code VARCHAR(64) NOT NULL,
|
||||||
slot_no INT NOT NULL,
|
uid INT NOT NULL,
|
||||||
uid VARCHAR(64) NULL,
|
|
||||||
rid VARCHAR(64) NULL,
|
|
||||||
ukey_serial VARCHAR(128) NOT NULL,
|
ukey_serial VARCHAR(128) NOT NULL,
|
||||||
ukey_pubkey TEXT NOT NULL,
|
ukey_pubkey TEXT NOT NULL,
|
||||||
issuer_sign TEXT NULL,
|
issuer_sign TEXT NULL,
|
||||||
@@ -271,14 +269,33 @@ CREATE TABLE IF NOT EXISTS tms_role_ukey_binding (
|
|||||||
unbound_at DATETIME(3) NULL,
|
unbound_at DATETIME(3) NULL,
|
||||||
create_time DATETIME(3) NOT NULL,
|
create_time DATETIME(3) NOT NULL,
|
||||||
update_time DATETIME(3) NOT NULL,
|
update_time DATETIME(3) NOT NULL,
|
||||||
UNIQUE KEY uk_tms_role_ukey_binding_role_slot (role_code, slot_no),
|
UNIQUE KEY uk_tms_role_ukey_binding_role_uid (role_code, uid),
|
||||||
KEY idx_tms_role_ukey_binding_role_status (role_code, status)
|
KEY idx_tms_role_ukey_binding_role_status (role_code, status)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tms_auth_user_account (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
username VARCHAR(64) NOT NULL,
|
||||||
|
display_name VARCHAR(128) NOT NULL,
|
||||||
|
role_code VARCHAR(64) NOT NULL,
|
||||||
|
password_hash VARCHAR(256) NOT NULL,
|
||||||
|
password_salt VARCHAR(128) NOT NULL,
|
||||||
|
status VARCHAR(32) NOT NULL,
|
||||||
|
failed_count INT NOT NULL DEFAULT 0,
|
||||||
|
locked_until DATETIME(3) NULL,
|
||||||
|
last_login_at DATETIME(3) NULL,
|
||||||
|
last_active_at DATETIME(3) NULL,
|
||||||
|
create_time DATETIME(3) NOT NULL,
|
||||||
|
update_time DATETIME(3) NOT NULL,
|
||||||
|
UNIQUE KEY uk_tms_auth_user_account_username (username),
|
||||||
|
KEY idx_tms_auth_user_account_role_code (role_code)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tms_auth_session (
|
CREATE TABLE IF NOT EXISTS tms_auth_session (
|
||||||
id BIGINT PRIMARY KEY,
|
id BIGINT PRIMARY KEY,
|
||||||
session_token VARCHAR(128) NOT NULL,
|
session_token VARCHAR(128) NOT NULL,
|
||||||
role_code VARCHAR(64) NOT NULL,
|
role_code VARCHAR(64) NOT NULL,
|
||||||
|
authenticated_users_json JSON NULL,
|
||||||
auth_method VARCHAR(32) NOT NULL,
|
auth_method VARCHAR(32) NOT NULL,
|
||||||
auth_level VARCHAR(32) NOT NULL,
|
auth_level VARCHAR(32) NOT NULL,
|
||||||
issued_at DATETIME(3) NOT NULL,
|
issued_at DATETIME(3) NOT NULL,
|
||||||
@@ -378,7 +395,7 @@ VALUES
|
|||||||
11001,
|
11001,
|
||||||
'SUPER_ADMIN',
|
'SUPER_ADMIN',
|
||||||
'超级管理员',
|
'超级管理员',
|
||||||
3,
|
2,
|
||||||
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
|
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
|
||||||
'init-super-admin-salt-20260325',
|
'init-super-admin-salt-20260325',
|
||||||
'UNENABLED',
|
'UNENABLED',
|
||||||
@@ -394,7 +411,7 @@ VALUES
|
|||||||
11002,
|
11002,
|
||||||
'KEY_ADMIN',
|
'KEY_ADMIN',
|
||||||
'密钥管理员',
|
'密钥管理员',
|
||||||
2,
|
1,
|
||||||
'/xqmDy3A/q9X5p7XWznIPSBabQ6bxTLAPgtHQ6Be33c=',
|
'/xqmDy3A/q9X5p7XWznIPSBabQ6bxTLAPgtHQ6Be33c=',
|
||||||
'init-key-admin-salt-20260325',
|
'init-key-admin-salt-20260325',
|
||||||
'ACTIVE',
|
'ACTIVE',
|
||||||
@@ -439,6 +456,98 @@ VALUES
|
|||||||
CURRENT_TIMESTAMP(3)
|
CURRENT_TIMESTAMP(3)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
INSERT IGNORE INTO tms_auth_user_account (
|
||||||
|
id,
|
||||||
|
username,
|
||||||
|
display_name,
|
||||||
|
role_code,
|
||||||
|
password_hash,
|
||||||
|
password_salt,
|
||||||
|
status,
|
||||||
|
failed_count,
|
||||||
|
locked_until,
|
||||||
|
last_login_at,
|
||||||
|
last_active_at,
|
||||||
|
create_time,
|
||||||
|
update_time
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
12001,
|
||||||
|
'super-admin-01',
|
||||||
|
'超级管理员账号一',
|
||||||
|
'SUPER_ADMIN',
|
||||||
|
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
|
||||||
|
'init-super-admin-salt-20260325',
|
||||||
|
'ACTIVE',
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
CURRENT_TIMESTAMP(3),
|
||||||
|
CURRENT_TIMESTAMP(3)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
12002,
|
||||||
|
'super-admin-02',
|
||||||
|
'超级管理员账号二',
|
||||||
|
'SUPER_ADMIN',
|
||||||
|
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
|
||||||
|
'init-super-admin-salt-20260325',
|
||||||
|
'ACTIVE',
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
CURRENT_TIMESTAMP(3),
|
||||||
|
CURRENT_TIMESTAMP(3)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
12003,
|
||||||
|
'key-admin-01',
|
||||||
|
'密钥管理员账号',
|
||||||
|
'KEY_ADMIN',
|
||||||
|
'/xqmDy3A/q9X5p7XWznIPSBabQ6bxTLAPgtHQ6Be33c=',
|
||||||
|
'init-key-admin-salt-20260325',
|
||||||
|
'ACTIVE',
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
CURRENT_TIMESTAMP(3),
|
||||||
|
CURRENT_TIMESTAMP(3)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
12004,
|
||||||
|
'audit-admin-01',
|
||||||
|
'审计管理员账号',
|
||||||
|
'AUDIT_ADMIN',
|
||||||
|
'j/J1LKyUOzVEJfbbHe2dwKBKNdabRk502olk/LWIwhg=',
|
||||||
|
'init-audit-admin-salt-20260325',
|
||||||
|
'ACTIVE',
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
CURRENT_TIMESTAMP(3),
|
||||||
|
CURRENT_TIMESTAMP(3)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
12005,
|
||||||
|
'ops-admin-01',
|
||||||
|
'运维管理员账号',
|
||||||
|
'OPS_ADMIN',
|
||||||
|
'2e9F4aCgsDb+AuD1LrBEqEg8yjy3ODo27UHtx06vj/A=',
|
||||||
|
'init-ops-admin-salt-20260325',
|
||||||
|
'ACTIVE',
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
CURRENT_TIMESTAMP(3),
|
||||||
|
CURRENT_TIMESTAMP(3)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
-- -----------------------------------------------------------------------------
|
-- -----------------------------------------------------------------------------
|
||||||
-- IP白名单表
|
-- IP白名单表
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<id property="id" column="id"/>
|
<id property="id" column="id"/>
|
||||||
<result property="sessionToken" column="session_token"/>
|
<result property="sessionToken" column="session_token"/>
|
||||||
<result property="roleCode" column="role_code"/>
|
<result property="roleCode" column="role_code"/>
|
||||||
|
<result property="authenticatedUsersJson" column="authenticated_users_json"/>
|
||||||
<result property="authMethod" column="auth_method"/>
|
<result property="authMethod" column="auth_method"/>
|
||||||
<result property="authLevel" column="auth_level"/>
|
<result property="authLevel" column="auth_level"/>
|
||||||
<result property="issuedAt" column="issued_at"/>
|
<result property="issuedAt" column="issued_at"/>
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
SELECT id,
|
SELECT id,
|
||||||
session_token,
|
session_token,
|
||||||
role_code,
|
role_code,
|
||||||
|
authenticated_users_json,
|
||||||
auth_method,
|
auth_method,
|
||||||
auth_level,
|
auth_level,
|
||||||
issued_at,
|
issued_at,
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.cisd.tms.modules.auth.mapper.AuthUserAccountMapper">
|
||||||
|
|
||||||
|
<resultMap id="AuthUserAccountResultMap" type="com.cisd.tms.modules.auth.entity.AuthUserAccountEntity">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="username" column="username"/>
|
||||||
|
<result property="displayName" column="display_name"/>
|
||||||
|
<result property="roleCode" column="role_code"/>
|
||||||
|
<result property="passwordHash" column="password_hash"/>
|
||||||
|
<result property="passwordSalt" column="password_salt"/>
|
||||||
|
<result property="status" column="status"/>
|
||||||
|
<result property="failedCount" column="failed_count"/>
|
||||||
|
<result property="lockedUntil" column="locked_until"/>
|
||||||
|
<result property="lastLoginAt" column="last_login_at"/>
|
||||||
|
<result property="lastActiveAt" column="last_active_at"/>
|
||||||
|
<result property="createTime" column="create_time"/>
|
||||||
|
<result property="updateTime" column="update_time"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="selectByUsername" parameterType="string" resultMap="AuthUserAccountResultMap">
|
||||||
|
SELECT id,
|
||||||
|
username,
|
||||||
|
display_name,
|
||||||
|
role_code,
|
||||||
|
password_hash,
|
||||||
|
password_salt,
|
||||||
|
status,
|
||||||
|
failed_count,
|
||||||
|
locked_until,
|
||||||
|
last_login_at,
|
||||||
|
last_active_at,
|
||||||
|
create_time,
|
||||||
|
update_time
|
||||||
|
FROM tms_auth_user_account
|
||||||
|
WHERE username = #{username}
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByRoleCode" parameterType="string" resultMap="AuthUserAccountResultMap">
|
||||||
|
SELECT id,
|
||||||
|
username,
|
||||||
|
display_name,
|
||||||
|
role_code,
|
||||||
|
password_hash,
|
||||||
|
password_salt,
|
||||||
|
status,
|
||||||
|
failed_count,
|
||||||
|
locked_until,
|
||||||
|
last_login_at,
|
||||||
|
last_active_at,
|
||||||
|
create_time,
|
||||||
|
update_time
|
||||||
|
FROM tms_auth_user_account
|
||||||
|
WHERE role_code = #{roleCode}
|
||||||
|
ORDER BY username ASC
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
@@ -7,9 +7,7 @@
|
|||||||
<resultMap id="RoleUkeyBindingResultMap" type="com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity">
|
<resultMap id="RoleUkeyBindingResultMap" type="com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity">
|
||||||
<id property="id" column="id"/>
|
<id property="id" column="id"/>
|
||||||
<result property="roleCode" column="role_code"/>
|
<result property="roleCode" column="role_code"/>
|
||||||
<result property="slotNo" column="slot_no"/>
|
|
||||||
<result property="uid" column="uid"/>
|
<result property="uid" column="uid"/>
|
||||||
<result property="rid" column="rid"/>
|
|
||||||
<result property="ukeySerial" column="ukey_serial"/>
|
<result property="ukeySerial" column="ukey_serial"/>
|
||||||
<result property="ukeyPubkey" column="ukey_pubkey"/>
|
<result property="ukeyPubkey" column="ukey_pubkey"/>
|
||||||
<result property="issuerSign" column="issuer_sign"/>
|
<result property="issuerSign" column="issuer_sign"/>
|
||||||
@@ -20,12 +18,10 @@
|
|||||||
<result property="updateTime" column="update_time"/>
|
<result property="updateTime" column="update_time"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="selectActiveByRoleCodeAndSlotNo" resultMap="RoleUkeyBindingResultMap">
|
<select id="selectActiveByRoleCodeAndUid" resultMap="RoleUkeyBindingResultMap">
|
||||||
SELECT id,
|
SELECT id,
|
||||||
role_code,
|
role_code,
|
||||||
slot_no,
|
|
||||||
uid,
|
uid,
|
||||||
rid,
|
|
||||||
ukey_serial,
|
ukey_serial,
|
||||||
ukey_pubkey,
|
ukey_pubkey,
|
||||||
issuer_sign,
|
issuer_sign,
|
||||||
@@ -36,7 +32,7 @@
|
|||||||
update_time
|
update_time
|
||||||
FROM tms_role_ukey_binding
|
FROM tms_role_ukey_binding
|
||||||
WHERE role_code = #{roleCode}
|
WHERE role_code = #{roleCode}
|
||||||
AND slot_no = #{slotNo}
|
AND uid = #{uid}
|
||||||
AND status = 'ACTIVE'
|
AND status = 'ACTIVE'
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
@@ -44,9 +40,7 @@
|
|||||||
<select id="selectActiveByRoleCode" parameterType="string" resultMap="RoleUkeyBindingResultMap">
|
<select id="selectActiveByRoleCode" parameterType="string" resultMap="RoleUkeyBindingResultMap">
|
||||||
SELECT id,
|
SELECT id,
|
||||||
role_code,
|
role_code,
|
||||||
slot_no,
|
|
||||||
uid,
|
uid,
|
||||||
rid,
|
|
||||||
ukey_serial,
|
ukey_serial,
|
||||||
ukey_pubkey,
|
ukey_pubkey,
|
||||||
issuer_sign,
|
issuer_sign,
|
||||||
@@ -58,6 +52,6 @@
|
|||||||
FROM tms_role_ukey_binding
|
FROM tms_role_ukey_binding
|
||||||
WHERE role_code = #{roleCode}
|
WHERE role_code = #{roleCode}
|
||||||
AND status = 'ACTIVE'
|
AND status = 'ACTIVE'
|
||||||
ORDER BY slot_no ASC
|
ORDER BY uid ASC
|
||||||
</select>
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ class TmsApplicationTests {
|
|||||||
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS tms_role_ukey_binding"));
|
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS tms_role_ukey_binding"));
|
||||||
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS tms_auth_session"));
|
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS tms_auth_session"));
|
||||||
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("auth_method VARCHAR(32) NOT NULL"));
|
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("auth_method VARCHAR(32) NOT NULL"));
|
||||||
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("uid VARCHAR(64) NULL"));
|
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("uid INT NOT NULL"));
|
||||||
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("rid VARCHAR(64) NULL"));
|
org.junit.jupiter.api.Assertions.assertFalse(sql.contains("rid VARCHAR(64) NULL"));
|
||||||
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS tms_auth_challenge"));
|
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS tms_auth_challenge"));
|
||||||
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS tms_auth_audit_log"));
|
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS tms_auth_audit_log"));
|
||||||
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("'KEY_ADMIN'"));
|
org.junit.jupiter.api.Assertions.assertTrue(sql.contains("'KEY_ADMIN'"));
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.cisd.tms.modules.auth.controller;
|
package com.cisd.tms.modules.auth.controller;
|
||||||
|
|
||||||
import com.cisd.tms.common.exception.GlobalExceptionHandler;
|
import com.cisd.tms.common.exception.GlobalExceptionHandler;
|
||||||
|
import com.cisd.tms.common.exception.BizException;
|
||||||
import com.cisd.tms.modules.auth.dto.CaptchaResponse;
|
import com.cisd.tms.modules.auth.dto.CaptchaResponse;
|
||||||
import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
|
import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
|
||||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||||
@@ -45,7 +46,12 @@ class AuthControllerTest {
|
|||||||
.content("""
|
.content("""
|
||||||
{
|
{
|
||||||
"roleCode": "AUDIT_ADMIN",
|
"roleCode": "AUDIT_ADMIN",
|
||||||
"password": "12345678",
|
"accounts": [
|
||||||
|
{
|
||||||
|
"username": "audit-admin-01",
|
||||||
|
"password": "12345678"
|
||||||
|
}
|
||||||
|
],
|
||||||
"captchaCode": "ABCD",
|
"captchaCode": "ABCD",
|
||||||
"captchaId": "captcha-001"
|
"captchaId": "captcha-001"
|
||||||
}
|
}
|
||||||
@@ -101,12 +107,11 @@ class AuthControllerTest {
|
|||||||
.content("""
|
.content("""
|
||||||
{
|
{
|
||||||
"roleCode": "KEY_ADMIN",
|
"roleCode": "KEY_ADMIN",
|
||||||
"password": "12345678",
|
"rolePassword": "12345678",
|
||||||
"ukeyProofs": [
|
"ukeyProofs": [
|
||||||
{
|
{
|
||||||
"pubKey": "PUB-1",
|
"pubKey": "PUB-1",
|
||||||
"uid": "4",
|
"uid": 1,
|
||||||
"rid": "4",
|
|
||||||
"serverRandom": "RB-1",
|
"serverRandom": "RB-1",
|
||||||
"issueSignature": "ISSUE-1",
|
"issueSignature": "ISSUE-1",
|
||||||
"loginPayload": "LOGIN-DATA-1",
|
"loginPayload": "LOGIN-DATA-1",
|
||||||
@@ -114,8 +119,7 @@ class AuthControllerTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubKey": "PUB-2",
|
"pubKey": "PUB-2",
|
||||||
"uid": "5",
|
"uid": 2,
|
||||||
"rid": "5",
|
|
||||||
"serverRandom": "RB-2",
|
"serverRandom": "RB-2",
|
||||||
"issueSignature": "ISSUE-2",
|
"issueSignature": "ISSUE-2",
|
||||||
"loginPayload": "LOGIN-DATA-2",
|
"loginPayload": "LOGIN-DATA-2",
|
||||||
@@ -158,7 +162,7 @@ class AuthControllerTest {
|
|||||||
response.setDisplayName("审计管理员");
|
response.setDisplayName("审计管理员");
|
||||||
response.setRole("AUDIT_ADMIN");
|
response.setRole("AUDIT_ADMIN");
|
||||||
response.setAuthLevel("LIMITED");
|
response.setAuthLevel("LIMITED");
|
||||||
response.setNeedChangePassword(false);
|
response.setNeedChangePassword(true);
|
||||||
Mockito.when(authService.me("AUDIT_ADMIN", "token-me-001")).thenReturn(response);
|
Mockito.when(authService.me("AUDIT_ADMIN", "token-me-001")).thenReturn(response);
|
||||||
|
|
||||||
MockMvc mockMvc = MockMvcBuilders
|
MockMvc mockMvc = MockMvcBuilders
|
||||||
@@ -171,7 +175,8 @@ class AuthControllerTest {
|
|||||||
.requestAttr(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "AUDIT_ADMIN"))
|
.requestAttr(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "AUDIT_ADMIN"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(containsString("\"role\":\"AUDIT_ADMIN\"")))
|
.andExpect(content().string(containsString("\"role\":\"AUDIT_ADMIN\"")))
|
||||||
.andExpect(content().string(containsString("\"authLevel\":\"LIMITED\"")));
|
.andExpect(content().string(containsString("\"authLevel\":\"LIMITED\"")))
|
||||||
|
.andExpect(content().string(containsString("\"needChangePassword\":true")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -252,18 +257,16 @@ class AuthControllerTest {
|
|||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content("""
|
.content("""
|
||||||
{
|
{
|
||||||
"slotNo": 1,
|
"uid": 1,
|
||||||
"ukeySerial": "UK-NEW",
|
"ukeySerial": "UK-NEW",
|
||||||
"pubKey": "PUB-NEW",
|
"pubKey": "PUB-NEW",
|
||||||
"uid": "1",
|
|
||||||
"rid": "RID-001",
|
|
||||||
"issuerSignature": "SIG-NEW"
|
"issuerSignature": "SIG-NEW"
|
||||||
}
|
}
|
||||||
"""))
|
"""))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(containsString("\"success\":true")));
|
.andExpect(content().string(containsString("\"success\":true")));
|
||||||
|
|
||||||
Mockito.verify(authAdminService).bindIssuedUkey("KEY_ADMIN", "FULL", "SUPER_ADMIN", 1, "UK-NEW", "PUB-NEW", "1", "RID-001", "SIG-NEW");
|
Mockito.verify(authAdminService).bindIssuedUkey("KEY_ADMIN", "FULL", "SUPER_ADMIN", 1, "UK-NEW", "PUB-NEW", "SIG-NEW");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -293,8 +296,7 @@ class AuthControllerTest {
|
|||||||
{
|
{
|
||||||
"pubKey": "PUB-NEW",
|
"pubKey": "PUB-NEW",
|
||||||
"role": "SUPER_ADMIN",
|
"role": "SUPER_ADMIN",
|
||||||
"uid": "1",
|
"uid": 1,
|
||||||
"rid": "RID-001",
|
|
||||||
"extra": "EXTRA-001"
|
"extra": "EXTRA-001"
|
||||||
}
|
}
|
||||||
"""))
|
"""))
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package com.cisd.tms.modules.auth.service;
|
package com.cisd.tms.modules.auth.service;
|
||||||
|
|
||||||
import com.cisd.tms.common.exception.BizException;
|
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||||
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
||||||
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
||||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||||
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
||||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||||
|
import com.cisd.tms.modules.auth.repository.AuthUserAccountRepository;
|
||||||
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
||||||
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||||
import com.cisd.tms.modules.auth.service.impl.AuthAdminServiceImpl;
|
import com.cisd.tms.modules.auth.service.impl.AuthAdminServiceImpl;
|
||||||
@@ -35,10 +36,11 @@ class AuthAdminServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void shouldEnableRoleWhenOperatorIsKeyAdminWithFullSession() {
|
void shouldEnableRoleWhenOperatorIsKeyAdminWithFullSession() {
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||||
roleAccounts.save(role(RoleCode.AUDIT_ADMIN, RoleAccountStatus.UNENABLED, "OLD-HASH", "OLD-SALT", true));
|
roleAccounts.save(role(RoleCode.AUDIT_ADMIN, RoleAccountStatus.UNENABLED));
|
||||||
|
|
||||||
AuthAdminService service = newAuthAdminService(
|
AuthAdminService service = newAuthAdminService(
|
||||||
roleAccounts,
|
roleAccounts,
|
||||||
|
new InMemoryAuthUserAccountRepository(),
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
new InMemoryRoleUkeyBindingRepository(),
|
||||||
FIXED_CLOCK,
|
FIXED_CLOCK,
|
||||||
new FixedSaltSupplier("salt-001")
|
new FixedSaltSupplier("salt-001")
|
||||||
@@ -48,61 +50,58 @@ class AuthAdminServiceTest {
|
|||||||
|
|
||||||
RoleAccountEntity updated = roleAccounts.findByRoleCode(RoleCode.AUDIT_ADMIN.getCode()).orElseThrow();
|
RoleAccountEntity updated = roleAccounts.findByRoleCode(RoleCode.AUDIT_ADMIN.getCode()).orElseThrow();
|
||||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), updated.getStatus());
|
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), updated.getStatus());
|
||||||
Assertions.assertTrue(updated.getNeedChangePassword());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldRejectEnableRoleWhenOperatorIsNotKeyAdminFull() {
|
void shouldResetPasswordForAllAccountsUnderRole() {
|
||||||
AuthAdminService service = newAuthAdminService(
|
|
||||||
new InMemoryRoleAccountRepository(),
|
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
|
||||||
FIXED_CLOCK,
|
|
||||||
new FixedSaltSupplier("salt-001")
|
|
||||||
);
|
|
||||||
|
|
||||||
BizException exception = Assertions.assertThrows(
|
|
||||||
BizException.class,
|
|
||||||
() -> service.enableRole(RoleCode.KEY_ADMIN.getCode(), AuthLevel.LIMITED.name(), RoleCode.AUDIT_ADMIN.getCode())
|
|
||||||
);
|
|
||||||
|
|
||||||
Assertions.assertEquals("operation requires KEY_ADMIN FULL session", exception.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldResetPasswordUnlockRoleAndRequirePasswordChange() {
|
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||||
RoleAccountEntity target = role(RoleCode.OPS_ADMIN, RoleAccountStatus.LOCKED, "OLD-HASH", "OLD-SALT", false);
|
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||||
target.setFailedCount(5);
|
roleAccounts.save(role(RoleCode.SUPER_ADMIN, RoleAccountStatus.ACTIVE, "OLD-ROLE-HASH", "OLD-ROLE-SALT"));
|
||||||
target.setLockedUntil(LocalDateTime.of(2026, 3, 23, 3, 30));
|
userAccounts.save(user("super-admin-01", RoleCode.SUPER_ADMIN, "OLD-HASH-1", "OLD-SALT-1"));
|
||||||
roleAccounts.save(target);
|
userAccounts.save(user("super-admin-02", RoleCode.SUPER_ADMIN, "OLD-HASH-2", "OLD-SALT-2"));
|
||||||
|
userAccounts.findByUsername("super-admin-01").orElseThrow().setFailedCount(3);
|
||||||
|
userAccounts.findByUsername("super-admin-01").orElseThrow().setLockedUntil(LocalDateTime.of(2026, 3, 23, 3, 30));
|
||||||
|
userAccounts.findByUsername("super-admin-02").orElseThrow().setFailedCount(5);
|
||||||
|
userAccounts.findByUsername("super-admin-02").orElseThrow().setLockedUntil(LocalDateTime.of(2026, 3, 23, 3, 40));
|
||||||
|
|
||||||
AuthAdminService service = newAuthAdminService(
|
AuthAdminService service = newAuthAdminService(
|
||||||
roleAccounts,
|
roleAccounts,
|
||||||
|
userAccounts,
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
new InMemoryRoleUkeyBindingRepository(),
|
||||||
FIXED_CLOCK,
|
FIXED_CLOCK,
|
||||||
new FixedSaltSupplier("salt-002")
|
new FixedSaltSupplier("salt-role-001", "salt-002", "salt-003")
|
||||||
);
|
);
|
||||||
|
|
||||||
service.resetPassword(RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.OPS_ADMIN.getCode());
|
service.resetPassword(RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.SUPER_ADMIN.getCode());
|
||||||
|
|
||||||
RoleAccountEntity updated = roleAccounts.findByRoleCode(RoleCode.OPS_ADMIN.getCode()).orElseThrow();
|
RoleAccountEntity role = roleAccounts.findByRoleCode(RoleCode.SUPER_ADMIN.getCode()).orElseThrow();
|
||||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), updated.getStatus());
|
AuthUserAccountEntity first = userAccounts.findByUsername("super-admin-01").orElseThrow();
|
||||||
Assertions.assertEquals(0, updated.getFailedCount());
|
AuthUserAccountEntity second = userAccounts.findByUsername("super-admin-02").orElseThrow();
|
||||||
Assertions.assertNull(updated.getLockedUntil());
|
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), role.getStatus());
|
||||||
Assertions.assertEquals("salt-002", updated.getPasswordSalt());
|
Assertions.assertEquals("salt-role-001", role.getPasswordSalt());
|
||||||
Assertions.assertEquals("HASH:12345678:salt-002", updated.getPasswordHash());
|
Assertions.assertEquals("HASH:12345678:salt-role-001", role.getPasswordHash());
|
||||||
Assertions.assertTrue(updated.getNeedChangePassword());
|
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), first.getStatus());
|
||||||
|
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), second.getStatus());
|
||||||
|
Assertions.assertEquals(0, first.getFailedCount());
|
||||||
|
Assertions.assertEquals(0, second.getFailedCount());
|
||||||
|
Assertions.assertNull(first.getLockedUntil());
|
||||||
|
Assertions.assertNull(second.getLockedUntil());
|
||||||
|
Assertions.assertEquals("salt-002", first.getPasswordSalt());
|
||||||
|
Assertions.assertEquals("HASH:12345678:salt-002", first.getPasswordHash());
|
||||||
|
Assertions.assertEquals("salt-003", second.getPasswordSalt());
|
||||||
|
Assertions.assertEquals("HASH:12345678:salt-003", second.getPasswordHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReplaceActiveBindingInSameSlot() {
|
void shouldReplaceActiveBindingInSameUidSeat() {
|
||||||
InMemoryRoleUkeyBindingRepository bindings = new InMemoryRoleUkeyBindingRepository();
|
InMemoryRoleUkeyBindingRepository bindings = new InMemoryRoleUkeyBindingRepository();
|
||||||
bindings.save(binding(RoleCode.SUPER_ADMIN, 1, "UK-OLD", "PUB-OLD", "SIG-OLD"));
|
bindings.save(binding(RoleCode.SUPER_ADMIN, 1, "UK-OLD", "PUB-OLD", "SIG-OLD"));
|
||||||
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
|
LmkService lmkService = Mockito.mock(LmkService.class);
|
||||||
org.mockito.Mockito.when(lmkService.exportIkPublicKeyHex()).thenReturn("IK-PUB-001");
|
Mockito.when(lmkService.exportIkPublicKeyHex()).thenReturn("IK-PUB-001");
|
||||||
|
|
||||||
AuthAdminService service = new AuthAdminServiceImpl(
|
AuthAdminService service = new AuthAdminServiceImpl(
|
||||||
new InMemoryRoleAccountRepository(),
|
new InMemoryRoleAccountRepository(),
|
||||||
|
new InMemoryAuthUserAccountRepository(),
|
||||||
bindings,
|
bindings,
|
||||||
new FakePasswordHasher(),
|
new FakePasswordHasher(),
|
||||||
lmkService,
|
lmkService,
|
||||||
@@ -118,53 +117,12 @@ class AuthAdminServiceTest {
|
|||||||
1,
|
1,
|
||||||
"UK-NEW",
|
"UK-NEW",
|
||||||
"PUB-NEW",
|
"PUB-NEW",
|
||||||
"1",
|
|
||||||
"RID-001",
|
|
||||||
"SIG-NEW"
|
"SIG-NEW"
|
||||||
);
|
);
|
||||||
|
|
||||||
RoleUkeyBindingEntity updated = bindings.findActiveByRoleCodeAndSlotNo(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow();
|
RoleUkeyBindingEntity updated = bindings.findActiveByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow();
|
||||||
Assertions.assertEquals("UK-NEW", updated.getUkeySerial());
|
Assertions.assertEquals("UK-NEW", updated.getUkeySerial());
|
||||||
Assertions.assertEquals("PUB-NEW", updated.getUkeyPubkey());
|
Assertions.assertEquals("PUB-NEW", updated.getUkeyPubkey());
|
||||||
Assertions.assertEquals("1", updated.getUid());
|
|
||||||
Assertions.assertEquals("RID-001", updated.getRid());
|
|
||||||
Assertions.assertEquals("SIG-NEW", updated.getIssuerSign());
|
|
||||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), updated.getBoundAt());
|
|
||||||
org.mockito.Mockito.verify(lmkService).verifyIk(
|
|
||||||
"{\"pubKey\":\"PUB-NEW\",\"authKeyPair\":\"IK-PUB-001\",\"role\":\"SUPER_ADMIN\",\"uid\":\"1\",\"rid\":\"RID-001\"}",
|
|
||||||
"SIG-NEW"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldRejectBindingWhenSlotExceedsRoleRequirement() {
|
|
||||||
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
|
|
||||||
AuthAdminService service = new AuthAdminServiceImpl(
|
|
||||||
new InMemoryRoleAccountRepository(),
|
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
|
||||||
new FakePasswordHasher(),
|
|
||||||
lmkService,
|
|
||||||
new com.fasterxml.jackson.databind.ObjectMapper(),
|
|
||||||
FIXED_CLOCK,
|
|
||||||
new FixedSaltSupplier("salt-001")
|
|
||||||
);
|
|
||||||
|
|
||||||
BizException exception = Assertions.assertThrows(
|
|
||||||
BizException.class,
|
|
||||||
() -> service.bindIssuedUkey(
|
|
||||||
RoleCode.KEY_ADMIN.getCode(),
|
|
||||||
AuthLevel.FULL.name(),
|
|
||||||
RoleCode.AUDIT_ADMIN.getCode(),
|
|
||||||
2,
|
|
||||||
"UK-001",
|
|
||||||
"PUB-001",
|
|
||||||
"1",
|
|
||||||
"RID-001",
|
|
||||||
"SIG-001"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
Assertions.assertEquals("slotNo exceeds role ukey requirement", exception.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -178,6 +136,7 @@ class AuthAdminServiceTest {
|
|||||||
|
|
||||||
AuthAdminService service = new AuthAdminServiceImpl(
|
AuthAdminService service = new AuthAdminServiceImpl(
|
||||||
new InMemoryRoleAccountRepository(),
|
new InMemoryRoleAccountRepository(),
|
||||||
|
new InMemoryAuthUserAccountRepository(),
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
new InMemoryRoleUkeyBindingRepository(),
|
||||||
new FakePasswordHasher(),
|
new FakePasswordHasher(),
|
||||||
lmkService,
|
lmkService,
|
||||||
@@ -187,80 +146,57 @@ class AuthAdminServiceTest {
|
|||||||
);
|
);
|
||||||
UKeySignDTO dto = new UKeySignDTO();
|
UKeySignDTO dto = new UKeySignDTO();
|
||||||
dto.setPubKey("PUB-001");
|
dto.setPubKey("PUB-001");
|
||||||
dto.setRole("ignored");
|
dto.setUid(1);
|
||||||
dto.setUid("1");
|
|
||||||
dto.setRid("RID-001");
|
|
||||||
|
|
||||||
UKeySignResult result = service.issueUkeyBindingSign(RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.SUPER_ADMIN.getCode(), dto);
|
UKeySignResult result = service.issueUkeyBindingSign(RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.SUPER_ADMIN.getCode(), dto);
|
||||||
|
|
||||||
Assertions.assertEquals("ISSUE-SIGN-001", result.getSign());
|
Assertions.assertEquals("ISSUE-SIGN-001", result.getSign());
|
||||||
Assertions.assertNotNull(result.getBackupPacket());
|
Assertions.assertNotNull(result.getBackupPacket());
|
||||||
Assertions.assertEquals(1, result.getBackupPacket().getPacketIndex());
|
Assertions.assertEquals(1, result.getBackupPacket().getPacketIndex());
|
||||||
Mockito.verify(lmkService).signIk(
|
|
||||||
"{\"pubKey\":\"PUB-001\",\"authKeyPair\":\"IK-PUB-001\",\"role\":\"SUPER_ADMIN\",\"uid\":\"1\",\"rid\":\"RID-001\"}"
|
|
||||||
);
|
|
||||||
Mockito.verify(lmkService).buildBackupPacket(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private static RoleAccountEntity role(RoleCode roleCode, RoleAccountStatus status) {
|
||||||
void shouldIssueBindingSignAndPreserveExtraForNonSuperAdmin() {
|
|
||||||
LmkService lmkService = Mockito.mock(LmkService.class);
|
|
||||||
Mockito.when(lmkService.exportIkPublicKeyHex()).thenReturn("IK-PUB-001");
|
|
||||||
Mockito.when(lmkService.signIk(org.mockito.ArgumentMatchers.anyString())).thenReturn("ISSUE-SIGN-002");
|
|
||||||
|
|
||||||
AuthAdminService service = new AuthAdminServiceImpl(
|
|
||||||
new InMemoryRoleAccountRepository(),
|
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
|
||||||
new FakePasswordHasher(),
|
|
||||||
lmkService,
|
|
||||||
new com.fasterxml.jackson.databind.ObjectMapper(),
|
|
||||||
FIXED_CLOCK,
|
|
||||||
new FixedSaltSupplier("salt-001")
|
|
||||||
);
|
|
||||||
UKeySignDTO dto = new UKeySignDTO();
|
|
||||||
dto.setPubKey("PUB-002");
|
|
||||||
dto.setRole("ignored");
|
|
||||||
dto.setUid("1");
|
|
||||||
dto.setRid("RID-002");
|
|
||||||
// dto.setExtra("EXTRA-KEEP");
|
|
||||||
|
|
||||||
UKeySignResult result = service.issueUkeyBindingSign(RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.AUDIT_ADMIN.getCode(), dto);
|
|
||||||
|
|
||||||
Assertions.assertEquals("ISSUE-SIGN-002", result.getSign());
|
|
||||||
Assertions.assertNull(result.getBackupPacket());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RoleAccountEntity role(
|
|
||||||
RoleCode roleCode,
|
|
||||||
RoleAccountStatus status,
|
|
||||||
String passwordHash,
|
|
||||||
String passwordSalt,
|
|
||||||
boolean needChangePassword
|
|
||||||
) {
|
|
||||||
RoleAccountEntity entity = new RoleAccountEntity();
|
RoleAccountEntity entity = new RoleAccountEntity();
|
||||||
entity.setId((long) roleCode.ordinal() + 100);
|
entity.setId((long) roleCode.ordinal() + 100);
|
||||||
entity.setRoleCode(roleCode.getCode());
|
entity.setRoleCode(roleCode.getCode());
|
||||||
entity.setDisplayName(roleCode.getDisplayName());
|
entity.setDisplayName(roleCode.getDisplayName());
|
||||||
entity.setRequiredUkeyCount(roleCode.getRequiredUkeyCount());
|
entity.setRequiredUkeyCount(roleCode.getRequiredUkeyCount());
|
||||||
|
entity.setStatus(status.name());
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RoleAccountEntity role(RoleCode roleCode, RoleAccountStatus status, String passwordHash, String passwordSalt) {
|
||||||
|
RoleAccountEntity entity = role(roleCode, status);
|
||||||
entity.setPasswordHash(passwordHash);
|
entity.setPasswordHash(passwordHash);
|
||||||
entity.setPasswordSalt(passwordSalt);
|
entity.setPasswordSalt(passwordSalt);
|
||||||
entity.setStatus(status.name());
|
entity.setFailedCount(0);
|
||||||
entity.setNeedChangePassword(needChangePassword);
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AuthUserAccountEntity user(String username, RoleCode roleCode, String passwordHash, String passwordSalt) {
|
||||||
|
AuthUserAccountEntity entity = new AuthUserAccountEntity();
|
||||||
|
entity.setId((long) (username.hashCode() & Integer.MAX_VALUE));
|
||||||
|
entity.setUsername(username);
|
||||||
|
entity.setDisplayName(username);
|
||||||
|
entity.setRoleCode(roleCode.getCode());
|
||||||
|
entity.setPasswordHash(passwordHash);
|
||||||
|
entity.setPasswordSalt(passwordSalt);
|
||||||
|
entity.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
entity.setFailedCount(0);
|
entity.setFailedCount(0);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RoleUkeyBindingEntity binding(
|
private static RoleUkeyBindingEntity binding(
|
||||||
RoleCode roleCode,
|
RoleCode roleCode,
|
||||||
int slotNo,
|
int uid,
|
||||||
String serial,
|
String serial,
|
||||||
String pubkey,
|
String pubkey,
|
||||||
String issuerSign
|
String issuerSign
|
||||||
) {
|
) {
|
||||||
RoleUkeyBindingEntity entity = new RoleUkeyBindingEntity();
|
RoleUkeyBindingEntity entity = new RoleUkeyBindingEntity();
|
||||||
entity.setId((long) slotNo);
|
entity.setId((long) uid);
|
||||||
entity.setRoleCode(roleCode.getCode());
|
entity.setRoleCode(roleCode.getCode());
|
||||||
entity.setSlotNo(slotNo);
|
entity.setUid(uid);
|
||||||
entity.setUkeySerial(serial);
|
entity.setUkeySerial(serial);
|
||||||
entity.setUkeyPubkey(pubkey);
|
entity.setUkeyPubkey(pubkey);
|
||||||
entity.setIssuerSign(issuerSign);
|
entity.setIssuerSign(issuerSign);
|
||||||
@@ -314,13 +250,40 @@ class AuthAdminServiceTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class InMemoryAuthUserAccountRepository implements AuthUserAccountRepository {
|
||||||
|
private final Map<String, AuthUserAccountEntity> store = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<AuthUserAccountEntity> findByUsername(String username) {
|
||||||
|
return Optional.ofNullable(store.get(username));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AuthUserAccountEntity> findByRoleCode(String roleCode) {
|
||||||
|
return store.values().stream()
|
||||||
|
.filter(entity -> roleCode.equals(entity.getRoleCode()))
|
||||||
|
.sorted(Comparator.comparing(AuthUserAccountEntity::getUsername))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(AuthUserAccountEntity entity) {
|
||||||
|
store.put(entity.getUsername(), entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(AuthUserAccountEntity entity) {
|
||||||
|
store.put(entity.getUsername(), entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class InMemoryRoleUkeyBindingRepository implements RoleUkeyBindingRepository {
|
private static class InMemoryRoleUkeyBindingRepository implements RoleUkeyBindingRepository {
|
||||||
private final Map<String, List<RoleUkeyBindingEntity>> store = new ConcurrentHashMap<>();
|
private final Map<String, List<RoleUkeyBindingEntity>> store = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndSlotNo(String roleCode, Integer slotNo) {
|
public Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndUid(String roleCode, Integer uid) {
|
||||||
return store.getOrDefault(roleCode, List.of()).stream()
|
return store.getOrDefault(roleCode, List.of()).stream()
|
||||||
.filter(entity -> slotNo.equals(entity.getSlotNo()))
|
.filter(entity -> uid.equals(entity.getUid()))
|
||||||
.filter(entity -> "ACTIVE".equals(entity.getStatus()))
|
.filter(entity -> "ACTIVE".equals(entity.getStatus()))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
@@ -329,7 +292,7 @@ class AuthAdminServiceTest {
|
|||||||
public List<RoleUkeyBindingEntity> findActiveByRoleCode(String roleCode) {
|
public List<RoleUkeyBindingEntity> findActiveByRoleCode(String roleCode) {
|
||||||
return store.getOrDefault(roleCode, List.of()).stream()
|
return store.getOrDefault(roleCode, List.of()).stream()
|
||||||
.filter(entity -> "ACTIVE".equals(entity.getStatus()))
|
.filter(entity -> "ACTIVE".equals(entity.getStatus()))
|
||||||
.sorted(Comparator.comparing(RoleUkeyBindingEntity::getSlotNo))
|
.sorted(Comparator.comparing(RoleUkeyBindingEntity::getUid))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,15 +311,17 @@ class AuthAdminServiceTest {
|
|||||||
|
|
||||||
private static AuthAdminService newAuthAdminService(
|
private static AuthAdminService newAuthAdminService(
|
||||||
InMemoryRoleAccountRepository roleAccounts,
|
InMemoryRoleAccountRepository roleAccounts,
|
||||||
|
InMemoryAuthUserAccountRepository userAccounts,
|
||||||
InMemoryRoleUkeyBindingRepository bindings,
|
InMemoryRoleUkeyBindingRepository bindings,
|
||||||
Clock clock,
|
Clock clock,
|
||||||
PasswordSaltGenerator saltGenerator
|
PasswordSaltGenerator saltGenerator
|
||||||
) {
|
) {
|
||||||
return new AuthAdminServiceImpl(
|
return new AuthAdminServiceImpl(
|
||||||
roleAccounts,
|
roleAccounts,
|
||||||
|
userAccounts,
|
||||||
bindings,
|
bindings,
|
||||||
new FakePasswordHasher(),
|
new FakePasswordHasher(),
|
||||||
org.mockito.Mockito.mock(LmkService.class),
|
Mockito.mock(LmkService.class),
|
||||||
new com.fasterxml.jackson.databind.ObjectMapper(),
|
new com.fasterxml.jackson.databind.ObjectMapper(),
|
||||||
clock,
|
clock,
|
||||||
saltGenerator
|
saltGenerator
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package com.cisd.tms.modules.auth.service;
|
|||||||
|
|
||||||
import com.cisd.tms.common.exception.BizException;
|
import com.cisd.tms.common.exception.BizException;
|
||||||
import com.cisd.tms.modules.auth.dto.CaptchaResponse;
|
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.LoginRequest;
|
||||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||||
|
import com.cisd.tms.modules.auth.dto.PasswordLoginAccountRequest;
|
||||||
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginProof;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginProof;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
|
||||||
@@ -11,20 +13,23 @@ import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
|||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
|
||||||
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
|
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
|
||||||
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
|
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
|
||||||
|
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
|
||||||
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
|
||||||
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
|
||||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||||
import com.cisd.tms.modules.auth.enums.AuthMethod;
|
import com.cisd.tms.modules.auth.enums.AuthMethod;
|
||||||
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
|
||||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
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.AuthSessionRepository;
|
||||||
|
import com.cisd.tms.modules.auth.repository.AuthUserAccountRepository;
|
||||||
|
import com.cisd.tms.modules.auth.repository.AuthUserRepository;
|
||||||
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
|
||||||
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||||
import com.cisd.tms.modules.auth.service.impl.AuthPolicyServiceImpl;
|
import com.cisd.tms.modules.auth.service.impl.AuthPolicyServiceImpl;
|
||||||
import com.cisd.tms.modules.auth.service.impl.AuthServiceImpl;
|
import com.cisd.tms.modules.auth.service.impl.AuthServiceImpl;
|
||||||
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
|
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
|
||||||
import com.cisd.tms.modules.mk.service.LmkService;
|
import com.cisd.tms.modules.mk.service.LmkService;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -35,7 +40,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -44,177 +48,134 @@ class AuthServiceTest {
|
|||||||
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-03-23T02:00:00Z"), ZoneOffset.UTC);
|
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-03-23T02:00:00Z"), ZoneOffset.UTC);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateLimitedSessionForPasswordOnlyLogin() {
|
void shouldCreateLimitedSessionForSingleAccountRoleLogin() {
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||||
|
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||||
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
||||||
InMemoryRoleUkeyBindingRepository ukeyBindings = new InMemoryRoleUkeyBindingRepository();
|
roleAccounts.save(activeRole(RoleCode.AUDIT_ADMIN, true));
|
||||||
roleAccounts.save(activeRole(RoleCode.AUDIT_ADMIN, "HASH:12345678:SALT-A", "SALT-A", 0, true));
|
userAccounts.save(activeUserAccount("audit-admin-01", RoleCode.AUDIT_ADMIN, "12345678", "SALT-A", 0, null));
|
||||||
|
|
||||||
AuthService service = newAuthService(
|
AuthService service = newAuthService(
|
||||||
roleAccounts,
|
roleAccounts,
|
||||||
|
userAccounts,
|
||||||
sessions,
|
sessions,
|
||||||
ukeyBindings,
|
new InMemoryRoleUkeyBindingRepository(),
|
||||||
FIXED_CLOCK,
|
FIXED_CLOCK,
|
||||||
() -> "token-limited-001"
|
() -> "token-limited-001"
|
||||||
);
|
);
|
||||||
|
|
||||||
LoginRequest request = new LoginRequest();
|
LoginRequest request = new LoginRequest();
|
||||||
request.setRoleCode(RoleCode.AUDIT_ADMIN.getCode());
|
request.setRoleCode(RoleCode.AUDIT_ADMIN.getCode());
|
||||||
request.setPassword("12345678");
|
request.setAccounts(List.of(account("audit-admin-01", "12345678")));
|
||||||
|
|
||||||
LoginResponse response = service.login(request);
|
LoginResponse response = service.login(request);
|
||||||
|
|
||||||
Assertions.assertEquals(RoleCode.AUDIT_ADMIN.getCode(), response.getRoleCode());
|
Assertions.assertEquals(RoleCode.AUDIT_ADMIN.getCode(), response.getRoleCode());
|
||||||
Assertions.assertEquals(AuthLevel.LIMITED.name(), response.getAuthLevel());
|
Assertions.assertEquals(AuthLevel.LIMITED.name(), response.getAuthLevel());
|
||||||
Assertions.assertEquals("token-limited-001", response.getToken());
|
Assertions.assertEquals("token-limited-001", response.getToken());
|
||||||
Assertions.assertEquals("2026-03-23T02:10Z", response.getExpiresAt());
|
|
||||||
Assertions.assertTrue(response.getNeedChangePassword());
|
Assertions.assertTrue(response.getNeedChangePassword());
|
||||||
Assertions.assertEquals(1, sessions.count());
|
|
||||||
AuthSessionEntity session = sessions.findBySessionToken("token-limited-001").orElseThrow();
|
AuthSessionEntity session = sessions.findBySessionToken("token-limited-001").orElseThrow();
|
||||||
Assertions.assertEquals(AuthLevel.LIMITED.name(), session.getAuthLevel());
|
|
||||||
Assertions.assertEquals(AuthMethod.PASSWORD.name(), session.getAuthMethod());
|
Assertions.assertEquals(AuthMethod.PASSWORD.name(), session.getAuthMethod());
|
||||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 0), session.getIssuedAt());
|
Assertions.assertEquals("[\"audit-admin-01\"]", session.getAuthenticatedUsersJson());
|
||||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 10), session.getExpiresAt());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateFullSessionWhenEnoughUkeysAreProvided() {
|
void shouldRequireTwoAccountsForSuperAdminPasswordLogin() {
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||||
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||||
InMemoryRoleUkeyBindingRepository ukeyBindings = new InMemoryRoleUkeyBindingRepository();
|
roleAccounts.save(activeRole(RoleCode.SUPER_ADMIN));
|
||||||
roleAccounts.save(activeRole(RoleCode.KEY_ADMIN, "HASH:12345678:SALT-K", "SALT-K", 0, false));
|
userAccounts.save(activeUserAccount("super-admin-01", RoleCode.SUPER_ADMIN, "11111111", "SALT-S1", 0, null));
|
||||||
ukeyBindings.save(activeBinding(RoleCode.KEY_ADMIN, 1, "UK-1"));
|
userAccounts.save(activeUserAccount("super-admin-02", RoleCode.SUPER_ADMIN, "22222222", "SALT-S2", 0, null));
|
||||||
|
|
||||||
AuthService service = newAuthService(
|
|
||||||
roleAccounts,
|
|
||||||
sessions,
|
|
||||||
ukeyBindings,
|
|
||||||
FIXED_CLOCK,
|
|
||||||
() -> "token-full-001"
|
|
||||||
);
|
|
||||||
|
|
||||||
LoginRequest request = new LoginRequest();
|
|
||||||
request.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
|
||||||
request.setPassword("12345678");
|
|
||||||
request.setUkeySerials(List.of("UK-1"));
|
|
||||||
|
|
||||||
LoginResponse response = service.login(request);
|
|
||||||
|
|
||||||
Assertions.assertEquals(AuthLevel.FULL.name(), response.getAuthLevel());
|
|
||||||
Assertions.assertEquals("token-full-001", response.getToken());
|
|
||||||
Assertions.assertFalse(response.getNeedChangePassword());
|
|
||||||
AuthSessionEntity session = sessions.findBySessionToken("token-full-001").orElseThrow();
|
|
||||||
Assertions.assertEquals(AuthMethod.UKEY.name(), session.getAuthMethod());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldRejectLoginWhenRoleIsUnenabled() {
|
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
|
||||||
roleAccounts.save(role(RoleCode.OPS_ADMIN, RoleAccountStatus.UNENABLED, "HASH:12345678:SALT-O", "SALT-O", 0, true, null));
|
|
||||||
|
|
||||||
AuthService service = newAuthService(
|
AuthService service = newAuthService(
|
||||||
roleAccounts,
|
roleAccounts,
|
||||||
|
userAccounts,
|
||||||
new InMemoryAuthSessionRepository(),
|
new InMemoryAuthSessionRepository(),
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
new InMemoryRoleUkeyBindingRepository(),
|
||||||
FIXED_CLOCK,
|
FIXED_CLOCK,
|
||||||
() -> "unused"
|
() -> "unused"
|
||||||
);
|
);
|
||||||
|
|
||||||
LoginRequest request = new LoginRequest();
|
|
||||||
request.setRoleCode(RoleCode.OPS_ADMIN.getCode());
|
|
||||||
request.setPassword("12345678");
|
|
||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.login(request));
|
|
||||||
|
|
||||||
Assertions.assertEquals("role is not enabled", exception.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldIncrementFailedCountAndLockRoleAfterFiveWrongPasswords() {
|
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
|
||||||
RoleAccountEntity role = activeRole(RoleCode.AUDIT_ADMIN, "HASH:12345678:SALT-A", "SALT-A", 4, false);
|
|
||||||
roleAccounts.save(role);
|
|
||||||
|
|
||||||
AuthService service = newAuthService(
|
|
||||||
roleAccounts,
|
|
||||||
new InMemoryAuthSessionRepository(),
|
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
|
||||||
FIXED_CLOCK,
|
|
||||||
() -> "unused"
|
|
||||||
);
|
|
||||||
|
|
||||||
LoginRequest request = new LoginRequest();
|
|
||||||
request.setRoleCode(RoleCode.AUDIT_ADMIN.getCode());
|
|
||||||
request.setPassword("wrong-password");
|
|
||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.login(request));
|
|
||||||
|
|
||||||
Assertions.assertEquals("role account is locked", exception.getMessage());
|
|
||||||
RoleAccountEntity updated = roleAccounts.findByRoleCode(RoleCode.AUDIT_ADMIN.getCode()).orElseThrow();
|
|
||||||
Assertions.assertEquals(RoleAccountStatus.LOCKED.name(), updated.getStatus());
|
|
||||||
Assertions.assertEquals(5, updated.getFailedCount());
|
|
||||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 10), updated.getLockedUntil());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldRejectFullLoginWhenUkeyCountDoesNotMeetRoleRequirement() {
|
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
|
||||||
InMemoryRoleUkeyBindingRepository ukeyBindings = new InMemoryRoleUkeyBindingRepository();
|
|
||||||
roleAccounts.save(activeRole(RoleCode.SUPER_ADMIN, "HASH:12345678:SALT-S", "SALT-S", 0, false));
|
|
||||||
ukeyBindings.save(activeBinding(RoleCode.SUPER_ADMIN, 1, "UK-1"));
|
|
||||||
ukeyBindings.save(activeBinding(RoleCode.SUPER_ADMIN, 2, "UK-2"));
|
|
||||||
|
|
||||||
AuthService service = newAuthService(
|
|
||||||
roleAccounts,
|
|
||||||
new InMemoryAuthSessionRepository(),
|
|
||||||
ukeyBindings,
|
|
||||||
FIXED_CLOCK,
|
|
||||||
() -> "unused"
|
|
||||||
);
|
|
||||||
|
|
||||||
LoginRequest request = new LoginRequest();
|
LoginRequest request = new LoginRequest();
|
||||||
request.setRoleCode(RoleCode.SUPER_ADMIN.getCode());
|
request.setRoleCode(RoleCode.SUPER_ADMIN.getCode());
|
||||||
request.setPassword("12345678");
|
request.setAccounts(List.of(account("super-admin-01", "11111111")));
|
||||||
request.setUkeySerials(List.of("UK-1"));
|
|
||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.login(request));
|
BizException exception = Assertions.assertThrows(BizException.class, () -> service.login(request));
|
||||||
|
|
||||||
Assertions.assertEquals("ukey count does not satisfy role requirement", exception.getMessage());
|
Assertions.assertEquals("SUPER_ADMIN requires exactly 2 account passwords", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnCurrentFixedRoleInfoForMe() {
|
void shouldLockOnlySpecificUserAccountAfterFiveWrongPasswords() {
|
||||||
|
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||||
|
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||||
|
roleAccounts.save(activeRole(RoleCode.SUPER_ADMIN));
|
||||||
|
userAccounts.save(activeUserAccount("super-admin-01", RoleCode.SUPER_ADMIN, "11111111", "SALT-S1", 4, null));
|
||||||
|
userAccounts.save(activeUserAccount("super-admin-02", RoleCode.SUPER_ADMIN, "22222222", "SALT-S2", 0, null));
|
||||||
|
|
||||||
|
AuthService service = newAuthService(
|
||||||
|
roleAccounts,
|
||||||
|
userAccounts,
|
||||||
|
new InMemoryAuthSessionRepository(),
|
||||||
|
new InMemoryRoleUkeyBindingRepository(),
|
||||||
|
FIXED_CLOCK,
|
||||||
|
() -> "unused"
|
||||||
|
);
|
||||||
|
|
||||||
|
LoginRequest request = new LoginRequest();
|
||||||
|
request.setRoleCode(RoleCode.SUPER_ADMIN.getCode());
|
||||||
|
request.setAccounts(List.of(
|
||||||
|
account("super-admin-01", "bad-password"),
|
||||||
|
account("super-admin-02", "22222222")
|
||||||
|
));
|
||||||
|
|
||||||
|
BizException exception = Assertions.assertThrows(BizException.class, () -> service.login(request));
|
||||||
|
|
||||||
|
Assertions.assertEquals("user account is locked", exception.getMessage());
|
||||||
|
AuthUserAccountEntity locked = userAccounts.findByUsername("super-admin-01").orElseThrow();
|
||||||
|
AuthUserAccountEntity untouched = userAccounts.findByUsername("super-admin-02").orElseThrow();
|
||||||
|
Assertions.assertEquals(RoleAccountStatus.LOCKED.name(), locked.getStatus());
|
||||||
|
Assertions.assertEquals(5, locked.getFailedCount());
|
||||||
|
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 10), locked.getLockedUntil());
|
||||||
|
Assertions.assertEquals(0, untouched.getFailedCount());
|
||||||
|
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), untouched.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldKeepMeEndpointRoleScoped() {
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||||
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
||||||
roleAccounts.save(activeRole(RoleCode.AUDIT_ADMIN, "HASH:12345678:SALT-A", "SALT-A", 0, false));
|
roleAccounts.save(activeRole(RoleCode.AUDIT_ADMIN, true));
|
||||||
sessions.save(session("token-me-001", RoleCode.AUDIT_ADMIN.getCode(), AuthLevel.LIMITED.name()));
|
sessions.save(session("token-me-001", RoleCode.AUDIT_ADMIN.getCode(), AuthLevel.LIMITED.name()));
|
||||||
|
|
||||||
AuthService service = newAuthService(
|
AuthService service = newAuthService(
|
||||||
roleAccounts,
|
roleAccounts,
|
||||||
|
new InMemoryAuthUserAccountRepository(),
|
||||||
sessions,
|
sessions,
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
new InMemoryRoleUkeyBindingRepository(),
|
||||||
FIXED_CLOCK,
|
FIXED_CLOCK,
|
||||||
() -> "unused"
|
() -> "unused"
|
||||||
);
|
);
|
||||||
|
|
||||||
var response = service.me(RoleCode.AUDIT_ADMIN.getCode(), "token-me-001");
|
CurrentUserResponse response = service.me(RoleCode.AUDIT_ADMIN.getCode(), "token-me-001");
|
||||||
|
|
||||||
Assertions.assertEquals(RoleCode.AUDIT_ADMIN.getCode(), response.getUsername());
|
Assertions.assertEquals(RoleCode.AUDIT_ADMIN.getCode(), response.getUsername());
|
||||||
Assertions.assertEquals(RoleCode.AUDIT_ADMIN.getDisplayName(), response.getDisplayName());
|
Assertions.assertEquals(RoleCode.AUDIT_ADMIN.getDisplayName(), response.getDisplayName());
|
||||||
Assertions.assertEquals(RoleCode.AUDIT_ADMIN.getCode(), response.getRole());
|
Assertions.assertEquals(RoleCode.AUDIT_ADMIN.getCode(), response.getRole());
|
||||||
Assertions.assertEquals(AuthLevel.LIMITED.name(), response.getAuthLevel());
|
Assertions.assertEquals(AuthLevel.LIMITED.name(), response.getAuthLevel());
|
||||||
Assertions.assertFalse(response.getNeedChangePassword());
|
Assertions.assertTrue(response.getNeedChangePassword());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldChangePasswordAndClearNeedChangePassword() {
|
void shouldChangeCurrentRolePasswordForActiveSession() {
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||||
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
||||||
roleAccounts.save(activeRole(RoleCode.KEY_ADMIN, "HASH:12345678:SALT-K", "SALT-K", 0, true));
|
roleAccounts.save(activeRole(RoleCode.KEY_ADMIN, "12345678", "ROLE-SALT-K", 0, null, true));
|
||||||
sessions.save(session("token-change-001", RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name()));
|
sessions.save(session("token-change-001", RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name()));
|
||||||
|
|
||||||
AuthService service = newAuthService(
|
AuthService service = newAuthService(
|
||||||
roleAccounts,
|
roleAccounts,
|
||||||
|
new InMemoryAuthUserAccountRepository(),
|
||||||
sessions,
|
sessions,
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
new InMemoryRoleUkeyBindingRepository(),
|
||||||
FIXED_CLOCK,
|
FIXED_CLOCK,
|
||||||
@@ -223,106 +184,21 @@ class AuthServiceTest {
|
|||||||
|
|
||||||
service.changePassword("token-change-001", "12345678", "87654321");
|
service.changePassword("token-change-001", "12345678", "87654321");
|
||||||
|
|
||||||
RoleAccountEntity updated = roleAccounts.findByRoleCode(RoleCode.KEY_ADMIN.getCode()).orElseThrow();
|
RoleAccountEntity changed = roleAccounts.findByRoleCode(RoleCode.KEY_ADMIN.getCode()).orElseThrow();
|
||||||
Assertions.assertFalse(updated.getNeedChangePassword());
|
Assertions.assertEquals("salt-test", changed.getPasswordSalt());
|
||||||
Assertions.assertNotEquals("SALT-K", updated.getPasswordSalt());
|
Assertions.assertEquals("HASH:87654321:salt-test", changed.getPasswordHash());
|
||||||
Assertions.assertEquals("HASH:87654321:" + updated.getPasswordSalt(), updated.getPasswordHash());
|
Assertions.assertEquals(0, changed.getFailedCount());
|
||||||
}
|
Assertions.assertNull(changed.getLockedUntil());
|
||||||
|
Assertions.assertFalse(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||||
@Test
|
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 0), changed.getLastActiveAt());
|
||||||
void shouldLogoutSession() {
|
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
|
||||||
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
|
||||||
roleAccounts.save(activeRole(RoleCode.OPS_ADMIN, "HASH:12345678:SALT-O", "SALT-O", 0, false));
|
|
||||||
sessions.save(session("token-logout-001", RoleCode.OPS_ADMIN.getCode(), AuthLevel.LIMITED.name()));
|
|
||||||
|
|
||||||
AuthService service = newAuthService(
|
|
||||||
roleAccounts,
|
|
||||||
sessions,
|
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
|
||||||
FIXED_CLOCK,
|
|
||||||
() -> "unused"
|
|
||||||
);
|
|
||||||
|
|
||||||
service.logout("token-logout-001");
|
|
||||||
|
|
||||||
AuthSessionEntity session = sessions.findBySessionToken("token-logout-001").orElseThrow();
|
|
||||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 0), session.getLogoutAt());
|
|
||||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 0), session.getExpiresAt());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldIssueRoleScopedRandomsForUkeyLogin() {
|
|
||||||
UkeyLoginRandomService randomService = org.mockito.Mockito.mock(UkeyLoginRandomService.class);
|
|
||||||
org.mockito.Mockito.when(randomService.issue(RoleCode.SUPER_ADMIN.getCode(), 2)).thenReturn(List.of("RB-1", "RB-2"));
|
|
||||||
|
|
||||||
AuthService service = new AuthServiceImpl(
|
|
||||||
new InMemoryRoleAccountRepository(),
|
|
||||||
new InMemoryLegacyAuthUserRepository(),
|
|
||||||
new InMemoryAuthSessionRepository(),
|
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
|
||||||
new FakePasswordHasher(),
|
|
||||||
org.mockito.Mockito.mock(LmkService.class),
|
|
||||||
randomService,
|
|
||||||
org.mockito.Mockito.mock(CompatUkeyVerifier.class),
|
|
||||||
new InMemoryCaptchaService(),
|
|
||||||
new ObjectMapper(),
|
|
||||||
FIXED_CLOCK,
|
|
||||||
() -> "unused",
|
|
||||||
() -> "salt-unused",
|
|
||||||
new AuthPolicyServiceImpl()
|
|
||||||
);
|
|
||||||
|
|
||||||
UkeyLoginRandomRequest request = new UkeyLoginRandomRequest();
|
|
||||||
request.setRoleCode(RoleCode.SUPER_ADMIN.getCode());
|
|
||||||
|
|
||||||
UkeyLoginRandomResponse response = service.issueUkeyLoginRandoms(request);
|
|
||||||
|
|
||||||
Assertions.assertEquals(RoleCode.SUPER_ADMIN.getCode(), response.getRoleCode());
|
|
||||||
Assertions.assertEquals(List.of("RB-1", "RB-2"), response.getRandoms());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldRejectPasswordLoginWhenMasterKeyIsNotReady() {
|
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
|
||||||
roleAccounts.save(activeRole(RoleCode.AUDIT_ADMIN, "HASH:12345678:SALT-A", "SALT-A", 0, false));
|
|
||||||
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
|
|
||||||
org.mockito.Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.ABNORMAL.getDetail());
|
|
||||||
InMemoryCaptchaService captchaService = new InMemoryCaptchaService();
|
|
||||||
String captchaId = captchaService.issue("ABCD").getCaptchaId();
|
|
||||||
|
|
||||||
AuthService service = new AuthServiceImpl(
|
|
||||||
roleAccounts,
|
|
||||||
new InMemoryLegacyAuthUserRepository(),
|
|
||||||
new InMemoryAuthSessionRepository(),
|
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
|
||||||
new FakePasswordHasher(),
|
|
||||||
lmkService,
|
|
||||||
org.mockito.Mockito.mock(UkeyLoginRandomService.class),
|
|
||||||
org.mockito.Mockito.mock(CompatUkeyVerifier.class),
|
|
||||||
captchaService,
|
|
||||||
new ObjectMapper(),
|
|
||||||
FIXED_CLOCK,
|
|
||||||
() -> "unused",
|
|
||||||
() -> "salt-unused",
|
|
||||||
new AuthPolicyServiceImpl()
|
|
||||||
);
|
|
||||||
|
|
||||||
PasswordLoginRequest request = new PasswordLoginRequest();
|
|
||||||
request.setRoleCode(RoleCode.AUDIT_ADMIN.getCode());
|
|
||||||
request.setPassword("12345678");
|
|
||||||
request.setCaptchaId(captchaId);
|
|
||||||
request.setCaptchaCode("ABCD");
|
|
||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.passwordLogin(request));
|
|
||||||
|
|
||||||
Assertions.assertEquals("master key is not ready", exception.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldIssueCaptchaAndAcceptPasswordLoginWithValidCaptcha() {
|
void shouldIssueCaptchaAndAcceptPasswordLoginWithValidCaptcha() {
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||||
roleAccounts.save(activeRole(RoleCode.OPS_ADMIN, "HASH:12345678:SALT-O", "SALT-O", 0, false));
|
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||||
|
roleAccounts.save(activeRole(RoleCode.OPS_ADMIN));
|
||||||
|
userAccounts.save(activeUserAccount("ops-admin-01", RoleCode.OPS_ADMIN, "12345678", "SALT-O", 0, null));
|
||||||
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
|
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
|
||||||
org.mockito.Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.NORMAL.getDetail("MAC-001"));
|
org.mockito.Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.NORMAL.getDetail("MAC-001"));
|
||||||
InMemoryCaptchaService captchaService = new InMemoryCaptchaService();
|
InMemoryCaptchaService captchaService = new InMemoryCaptchaService();
|
||||||
@@ -330,6 +206,7 @@ class AuthServiceTest {
|
|||||||
AuthService service = new AuthServiceImpl(
|
AuthService service = new AuthServiceImpl(
|
||||||
roleAccounts,
|
roleAccounts,
|
||||||
new InMemoryLegacyAuthUserRepository(),
|
new InMemoryLegacyAuthUserRepository(),
|
||||||
|
userAccounts,
|
||||||
new InMemoryAuthSessionRepository(),
|
new InMemoryAuthSessionRepository(),
|
||||||
new InMemoryRoleUkeyBindingRepository(),
|
new InMemoryRoleUkeyBindingRepository(),
|
||||||
new FakePasswordHasher(),
|
new FakePasswordHasher(),
|
||||||
@@ -348,9 +225,9 @@ class AuthServiceTest {
|
|||||||
|
|
||||||
PasswordLoginRequest request = new PasswordLoginRequest();
|
PasswordLoginRequest request = new PasswordLoginRequest();
|
||||||
request.setRoleCode(RoleCode.OPS_ADMIN.getCode());
|
request.setRoleCode(RoleCode.OPS_ADMIN.getCode());
|
||||||
request.setPassword("12345678");
|
|
||||||
request.setCaptchaId(captcha.getCaptchaId());
|
request.setCaptchaId(captcha.getCaptchaId());
|
||||||
request.setCaptchaCode("ABCD");
|
request.setCaptchaCode("ABCD");
|
||||||
|
request.setAccounts(List.of(account("ops-admin-01", "12345678")));
|
||||||
|
|
||||||
LoginResponse response = service.passwordLogin(request);
|
LoginResponse response = service.passwordLogin(request);
|
||||||
|
|
||||||
@@ -359,12 +236,14 @@ class AuthServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldVerifyIssuedUkeysRandomsAndLoginSignsBeforeCreatingFullSession() {
|
void shouldVerifyUkeyProofsBeforeCreatingFullSession() {
|
||||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||||
|
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||||
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
||||||
InMemoryRoleUkeyBindingRepository ukeyBindings = new InMemoryRoleUkeyBindingRepository();
|
InMemoryRoleUkeyBindingRepository ukeyBindings = new InMemoryRoleUkeyBindingRepository();
|
||||||
roleAccounts.save(activeRole(RoleCode.KEY_ADMIN, "HASH:12345678:SALT-K", "SALT-K", 0, false));
|
roleAccounts.save(activeRole(RoleCode.KEY_ADMIN, "12345678", "ROLE-SALT-K"));
|
||||||
ukeyBindings.save(activeBinding(RoleCode.KEY_ADMIN, 1, "UK-1", "PUB-1", "4", "4"));
|
userAccounts.save(activeUserAccount("key-admin-01", RoleCode.KEY_ADMIN, "12345678", "SALT-K", 0, null));
|
||||||
|
ukeyBindings.save(activeBinding(RoleCode.KEY_ADMIN, 1, "UK-1", "PUB-1"));
|
||||||
|
|
||||||
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
|
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
|
||||||
UkeyLoginRandomService randomService = org.mockito.Mockito.mock(UkeyLoginRandomService.class);
|
UkeyLoginRandomService randomService = org.mockito.Mockito.mock(UkeyLoginRandomService.class);
|
||||||
@@ -376,6 +255,7 @@ class AuthServiceTest {
|
|||||||
AuthService service = new AuthServiceImpl(
|
AuthService service = new AuthServiceImpl(
|
||||||
roleAccounts,
|
roleAccounts,
|
||||||
new InMemoryLegacyAuthUserRepository(),
|
new InMemoryLegacyAuthUserRepository(),
|
||||||
|
userAccounts,
|
||||||
sessions,
|
sessions,
|
||||||
ukeyBindings,
|
ukeyBindings,
|
||||||
new FakePasswordHasher(),
|
new FakePasswordHasher(),
|
||||||
@@ -392,13 +272,14 @@ class AuthServiceTest {
|
|||||||
|
|
||||||
UkeyLoginRandomRequest randomRequest = new UkeyLoginRandomRequest();
|
UkeyLoginRandomRequest randomRequest = new UkeyLoginRandomRequest();
|
||||||
randomRequest.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
randomRequest.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
||||||
service.issueUkeyLoginRandoms(randomRequest);
|
UkeyLoginRandomResponse ignored = service.issueUkeyLoginRandoms(randomRequest);
|
||||||
|
Assertions.assertNotNull(ignored);
|
||||||
|
|
||||||
UkeyLoginRequest request = new UkeyLoginRequest();
|
UkeyLoginRequest request = new UkeyLoginRequest();
|
||||||
request.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
request.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
||||||
request.setPassword("12345678");
|
request.setRolePassword("12345678");
|
||||||
request.setUkeyProofs(List.of(
|
request.setUkeyProofs(List.of(
|
||||||
proof("PUB-1", "4", "4", "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1")
|
proof("PUB-1", 1, "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1")
|
||||||
));
|
));
|
||||||
|
|
||||||
LoginResponse response = service.ukeyLogin(request);
|
LoginResponse response = service.ukeyLogin(request);
|
||||||
@@ -407,70 +288,207 @@ class AuthServiceTest {
|
|||||||
Assertions.assertEquals(AuthLevel.FULL.name(), response.getAuthLevel());
|
Assertions.assertEquals(AuthLevel.FULL.name(), response.getAuthLevel());
|
||||||
org.mockito.Mockito.verify(randomService).assertIssued(RoleCode.KEY_ADMIN.getCode(), List.of("RB-1"));
|
org.mockito.Mockito.verify(randomService).assertIssued(RoleCode.KEY_ADMIN.getCode(), List.of("RB-1"));
|
||||||
org.mockito.Mockito.verify(verifier).verifyIssuedBinding(
|
org.mockito.Mockito.verify(verifier).verifyIssuedBinding(
|
||||||
"{\"pubKey\":\"PUB-1\",\"authKeyPair\":\"IK-PUB-001\",\"role\":\"KEY_ADMIN\",\"uid\":\"4\",\"rid\":\"4\"}",
|
"{\"pubKey\":\"PUB-1\",\"authKeyPair\":\"IK-PUB-001\",\"role\":\"KEY_ADMIN\",\"uid\":1}",
|
||||||
"ISSUE-1"
|
"ISSUE-1"
|
||||||
);
|
);
|
||||||
org.mockito.Mockito.verify(verifier).verifyLoginSignature("PUB-1", "LOGIN-DATA-1", "LOGIN-SIGN-1");
|
org.mockito.Mockito.verify(verifier).verifyLoginSignature("PUB-1", "LOGIN-DATA-1", "LOGIN-SIGN-1");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RoleAccountEntity activeRole(
|
@Test
|
||||||
RoleCode roleCode,
|
void shouldLockRolePasswordDuringUkeyLoginWhenRolePasswordIsWrong() {
|
||||||
String passwordHash,
|
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||||
String salt,
|
InMemoryAuthUserAccountRepository userAccounts = new InMemoryAuthUserAccountRepository();
|
||||||
int failedCount,
|
InMemoryRoleUkeyBindingRepository ukeyBindings = new InMemoryRoleUkeyBindingRepository();
|
||||||
boolean needChangePassword
|
roleAccounts.save(activeRole(RoleCode.KEY_ADMIN, "12345678", "ROLE-SALT-K", 4, null));
|
||||||
) {
|
userAccounts.save(activeUserAccount("key-admin-01", RoleCode.KEY_ADMIN, "12345678", "SALT-K", 0, null));
|
||||||
return role(roleCode, RoleAccountStatus.ACTIVE, passwordHash, salt, failedCount, needChangePassword, null);
|
ukeyBindings.save(activeBinding(RoleCode.KEY_ADMIN, 1, "UK-1", "PUB-1"));
|
||||||
|
|
||||||
|
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
|
||||||
|
UkeyLoginRandomService randomService = org.mockito.Mockito.mock(UkeyLoginRandomService.class);
|
||||||
|
CompatUkeyVerifier verifier = org.mockito.Mockito.mock(CompatUkeyVerifier.class);
|
||||||
|
org.mockito.Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.NORMAL.getDetail("MAC-001"));
|
||||||
|
org.mockito.Mockito.when(lmkService.exportIkPublicKeyHex()).thenReturn("IK-PUB-001");
|
||||||
|
|
||||||
|
AuthService service = new AuthServiceImpl(
|
||||||
|
roleAccounts,
|
||||||
|
new InMemoryLegacyAuthUserRepository(),
|
||||||
|
userAccounts,
|
||||||
|
new InMemoryAuthSessionRepository(),
|
||||||
|
ukeyBindings,
|
||||||
|
new FakePasswordHasher(),
|
||||||
|
lmkService,
|
||||||
|
randomService,
|
||||||
|
verifier,
|
||||||
|
new InMemoryCaptchaService(),
|
||||||
|
new ObjectMapper(),
|
||||||
|
FIXED_CLOCK,
|
||||||
|
() -> "unused",
|
||||||
|
() -> "salt-full-ukey-003",
|
||||||
|
new AuthPolicyServiceImpl()
|
||||||
|
);
|
||||||
|
|
||||||
|
UkeyLoginRequest request = new UkeyLoginRequest();
|
||||||
|
request.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
||||||
|
request.setRolePassword("bad-role-password");
|
||||||
|
request.setUkeyProofs(List.of(
|
||||||
|
proof("PUB-1", 1, "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1")
|
||||||
|
));
|
||||||
|
|
||||||
|
BizException exception = Assertions.assertThrows(BizException.class, () -> service.ukeyLogin(request));
|
||||||
|
|
||||||
|
Assertions.assertEquals("role account is locked", exception.getMessage());
|
||||||
|
RoleAccountEntity updated = roleAccounts.findByRoleCode(RoleCode.KEY_ADMIN.getCode()).orElseThrow();
|
||||||
|
Assertions.assertEquals(5, updated.getFailedCount());
|
||||||
|
Assertions.assertEquals(RoleAccountStatus.LOCKED.name(), updated.getStatus());
|
||||||
|
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 10), updated.getLockedUntil());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RoleAccountEntity role(
|
private static RoleAccountEntity activeRole(RoleCode roleCode) {
|
||||||
RoleCode roleCode,
|
return activeRole(roleCode, false);
|
||||||
RoleAccountStatus status,
|
}
|
||||||
String passwordHash,
|
|
||||||
String salt,
|
private static RoleAccountEntity activeRole(RoleCode roleCode, boolean needChangePassword) {
|
||||||
int failedCount,
|
|
||||||
boolean needChangePassword,
|
|
||||||
LocalDateTime lockedUntil
|
|
||||||
) {
|
|
||||||
RoleAccountEntity entity = new RoleAccountEntity();
|
RoleAccountEntity entity = new RoleAccountEntity();
|
||||||
entity.setId((long) roleCode.ordinal() + 1);
|
entity.setId((long) roleCode.ordinal() + 1);
|
||||||
entity.setRoleCode(roleCode.getCode());
|
entity.setRoleCode(roleCode.getCode());
|
||||||
entity.setDisplayName(roleCode.getDisplayName());
|
entity.setDisplayName(roleCode.getDisplayName());
|
||||||
entity.setRequiredUkeyCount(roleCode.getRequiredUkeyCount());
|
entity.setRequiredUkeyCount(roleCode.getRequiredUkeyCount());
|
||||||
entity.setPasswordHash(passwordHash);
|
entity.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
entity.setPasswordSalt(salt);
|
|
||||||
entity.setStatus(status.name());
|
|
||||||
entity.setNeedChangePassword(needChangePassword);
|
entity.setNeedChangePassword(needChangePassword);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RoleAccountEntity activeRole(RoleCode roleCode, String rolePassword, String salt) {
|
||||||
|
return activeRole(roleCode, rolePassword, salt, 0, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RoleAccountEntity activeRole(
|
||||||
|
RoleCode roleCode,
|
||||||
|
String rolePassword,
|
||||||
|
String salt,
|
||||||
|
int failedCount,
|
||||||
|
LocalDateTime lockedUntil
|
||||||
|
) {
|
||||||
|
return activeRole(roleCode, rolePassword, salt, failedCount, lockedUntil, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RoleAccountEntity activeRole(
|
||||||
|
RoleCode roleCode,
|
||||||
|
String rolePassword,
|
||||||
|
String salt,
|
||||||
|
int failedCount,
|
||||||
|
LocalDateTime lockedUntil,
|
||||||
|
boolean needChangePassword
|
||||||
|
) {
|
||||||
|
RoleAccountEntity entity = activeRole(roleCode, needChangePassword);
|
||||||
|
entity.setPasswordHash("HASH:" + rolePassword + ":" + salt);
|
||||||
|
entity.setPasswordSalt(salt);
|
||||||
entity.setFailedCount(failedCount);
|
entity.setFailedCount(failedCount);
|
||||||
entity.setLockedUntil(lockedUntil);
|
entity.setLockedUntil(lockedUntil);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RoleUkeyBindingEntity activeBinding(RoleCode roleCode, int slotNo, String serial) {
|
private static AuthUserAccountEntity activeUserAccount(
|
||||||
RoleUkeyBindingEntity entity = new RoleUkeyBindingEntity();
|
String username,
|
||||||
entity.setId((long) slotNo);
|
RoleCode roleCode,
|
||||||
|
String password,
|
||||||
|
String salt,
|
||||||
|
int failedCount,
|
||||||
|
LocalDateTime lockedUntil
|
||||||
|
) {
|
||||||
|
AuthUserAccountEntity entity = new AuthUserAccountEntity();
|
||||||
|
entity.setId((long) (username.hashCode() & Integer.MAX_VALUE));
|
||||||
|
entity.setUsername(username);
|
||||||
|
entity.setDisplayName(username);
|
||||||
entity.setRoleCode(roleCode.getCode());
|
entity.setRoleCode(roleCode.getCode());
|
||||||
entity.setSlotNo(slotNo);
|
entity.setPasswordSalt(salt);
|
||||||
entity.setUkeySerial(serial);
|
entity.setPasswordHash("HASH:" + password + ":" + salt);
|
||||||
entity.setStatus("ACTIVE");
|
entity.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
|
entity.setFailedCount(failedCount);
|
||||||
|
entity.setLockedUntil(lockedUntil);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PasswordLoginAccountRequest account(String username, String password) {
|
||||||
|
PasswordLoginAccountRequest request = new PasswordLoginAccountRequest();
|
||||||
|
request.setUsername(username);
|
||||||
|
request.setPassword(password);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
private static RoleUkeyBindingEntity activeBinding(
|
private static RoleUkeyBindingEntity activeBinding(
|
||||||
RoleCode roleCode,
|
RoleCode roleCode,
|
||||||
int slotNo,
|
int uid,
|
||||||
String serial,
|
String serial,
|
||||||
String pubKey,
|
String pubKey
|
||||||
String uid,
|
|
||||||
String rid
|
|
||||||
) {
|
) {
|
||||||
RoleUkeyBindingEntity entity = activeBinding(roleCode, slotNo, serial);
|
RoleUkeyBindingEntity entity = new RoleUkeyBindingEntity();
|
||||||
entity.setUkeyPubkey(pubKey);
|
entity.setId((long) uid);
|
||||||
|
entity.setRoleCode(roleCode.getCode());
|
||||||
entity.setUid(uid);
|
entity.setUid(uid);
|
||||||
entity.setRid(rid);
|
entity.setUkeySerial(serial);
|
||||||
|
entity.setUkeyPubkey(pubKey);
|
||||||
|
entity.setStatus("ACTIVE");
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static UkeyLoginProof proof(
|
||||||
|
String pubKey,
|
||||||
|
int uid,
|
||||||
|
String serverRandom,
|
||||||
|
String issueSignature,
|
||||||
|
String loginPayload,
|
||||||
|
String loginSignature
|
||||||
|
) {
|
||||||
|
UkeyLoginProof proof = new UkeyLoginProof();
|
||||||
|
proof.setPubKey(pubKey);
|
||||||
|
proof.setUid(uid);
|
||||||
|
proof.setServerRandom(serverRandom);
|
||||||
|
proof.setIssueSignature(issueSignature);
|
||||||
|
proof.setLoginPayload(loginPayload);
|
||||||
|
proof.setLoginSignature(loginSignature);
|
||||||
|
return proof;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AuthSessionEntity session(String token, String roleCode, String authLevel) {
|
||||||
|
AuthSessionEntity entity = new AuthSessionEntity();
|
||||||
|
entity.setId((long) (token.hashCode() & Integer.MAX_VALUE));
|
||||||
|
entity.setSessionToken(token);
|
||||||
|
entity.setRoleCode(roleCode);
|
||||||
|
entity.setAuthLevel(authLevel);
|
||||||
|
entity.setIssuedAt(LocalDateTime.of(2026, 3, 23, 1, 50));
|
||||||
|
entity.setLastActiveAt(LocalDateTime.of(2026, 3, 23, 1, 58));
|
||||||
|
entity.setExpiresAt(LocalDateTime.of(2026, 3, 23, 2, 8));
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AuthService newAuthService(
|
||||||
|
InMemoryRoleAccountRepository roleAccounts,
|
||||||
|
InMemoryAuthUserAccountRepository userAccounts,
|
||||||
|
InMemoryAuthSessionRepository sessions,
|
||||||
|
InMemoryRoleUkeyBindingRepository ukeyBindings,
|
||||||
|
Clock clock,
|
||||||
|
SessionTokenGenerator tokenGenerator
|
||||||
|
) {
|
||||||
|
return new AuthServiceImpl(
|
||||||
|
roleAccounts,
|
||||||
|
new InMemoryLegacyAuthUserRepository(),
|
||||||
|
userAccounts,
|
||||||
|
sessions,
|
||||||
|
ukeyBindings,
|
||||||
|
new FakePasswordHasher(),
|
||||||
|
org.mockito.Mockito.mock(LmkService.class),
|
||||||
|
org.mockito.Mockito.mock(UkeyLoginRandomService.class),
|
||||||
|
org.mockito.Mockito.mock(CompatUkeyVerifier.class),
|
||||||
|
new InMemoryCaptchaService(),
|
||||||
|
new ObjectMapper(),
|
||||||
|
clock,
|
||||||
|
tokenGenerator,
|
||||||
|
() -> "salt-test",
|
||||||
|
new AuthPolicyServiceImpl()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private static class FakePasswordHasher implements PasswordHasher {
|
private static class FakePasswordHasher implements PasswordHasher {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -503,6 +521,33 @@ class AuthServiceTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class InMemoryAuthUserAccountRepository implements AuthUserAccountRepository {
|
||||||
|
private final Map<String, AuthUserAccountEntity> store = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<AuthUserAccountEntity> findByUsername(String username) {
|
||||||
|
return Optional.ofNullable(store.get(username));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AuthUserAccountEntity> findByRoleCode(String roleCode) {
|
||||||
|
return store.values().stream()
|
||||||
|
.filter(entity -> roleCode.equals(entity.getRoleCode()))
|
||||||
|
.sorted(Comparator.comparing(AuthUserAccountEntity::getUsername))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(AuthUserAccountEntity entity) {
|
||||||
|
store.put(entity.getUsername(), entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(AuthUserAccountEntity entity) {
|
||||||
|
store.put(entity.getUsername(), entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class InMemoryLegacyAuthUserRepository implements AuthUserRepository {
|
private static class InMemoryLegacyAuthUserRepository implements AuthUserRepository {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -528,19 +573,15 @@ class AuthServiceTest {
|
|||||||
public void update(AuthSessionEntity entity) {
|
public void update(AuthSessionEntity entity) {
|
||||||
store.put(entity.getSessionToken(), entity);
|
store.put(entity.getSessionToken(), entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int count() {
|
|
||||||
return store.size();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class InMemoryRoleUkeyBindingRepository implements RoleUkeyBindingRepository {
|
private static class InMemoryRoleUkeyBindingRepository implements RoleUkeyBindingRepository {
|
||||||
private final Map<String, List<RoleUkeyBindingEntity>> store = new ConcurrentHashMap<>();
|
private final Map<String, List<RoleUkeyBindingEntity>> store = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndSlotNo(String roleCode, Integer slotNo) {
|
public Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndUid(String roleCode, Integer uid) {
|
||||||
return store.getOrDefault(roleCode, List.of()).stream()
|
return store.getOrDefault(roleCode, List.of()).stream()
|
||||||
.filter(entity -> slotNo.equals(entity.getSlotNo()))
|
.filter(entity -> uid.equals(entity.getUid()))
|
||||||
.filter(entity -> "ACTIVE".equals(entity.getStatus()))
|
.filter(entity -> "ACTIVE".equals(entity.getStatus()))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
@@ -549,7 +590,7 @@ class AuthServiceTest {
|
|||||||
public List<RoleUkeyBindingEntity> findActiveByRoleCode(String roleCode) {
|
public List<RoleUkeyBindingEntity> findActiveByRoleCode(String roleCode) {
|
||||||
return store.getOrDefault(roleCode, List.of()).stream()
|
return store.getOrDefault(roleCode, List.of()).stream()
|
||||||
.filter(entity -> "ACTIVE".equals(entity.getStatus()))
|
.filter(entity -> "ACTIVE".equals(entity.getStatus()))
|
||||||
.sorted(Comparator.comparing(RoleUkeyBindingEntity::getSlotNo))
|
.sorted(Comparator.comparing(RoleUkeyBindingEntity::getUid))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,38 +607,6 @@ class AuthServiceTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AuthSessionEntity session(String token, String roleCode, String authLevel) {
|
|
||||||
AuthSessionEntity entity = new AuthSessionEntity();
|
|
||||||
entity.setId((long) (token.hashCode() & Integer.MAX_VALUE));
|
|
||||||
entity.setSessionToken(token);
|
|
||||||
entity.setRoleCode(roleCode);
|
|
||||||
entity.setAuthLevel(authLevel);
|
|
||||||
entity.setIssuedAt(LocalDateTime.of(2026, 3, 23, 1, 50));
|
|
||||||
entity.setLastActiveAt(LocalDateTime.of(2026, 3, 23, 1, 58));
|
|
||||||
entity.setExpiresAt(LocalDateTime.of(2026, 3, 23, 2, 8));
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static UkeyLoginProof proof(
|
|
||||||
String pubKey,
|
|
||||||
String uid,
|
|
||||||
String rid,
|
|
||||||
String serverRandom,
|
|
||||||
String issueSignature,
|
|
||||||
String loginPayload,
|
|
||||||
String loginSignature
|
|
||||||
) {
|
|
||||||
UkeyLoginProof proof = new UkeyLoginProof();
|
|
||||||
proof.setPubKey(pubKey);
|
|
||||||
proof.setUid(uid);
|
|
||||||
proof.setRid(rid);
|
|
||||||
proof.setServerRandom(serverRandom);
|
|
||||||
proof.setIssueSignature(issueSignature);
|
|
||||||
proof.setLoginPayload(loginPayload);
|
|
||||||
proof.setLoginSignature(loginSignature);
|
|
||||||
return proof;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class InMemoryCaptchaService implements CaptchaService {
|
private static class InMemoryCaptchaService implements CaptchaService {
|
||||||
private final Map<String, String> store = new ConcurrentHashMap<>();
|
private final Map<String, String> store = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@@ -623,29 +632,4 @@ class AuthServiceTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AuthService newAuthService(
|
|
||||||
InMemoryRoleAccountRepository roleAccounts,
|
|
||||||
InMemoryAuthSessionRepository sessions,
|
|
||||||
InMemoryRoleUkeyBindingRepository ukeyBindings,
|
|
||||||
Clock clock,
|
|
||||||
SessionTokenGenerator tokenGenerator
|
|
||||||
) {
|
|
||||||
return new AuthServiceImpl(
|
|
||||||
roleAccounts,
|
|
||||||
new InMemoryLegacyAuthUserRepository(),
|
|
||||||
sessions,
|
|
||||||
ukeyBindings,
|
|
||||||
new FakePasswordHasher(),
|
|
||||||
org.mockito.Mockito.mock(LmkService.class),
|
|
||||||
org.mockito.Mockito.mock(UkeyLoginRandomService.class),
|
|
||||||
org.mockito.Mockito.mock(CompatUkeyVerifier.class),
|
|
||||||
new InMemoryCaptchaService(),
|
|
||||||
new ObjectMapper(),
|
|
||||||
clock,
|
|
||||||
tokenGenerator,
|
|
||||||
() -> "salt-test",
|
|
||||||
new AuthPolicyServiceImpl()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user