角色和登录改动

This commit is contained in:
waner
2026-04-15 11:18:01 +08:00
parent 622702b0b0
commit 9bea50466f
34 changed files with 1078 additions and 693 deletions
@@ -2,8 +2,9 @@ package com.cisd.tms.modules.auth.controller;
import com.cisd.tms.common.api.ApiResponse;
import com.cisd.tms.modules.auth.dto.UkeyBindRequest;
import com.cisd.tms.modules.auth.security.RequireAuthLevel;
import com.cisd.tms.modules.auth.security.RequireRole;
import com.cisd.tms.modules.auth.enums.AuthLevel;
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.mk.dto.UKeySignDTO;
import com.cisd.tms.modules.mk.dto.UKeySignResult;
@@ -31,9 +32,8 @@ public class AuthAdminController {
private final AuthAdminService authAdminService;
@PostMapping("/roles/{roleCode}/enable")
@Operation(summary = "启用角色", description = "仅允许 SUPER_ADMIN FULL 会话启用目标角色。")
@RequireRole("SUPER_ADMIN")
@RequireAuthLevel("FULL")
@Operation(summary = "启用角色", description = "仅允许 KEY_ADMIN FULL 会话启用目标角色。")
@RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.FULL)
public ApiResponse<Void> enableRole(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
authAdminService.enableRole(
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
@@ -44,9 +44,8 @@ public class AuthAdminController {
}
@PostMapping("/roles/{roleCode}/reset-password")
@Operation(summary = "重置角色密码", description = "仅允许 SUPER_ADMIN FULL 会话重置目标角色密码。")
@RequireRole("SUPER_ADMIN")
@RequireAuthLevel("FULL")
@Operation(summary = "重置角色密码", description = "仅允许 KEY_ADMIN FULL 会话重置目标角色密码。")
@RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.FULL)
public ApiResponse<Void> resetPassword(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
authAdminService.resetPassword(
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
@@ -57,9 +56,8 @@ public class AuthAdminController {
}
@PostMapping("/roles/{roleCode}/ukeys/bind")
@Operation(summary = "绑定角色 UKey", description = "仅允许 SUPER_ADMIN FULL 会话登记目标角色的 UKey 绑定信息。")
@RequireRole("SUPER_ADMIN")
@RequireAuthLevel("FULL")
@Operation(summary = "绑定角色 UKey", description = "仅允许 KEY_ADMIN FULL 会话登记目标角色的 UKey 绑定信息。")
@RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.FULL)
public ApiResponse<Void> bindUkey(
@PathVariable("roleCode") String roleCode,
@Valid @RequestBody UkeyBindRequest request,
@@ -69,11 +67,9 @@ public class AuthAdminController {
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
roleCode,
request.getSlotNo(),
request.getUid(),
request.getUkeySerial(),
request.getPubKey(),
request.getUid(),
request.getRid(),
request.getIssuerSignature()
);
return ApiResponse.success();
@@ -81,8 +77,7 @@ public class AuthAdminController {
@PostMapping("/roles/{roleCode}/ukeys/issue-sign")
@Operation(summary = "生成 UKey 发行签名", description = "按旧绑定流程为目标角色 UKey 材料生成发行签名。")
// @RequireRole("SUPER_ADMIN")
@RequireAuthLevel("FULL")
@RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.LIMITED)
public ApiResponse<UKeySignResult> issueUkeyBindingSign(
@PathVariable("roleCode") String roleCode,
@RequestBody UKeySignDTO request,
@@ -11,9 +11,11 @@ public class LoginRequest {
@Schema(description = "角色编码", example = "KEY_ADMIN")
private String roleCode;
@NotBlank(message = "password is required")
@Schema(description = "登录密码", example = "P@ssw0rd")
private String password;
@Schema(description = "参与角色口令校验的账号列表")
private List<PasswordLoginAccountRequest> accounts;
@Schema(description = "角色口令")
private String rolePassword;
@Schema(description = "UKey 序列号列表,完整登录时需传入角色要求数量的序列号")
private List<@NotBlank(message = "ukey serial must not be blank") String> ukeySerials;
@@ -26,12 +28,20 @@ public class LoginRequest {
this.roleCode = roleCode;
}
public String getPassword() {
return password;
public List<PasswordLoginAccountRequest> getAccounts() {
return accounts;
}
public void setPassword(String password) {
this.password = password;
public void setAccounts(List<PasswordLoginAccountRequest> accounts) {
this.accounts = accounts;
}
public String getRolePassword() {
return rolePassword;
}
public void setRolePassword(String rolePassword) {
this.rolePassword = rolePassword;
}
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;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
@Schema(description = "口令登录请求")
public class PasswordLoginRequest {
@@ -13,9 +16,10 @@ public class PasswordLoginRequest {
@Schema(description = "角色编码", example = "AUDIT_ADMIN")
private String roleCode;
@NotBlank(message = "password is required")
@Schema(description = "登录口令", example = "12345678")
private String password;
@Valid
@NotEmpty(message = "accounts is required")
@Schema(description = "参与本次角色口令校验的账号列表")
private List<PasswordLoginAccountRequest> accounts;
@NotBlank(message = "captchaCode is required")
@Schema(description = "图形验证码", example = "ABCD")
@@ -33,12 +37,12 @@ public class PasswordLoginRequest {
this.roleCode = roleCode;
}
public String getPassword() {
return password;
public List<PasswordLoginAccountRequest> getAccounts() {
return accounts;
}
public void setPassword(String password) {
this.password = password;
public void setAccounts(List<PasswordLoginAccountRequest> accounts) {
this.accounts = accounts;
}
public String getCaptchaCode() {
@@ -1,15 +1,15 @@
package com.cisd.tms.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.NotBlank;
@Schema(description = "UKey 绑定请求")
public class UkeyBindRequest {
@NotNull(message = "slotNo is required")
@Schema(description = "角色 UKey 槽位号", example = "1")
private Integer slotNo;
@NotNull(message = "uid is required")
@Schema(description = "角色内固定席位编号", example = "1")
private Integer uid;
@NotBlank(message = "ukeySerial is required")
@Schema(description = "UKey 序列号", example = "UK-001")
@@ -19,24 +19,16 @@ public class UkeyBindRequest {
@Schema(description = "UKey 公钥")
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")
@Schema(description = "认证公钥签名值")
private String issuerSignature;
public Integer getSlotNo() {
return slotNo;
public Integer getUid() {
return uid;
}
public void setSlotNo(Integer slotNo) {
this.slotNo = slotNo;
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUkeySerial() {
@@ -62,20 +54,4 @@ public class UkeyBindRequest {
public void setIssuerSignature(String 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 jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@Schema(description = "单个 UKey 登录证明")
public class UkeyLoginProof {
@@ -9,11 +10,8 @@ public class UkeyLoginProof {
@NotBlank(message = "pubKey is required")
private String pubKey;
@NotBlank(message = "uid is required")
private String uid;
@NotBlank(message = "rid is required")
private String rid;
@NotNull(message = "uid is required")
private Integer uid;
/**
* 对应旧系统下发的 rb,命名改为更明确的服务端随机数。
@@ -38,22 +36,14 @@ public class UkeyLoginProof {
this.pubKey = pubKey;
}
public String getUid() {
public Integer getUid() {
return uid;
}
public void setUid(String uid) {
public void setUid(Integer uid) {
this.uid = uid;
}
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getServerRandom() {
return serverRandom;
}
@@ -13,9 +13,9 @@ public class UkeyLoginRequest {
@Schema(description = "角色编码", example = "KEY_ADMIN")
private String roleCode;
@NotBlank(message = "password is required")
@NotBlank(message = "rolePassword is required")
@Schema(description = "角色口令", example = "12345678")
private String password;
private String rolePassword;
@Valid
@NotEmpty(message = "ukeyProofs is required")
@@ -30,12 +30,12 @@ public class UkeyLoginRequest {
this.roleCode = roleCode;
}
public String getPassword() {
return password;
public String getRolePassword() {
return rolePassword;
}
public void setPassword(String password) {
this.password = password;
public void setRolePassword(String rolePassword) {
this.rolePassword = rolePassword;
}
public List<UkeyLoginProof> getUkeyProofs() {
@@ -15,6 +15,10 @@ public class AuthSessionEntity extends BaseEntity {
* 当前会话所属角色编码。
*/
private String roleCode;
/**
* 本次角色会话在后端侧记录的认证账号列表。
*/
private String authenticatedUsersJson;
/**
* 当前会话的认证方式,区分口令登录和 UKey 登录。
*/
@@ -44,6 +48,14 @@ public class AuthSessionEntity extends BaseEntity {
this.roleCode = roleCode;
}
public String getAuthenticatedUsersJson() {
return authenticatedUsersJson;
}
public void setAuthenticatedUsersJson(String authenticatedUsersJson) {
this.authenticatedUsersJson = authenticatedUsersJson;
}
public String getAuthMethod() {
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;
/**
* 角色下的 UKey 槽位,从 1 开始,和旧系统卡位模型保持一致。
* 角色内固定席位编号,从 1 开始。
*/
private Integer slotNo;
/**
* 旧系统 auth 文件中的 uid,登录时要参与认证匹配。
*/
private String uid;
/**
* 旧系统 auth 文件中的 rid,登录时要参与角色槽位校验。
*/
private String rid;
private Integer uid;
private String ukeySerial;
private String ukeyPubkey;
private String issuerSign;
@@ -38,30 +30,14 @@ public class RoleUkeyBindingEntity extends BaseEntity {
this.roleCode = roleCode;
}
public Integer getSlotNo() {
return slotNo;
}
public void setSlotNo(Integer slotNo) {
this.slotNo = slotNo;
}
public String getUid() {
public Integer getUid() {
return uid;
}
public void setUid(String uid) {
public void setUid(Integer uid) {
this.uid = uid;
}
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getUkeySerial() {
return ukeySerial;
}
@@ -0,0 +1,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
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);
}
@@ -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 {
Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndSlotNo(String roleCode, Integer slotNo);
Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndUid(String roleCode, Integer uid);
List<RoleUkeyBindingEntity> findActiveByRoleCode(String roleCode);
@@ -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);
}
}
@@ -17,8 +17,8 @@ public class RoleUkeyBindingRepositoryImpl implements RoleUkeyBindingRepository
}
@Override
public Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndSlotNo(String roleCode, Integer slotNo) {
return Optional.ofNullable(roleUkeyBindingMapper.selectActiveByRoleCodeAndSlotNo(roleCode, slotNo));
public Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndUid(String roleCode, Integer uid) {
return Optional.ofNullable(roleUkeyBindingMapper.selectActiveByRoleCodeAndUid(roleCode, uid));
}
@Override
@@ -1,5 +1,7 @@
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.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -7,7 +9,9 @@ import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@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 operatorAuthLevel,
String targetRoleCode,
Integer slotNo,
Integer uid,
String ukeySerial,
String ukeyPubkey,
String uid,
String rid,
String issuerSign
);
@@ -25,11 +23,9 @@ public interface AuthAdminService {
String operatorRoleCode,
String operatorAuthLevel,
String targetRoleCode,
Integer slotNo,
Integer uid,
String ukeySerial,
String ukeyPubkey,
String uid,
String rid,
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.exception.BizException;
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.RoleUkeyBindingEntity;
import com.cisd.tms.modules.auth.enums.AuthLevel;
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
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.RoleUkeyBindingRepository;
import com.cisd.tms.modules.auth.service.AuthAdminService;
@@ -35,6 +36,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
static final String DEFAULT_PASSWORD = "12345678";
private final RoleAccountRepository roleAccountRepository;
private final AuthUserAccountRepository authUserAccountRepository;
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
private final PasswordHasher passwordHasher;
private final LmkService lmkService;
@@ -44,7 +46,6 @@ public class AuthAdminServiceImpl implements AuthAdminService {
@Override
public void enableRole(String operatorRoleCode, String operatorAuthLevel, String targetRoleCode) {
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
RoleAccountEntity target = loadRole(targetRoleCode);
target.setStatus(RoleAccountStatus.ACTIVE.name());
target.setNeedChangePassword(Boolean.TRUE);
@@ -55,16 +56,31 @@ public class AuthAdminServiceImpl implements AuthAdminService {
@Override
public void resetPassword(String operatorRoleCode, String operatorAuthLevel, String targetRoleCode) {
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
RoleAccountEntity target = loadRole(targetRoleCode);
String newSalt = passwordSaltGenerator.nextSalt();
target.setPasswordSalt(newSalt);
target.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD, newSalt));
String roleSalt = passwordSaltGenerator.nextSalt();
target.setPasswordSalt(roleSalt);
target.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD, roleSalt));
target.setStatus(RoleAccountStatus.ACTIVE.name());
target.setNeedChangePassword(Boolean.TRUE);
target.setFailedCount(0);
target.setLockedUntil(null);
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
@@ -72,33 +88,30 @@ public class AuthAdminServiceImpl implements AuthAdminService {
String operatorRoleCode,
String operatorAuthLevel,
String targetRoleCode,
Integer slotNo,
Integer uid,
String ukeySerial,
String ukeyPubkey,
String uid,
String rid,
String issuerSign
) {
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
RoleCode targetRole = resolveRoleCode(targetRoleCode);
if (slotNo == null || slotNo < 1 || slotNo > targetRole.getRequiredUkeyCount()) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "slotNo exceeds role ukey requirement");
if (uid == null || uid < 1 || uid > targetRole.getRequiredUkeyCount()) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "uid exceeds role ukey requirement");
}
RoleUkeyBindingEntity binding = roleUkeyBindingRepository
.findActiveByRoleCodeAndSlotNo(targetRoleCode, slotNo)
.findActiveByRoleCodeAndUid(targetRoleCode, uid)
.orElseGet(RoleUkeyBindingEntity::new);
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.setSlotNo(slotNo);
binding.setUid(uid);
binding.setStatus("ACTIVE");
applyBinding(binding, ukeySerial, ukeyPubkey, uid, rid, issuerSign);
applyBinding(binding, ukeySerial, ukeyPubkey, issuerSign);
roleUkeyBindingRepository.save(binding);
return;
}
applyBinding(binding, ukeySerial, ukeyPubkey, uid, rid, issuerSign);
applyBinding(binding, ukeySerial, ukeyPubkey, issuerSign);
roleUkeyBindingRepository.update(binding);
}
@@ -107,17 +120,14 @@ public class AuthAdminServiceImpl implements AuthAdminService {
String operatorRoleCode,
String operatorAuthLevel,
String targetRoleCode,
Integer slotNo,
Integer uid,
String ukeySerial,
String ukeyPubkey,
String uid,
String rid,
String issuerSign
) {
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
resolveRoleCode(targetRoleCode);
verifyIssuerSignature(targetRoleCode, ukeyPubkey, uid, rid, issuerSign);
bindUkey(operatorRoleCode, operatorAuthLevel, targetRoleCode, slotNo, ukeySerial, ukeyPubkey, uid, rid, issuerSign);
verifyIssuerSignature(targetRoleCode, ukeyPubkey, uid, issuerSign);
bindUkey(operatorRoleCode, operatorAuthLevel, targetRoleCode, uid, ukeySerial, ukeyPubkey, issuerSign);
}
@Override
@@ -127,7 +137,6 @@ public class AuthAdminServiceImpl implements AuthAdminService {
String targetRoleCode,
UKeySignDTO request
) {
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
RoleCode targetRole = resolveRoleCode(targetRoleCode);
String authKeyPair = lmkService.exportIkPublicKeyHex();
@@ -135,12 +144,11 @@ public class AuthAdminServiceImpl implements AuthAdminService {
dto.setPubKey(request.getPubKey());
dto.setRole(targetRole.getCode());
dto.setUid(request.getUid());
dto.setRid(request.getRid());
String signValue = lmkService.signIk(toIssuePayload(dto, authKeyPair));
MasterKeyBackupPacket backupPacket = null;
if (RoleCode.SUPER_ADMIN == targetRole) {
backupPacket = lmkService.buildBackupPacket(Integer.parseInt(request.getUid()));
backupPacket = lmkService.buildBackupPacket(request.getUid());
}
return UKeySignResult.builder()
.sign(signValue)
@@ -152,26 +160,16 @@ public class AuthAdminServiceImpl implements AuthAdminService {
RoleUkeyBindingEntity binding,
String ukeySerial,
String ukeyPubkey,
String uid,
String rid,
String issuerSign
) {
binding.setUkeySerial(ukeySerial);
binding.setUkeyPubkey(ukeyPubkey);
binding.setUid(uid);
binding.setRid(rid);
binding.setIssuerSign(issuerSign);
binding.setStatus("ACTIVE");
binding.setBoundAt(now());
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) {
resolveRoleCode(roleCode);
return roleAccountRepository.findByRoleCode(roleCode)
@@ -189,13 +187,12 @@ public class AuthAdminServiceImpl implements AuthAdminService {
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 {
UKeySignDTO dto = new UKeySignDTO();
dto.setPubKey(ukeyPubkey);
dto.setRole(resolveRoleCode(targetRoleCode).getCode());
dto.setUid(uid);
dto.setRid(rid);
String authKeyPair = lmkService.exportIkPublicKeyHex();
lmkService.verifyIk(toIssuePayload(dto, authKeyPair), issuerSign);
} 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.LoginRequest;
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.UkeyLoginProof;
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
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.RoleCode;
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.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.UKeySignEntity;
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
@@ -34,6 +44,7 @@ import java.time.Clock;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -51,6 +62,7 @@ public class AuthServiceImpl implements AuthService {
private final RoleAccountRepository roleAccountRepository;
private final AuthUserRepository authUserRepository;
private final AuthUserAccountRepository authUserAccountRepository;
private final AuthSessionRepository authSessionRepository;
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
private final PasswordHasher passwordHasher;
@@ -70,14 +82,22 @@ public class AuthServiceImpl implements AuthService {
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account not found"));
validateRoleStatus(roleAccount);
if (!passwordHasher.matches(request.getPassword(), roleAccount.getPasswordSalt(), roleAccount.getPasswordHash())) {
onPasswordFailed(roleAccount);
}
resetFailureState(roleAccount);
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());
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);
LoginResponse response = new LoginResponse();
@@ -85,7 +105,7 @@ public class AuthServiceImpl implements AuthService {
response.setAuthLevel(authLevel.name());
response.setToken(session.getSessionToken());
response.setExpiresAt(OffsetDateTime.of(session.getExpiresAt(), ZoneOffset.UTC).toString());
response.setNeedChangePassword(Boolean.TRUE.equals(roleAccount.getNeedChangePassword()));
response.setNeedChangePassword(needsPasswordChange(roleAccount));
return response;
}
@@ -95,7 +115,7 @@ public class AuthServiceImpl implements AuthService {
captchaService.verify(request.getCaptchaId(), request.getCaptchaCode());
LoginRequest loginRequest = new LoginRequest();
loginRequest.setRoleCode(request.getRoleCode());
loginRequest.setPassword(request.getPassword());
loginRequest.setAccounts(request.getAccounts());
return login(loginRequest);
}
@@ -114,12 +134,12 @@ public class AuthServiceImpl implements AuthService {
RoleCode roleCode = RoleCode.valueOf(request.getRoleCode());
List<RoleUkeyBindingEntity> activeBindings = roleUkeyBindingRepository.findActiveByRoleCode(roleCode.getCode());
validateUkeyCount(roleCode, activeBindings, request.getUkeyProofs());
Map<String, RoleUkeyBindingEntity> bindingsByPubKey = activeBindings.stream()
.collect(Collectors.toMap(RoleUkeyBindingEntity::getUkeyPubkey, item -> item, (left, right) -> left, java.util.LinkedHashMap::new));
Set<String> requestPubKeys = request.getUkeyProofs().stream()
.map(UkeyLoginProof::getPubKey)
Map<Integer, RoleUkeyBindingEntity> bindingsByUid = activeBindings.stream()
.collect(Collectors.toMap(RoleUkeyBindingEntity::getUid, item -> item, (left, right) -> left, java.util.LinkedHashMap::new));
Set<Integer> requestUids = request.getUkeyProofs().stream()
.map(UkeyLoginProof::getUid)
.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");
}
ukeyLoginRandomService.assertIssued(
@@ -127,16 +147,19 @@ public class AuthServiceImpl implements AuthService {
request.getUkeyProofs().stream().map(UkeyLoginProof::getServerRandom).toList()
);
String authKeyPair = lmkService.exportIkPublicKeyHex();
List<String> matchedSerials = new java.util.ArrayList<>();
List<String> matchedSerials = new ArrayList<>();
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.verifyLoginSignature(proof.getPubKey(), proof.getLoginPayload(), proof.getLoginSignature());
matchedSerials.add(binding.getUkeySerial());
}
LoginRequest loginRequest = new LoginRequest();
loginRequest.setRoleCode(request.getRoleCode());
loginRequest.setPassword(request.getPassword());
loginRequest.setRolePassword(request.getRolePassword());
loginRequest.setUkeySerials(matchedSerials);
return login(loginRequest);
}
@@ -164,23 +187,24 @@ public class AuthServiceImpl implements AuthService {
if (session != null) {
response.setAuthLevel(session.getAuthLevel());
}
response.setNeedChangePassword(Boolean.TRUE.equals(roleAccount.getNeedChangePassword()));
response.setNeedChangePassword(needsPasswordChange(roleAccount));
return response;
}
AuthUserEntity entity = authUserRepository.findByUsername(normalized).orElse(null);
CurrentUserResponse response = new CurrentUserResponse();
if (entity == null) {
response.setUsername(normalized);
response.setDisplayName("TMS Administrator");
response.setRole("ADMIN");
response.setNeedChangePassword(Boolean.FALSE);
return response;
}
response.setUsername(entity.getUsername());
response.setDisplayName(entity.getDisplayName());
response.setRole(entity.getRole());
response.setNeedChangePassword(Boolean.FALSE);
return response;
}
@@ -197,34 +221,107 @@ public class AuthServiceImpl implements AuthService {
AuthSessionEntity session = requireActiveSession(sessionToken);
RoleAccountEntity roleAccount = roleAccountRepository.findByRoleCode(session.getRoleCode())
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account not found"));
validateRoleStatus(roleAccount);
if (!passwordHasher.matches(currentPassword, roleAccount.getPasswordSalt(), roleAccount.getPasswordHash())) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "current password is incorrect");
onRolePasswordFailed(roleAccount);
}
String newSalt = passwordSaltGenerator.nextSalt();
roleAccount.setPasswordSalt(newSalt);
roleAccount.setPasswordHash(passwordHasher.hash(newPassword, newSalt));
String salt = passwordSaltGenerator.nextSalt();
LocalDateTime current = now();
roleAccount.setPasswordSalt(salt);
roleAccount.setPasswordHash(passwordHasher.hash(newPassword, salt));
roleAccount.setNeedChangePassword(Boolean.FALSE);
roleAccount.setFailedCount(0);
roleAccount.setLockedUntil(null);
roleAccount.setStatus(RoleAccountStatus.ACTIVE.name());
roleAccount.setLastActiveAt(current);
roleAccountRepository.update(roleAccount);
session.setLastActiveAt(current);
session.setExpiresAt(current.plusMinutes(IDLE_TIMEOUT_MINUTES));
authSessionRepository.update(session);
}
private void validateRoleStatus(RoleAccountEntity roleAccount) {
if (RoleAccountStatus.UNENABLED.name().equals(roleAccount.getStatus())) {
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");
}
}
private boolean isStillLocked(RoleAccountEntity roleAccount) {
return roleAccount.getLockedUntil() == null || roleAccount.getLockedUntil().isAfter(now());
private void validateRolePassword(RoleAccountEntity roleAccount, String rolePassword) {
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();
failedCount++;
roleAccount.setFailedCount(failedCount);
@@ -238,7 +335,19 @@ public class AuthServiceImpl implements AuthService {
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.setLockedUntil(null);
roleAccount.setStatus(RoleAccountStatus.ACTIVE.name());
@@ -247,6 +356,10 @@ public class AuthServiceImpl implements AuthService {
roleAccountRepository.update(roleAccount);
}
private int requiredPasswordAccountCount(String roleCode) {
return RoleCode.valueOf(roleCode).getRequiredUkeyCount();
}
private void ensureMasterKeyReady() {
MasterKeyStatus.StatusDetail status = lmkService.getMasterKeyStatus();
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
@@ -264,18 +377,19 @@ public class AuthServiceImpl implements AuthService {
}
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");
}
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");
}
Set<String> boundSerials = bindings.stream()
.map(RoleUkeyBindingEntity::getUkeySerial)
.collect(java.util.stream.Collectors.toSet());
.collect(Collectors.toSet());
if (!boundSerials.containsAll(uniqueSerials)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey verification failed");
}
@@ -300,7 +414,6 @@ public class AuthServiceImpl implements AuthService {
dto.setPubKey(proof.getPubKey());
dto.setRole(RoleCode.valueOf(roleCode).getCode());
dto.setUid(proof.getUid());
dto.setRid(proof.getRid());
try {
return objectMapper.writeValueAsString(UKeySignEntity.getInstance(dto, authKeyPair));
} 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();
AuthSessionEntity entity = new AuthSessionEntity();
entity.setRoleCode(roleCode);
entity.setAuthenticatedUsersJson(writeAuthenticatedUsers(authenticatedUsers));
entity.setAuthMethod(authMethod.name());
entity.setAuthLevel(authLevel.name());
entity.setSessionToken(sessionTokenGenerator.nextToken());
@@ -321,10 +440,25 @@ public class AuthServiceImpl implements AuthService {
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() {
return LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC);
}
private boolean needsPasswordChange(RoleAccountEntity roleAccount) {
return Boolean.TRUE.equals(roleAccount.getNeedChangePassword());
}
private AuthSessionEntity requireActiveSession(String sessionToken) {
AuthSessionEntity session = authSessionRepository.findBySessionToken(sessionToken)
.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.LocalDateTime;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@@ -18,6 +19,7 @@ public class RunnerStallDetector {
private final ResourceBackupProperties resourceBackupProperties;
private final Supplier<LocalDateTime> nowSupplier;
@Autowired
public RunnerStallDetector(
ResourceRestoreTaskRepository resourceRestoreTaskRepository,
ResourceRestoreStateFileStore resourceRestoreStateFileStore,
@@ -83,8 +83,8 @@ public class MasterKeyRestoreDTO {
this.iDevPriKeyLen = iDevPriKeyLen;
}
public Map<String,String> getComponents() {
Map<String,String> componentMap = new HashMap<>();
public Map<Integer,String> getComponents() {
Map<Integer,String> componentMap = new HashMap<>();
for(UKeySignVerifyDTO uKeySignVerifyDTO : uKeySignVerifyDTOList){
if(null == uKeySignVerifyDTO.getKeyComponent()){
throw new IllegalArgumentException("UKey无主密钥分量,UID:" + uKeySignVerifyDTO.getuKeySignDTO().getUid());
@@ -9,11 +9,6 @@ public class UKey {
*/
private String uid;
/**
* 角色 ID
*/
private String rid;
/**
* 设备公钥 HEX(32 字节 x + 32 字节 y)
*/
@@ -18,13 +18,8 @@ public class UKeySignDTO {
private String role;
/**
* UKey 中的认证信息
* 角色内固定席位编号。
*/
private String uid;
/**
* UKey 中的认证信息
*/
private String rid;
private Integer uid;
}
@@ -25,11 +25,7 @@ public class UKeySignEntity {
/**
* 登录时传输
*/
private String uid;
/**
* 登录时传输
*/
private String rid;
private Integer uid;
public static UKeySignEntity getInstance(UKeySignDTO uKeySignDTO,String authKeyPair){
return UKeySignEntity.builder()
@@ -37,7 +33,6 @@ public class UKeySignEntity {
.authKeyPair(authKeyPair)
.role(uKeySignDTO.getRole())
.uid(uKeySignDTO.getUid())
.rid(uKeySignDTO.getRid())
.build();
}
}
@@ -260,9 +260,7 @@ CREATE TABLE IF NOT EXISTS tms_role_account (
CREATE TABLE IF NOT EXISTS tms_role_ukey_binding (
id BIGINT PRIMARY KEY,
role_code VARCHAR(64) NOT NULL,
slot_no INT NOT NULL,
uid VARCHAR(64) NULL,
rid VARCHAR(64) NULL,
uid INT NOT NULL,
ukey_serial VARCHAR(128) NOT NULL,
ukey_pubkey TEXT NOT NULL,
issuer_sign TEXT NULL,
@@ -271,14 +269,33 @@ CREATE TABLE IF NOT EXISTS tms_role_ukey_binding (
unbound_at DATETIME(3) NULL,
create_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)
);
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 (
id BIGINT PRIMARY KEY,
session_token VARCHAR(128) NOT NULL,
role_code VARCHAR(64) NOT NULL,
authenticated_users_json JSON NULL,
auth_method VARCHAR(32) NOT NULL,
auth_level VARCHAR(32) NOT NULL,
issued_at DATETIME(3) NOT NULL,
@@ -378,7 +395,7 @@ VALUES
11001,
'SUPER_ADMIN',
'超级管理员',
3,
2,
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
'init-super-admin-salt-20260325',
'UNENABLED',
@@ -394,7 +411,7 @@ VALUES
11002,
'KEY_ADMIN',
'密钥管理员',
2,
1,
'/xqmDy3A/q9X5p7XWznIPSBabQ6bxTLAPgtHQ6Be33c=',
'init-key-admin-salt-20260325',
'ACTIVE',
@@ -439,6 +456,98 @@ VALUES
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白名单表
@@ -8,6 +8,7 @@
<id property="id" column="id"/>
<result property="sessionToken" column="session_token"/>
<result property="roleCode" column="role_code"/>
<result property="authenticatedUsersJson" column="authenticated_users_json"/>
<result property="authMethod" column="auth_method"/>
<result property="authLevel" column="auth_level"/>
<result property="issuedAt" column="issued_at"/>
@@ -22,6 +23,7 @@
SELECT id,
session_token,
role_code,
authenticated_users_json,
auth_method,
auth_level,
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">
<id property="id" column="id"/>
<result property="roleCode" column="role_code"/>
<result property="slotNo" column="slot_no"/>
<result property="uid" column="uid"/>
<result property="rid" column="rid"/>
<result property="ukeySerial" column="ukey_serial"/>
<result property="ukeyPubkey" column="ukey_pubkey"/>
<result property="issuerSign" column="issuer_sign"/>
@@ -20,12 +18,10 @@
<result property="updateTime" column="update_time"/>
</resultMap>
<select id="selectActiveByRoleCodeAndSlotNo" resultMap="RoleUkeyBindingResultMap">
<select id="selectActiveByRoleCodeAndUid" resultMap="RoleUkeyBindingResultMap">
SELECT id,
role_code,
slot_no,
uid,
rid,
ukey_serial,
ukey_pubkey,
issuer_sign,
@@ -36,7 +32,7 @@
update_time
FROM tms_role_ukey_binding
WHERE role_code = #{roleCode}
AND slot_no = #{slotNo}
AND uid = #{uid}
AND status = 'ACTIVE'
LIMIT 1
</select>
@@ -44,9 +40,7 @@
<select id="selectActiveByRoleCode" parameterType="string" resultMap="RoleUkeyBindingResultMap">
SELECT id,
role_code,
slot_no,
uid,
rid,
ukey_serial,
ukey_pubkey,
issuer_sign,
@@ -58,6 +52,6 @@
FROM tms_role_ukey_binding
WHERE role_code = #{roleCode}
AND status = 'ACTIVE'
ORDER BY slot_no ASC
ORDER BY uid ASC
</select>
</mapper>