fix:角色设计修改

This commit is contained in:
waner
2026-04-22 18:29:00 +08:00
parent 1fc32dbcf0
commit a3fd33a6e4
34 changed files with 1819 additions and 338 deletions
@@ -78,7 +78,7 @@ public class AuthAdminController {
@PostMapping("/roles/{roleCode}/ukeys/issue-sign")
@Operation(summary = "生成 UKey 发行签名", description = "按旧绑定流程为目标角色 UKey 材料生成发行签名。")
@RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.LIMITED)
// @RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.LIMITED)
public ApiResponse<UKeySignResult> issueUkeyBindingSign(
@PathVariable("roleCode") String roleCode,
@RequestBody UKeySignDTO request,
@@ -65,13 +65,36 @@ public class AuthController {
return ApiResponse.success();
}
@PostMapping("/change-password")
@Operation(summary = "修改当前角色口令", description = "基于当前会话校验并更新当前角色口令。")
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.UPDATE, summary = "修改当前角色口令")
@PostMapping("/full-accounts/{uid}/change-password")
@Operation(summary = "修改当前 FULL 账户口令", description = "基于当前 FULL 会话校验并更新指定席位账户口令。")
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.UPDATE, summary = "修改当前 FULL 账户口令")
@ReplayProtected
public ApiResponse<Void> changePassword(@Valid @RequestBody ChangePasswordRequest request, HttpServletRequest httpRequest) {
authService.changePassword(
public ApiResponse<Void> changeFullAccountPassword(
@PathVariable("uid") Integer uid,
@Valid @RequestBody ChangePasswordRequest request,
HttpServletRequest httpRequest
) {
authService.changeFullAccountPassword(
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN),
uid,
request.getCurrentPassword(),
request.getNewPassword()
);
return ApiResponse.success();
}
@PostMapping("/limited-accounts/{username}/change-password")
@Operation(summary = "修改当前 LIMITED 账户口令", description = "基于当前 LIMITED 会话校验并更新指定受限账户口令。")
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.UPDATE, summary = "修改当前 LIMITED 账户口令")
@ReplayProtected
public ApiResponse<Void> changeLimitedAccountPassword(
@PathVariable("username") String username,
@Valid @RequestBody ChangePasswordRequest request,
HttpServletRequest httpRequest
) {
authService.changeLimitedAccountPassword(
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN),
username,
request.getCurrentPassword(),
request.getNewPassword()
);
@@ -0,0 +1,46 @@
package com.cisd.tms.modules.auth.dto;
public class AuthSessionPrincipal {
private String type;
private Integer uid;
private String username;
public static AuthSessionPrincipal full(Integer uid) {
AuthSessionPrincipal principal = new AuthSessionPrincipal();
principal.setType("FULL");
principal.setUid(uid);
return principal;
}
public static AuthSessionPrincipal limited(String username) {
AuthSessionPrincipal principal = new AuthSessionPrincipal();
principal.setType("LIMITED");
principal.setUsername(username);
return principal;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
@@ -3,7 +3,7 @@ package com.cisd.tms.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
@Schema(description = "修改当前角色口令请求")
@Schema(description = "修改当前认证账户口令请求")
public class ChangePasswordRequest {
@NotBlank(message = "currentPassword is required")
@@ -0,0 +1,23 @@
package com.cisd.tms.modules.auth.dto;
public class FullLoginAccountRequest {
private Integer uid;
private String password;
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
@@ -14,8 +14,8 @@ public class LoginRequest {
@Schema(description = "参与角色口令校验的账号列表")
private List<PasswordLoginAccountRequest> accounts;
@Schema(description = "角色口令")
private String rolePassword;
@Schema(description = "参与 FULL 登录校验的席位账户列表")
private List<FullLoginAccountRequest> fullAccounts;
@Schema(description = "UKey 序列号列表,完整登录时需传入角色要求数量的序列号")
private List<@NotBlank(message = "ukey serial must not be blank") String> ukeySerials;
@@ -36,12 +36,12 @@ public class LoginRequest {
this.accounts = accounts;
}
public String getRolePassword() {
return rolePassword;
public List<FullLoginAccountRequest> getFullAccounts() {
return fullAccounts;
}
public void setRolePassword(String rolePassword) {
this.rolePassword = rolePassword;
public void setFullAccounts(List<FullLoginAccountRequest> fullAccounts) {
this.fullAccounts = fullAccounts;
}
public List<String> getUkeySerials() {
@@ -21,11 +21,11 @@ public class PasswordLoginRequest {
@Schema(description = "参与本次角色口令校验的账号列表")
private List<PasswordLoginAccountRequest> accounts;
@NotBlank(message = "captchaCode is required")
// @NotBlank(message = "captchaCode is required")
@Schema(description = "图形验证码", example = "ABCD")
private String captchaCode;
@NotBlank(message = "captchaId is required")
// @NotBlank(message = "captchaId is required")
@Schema(description = "验证码标识", example = "captcha-001")
private String captchaId;
@@ -4,7 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@Schema(description = "单个 UKey 登录证明")
@Schema(description = "单个 UKey 登录因子")
public class UkeyLoginProof {
@NotBlank(message = "pubKey is required")
@@ -28,6 +28,9 @@ public class UkeyLoginProof {
@NotBlank(message = "loginSignature is required")
private String loginSignature;
@Schema(description = "当前 UKey 固定席位对应账号的口令,所有角色都必传", example = "12345678")
private String password;
public String getPubKey() {
return pubKey;
}
@@ -75,4 +78,12 @@ public class UkeyLoginProof {
public void setLoginSignature(String loginSignature) {
this.loginSignature = loginSignature;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
@@ -1,5 +1,6 @@
package com.cisd.tms.modules.auth.dto;
import com.fasterxml.jackson.annotation.JsonAlias;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
@@ -13,14 +14,11 @@ public class UkeyLoginRequest {
@Schema(description = "角色编码", example = "KEY_ADMIN")
private String roleCode;
@NotBlank(message = "rolePassword is required")
@Schema(description = "角色口令", example = "12345678")
private String rolePassword;
@Valid
@NotEmpty(message = "ukeyProofs is required")
@Schema(description = "UKey 登录证明列表")
private List<UkeyLoginProof> ukeyProofs;
@NotEmpty(message = "loginFactors is required")
@JsonAlias("ukeyProofs")
@Schema(description = "UKey 登录因子列表")
private List<UkeyLoginProof> loginFactors;
public String getRoleCode() {
return roleCode;
@@ -30,19 +28,21 @@ public class UkeyLoginRequest {
this.roleCode = roleCode;
}
public String getRolePassword() {
return rolePassword;
public List<UkeyLoginProof> getLoginFactors() {
return loginFactors;
}
public void setRolePassword(String rolePassword) {
this.rolePassword = rolePassword;
public void setLoginFactors(List<UkeyLoginProof> loginFactors) {
this.loginFactors = loginFactors;
}
@Deprecated
public List<UkeyLoginProof> getUkeyProofs() {
return ukeyProofs;
return loginFactors;
}
@Deprecated
public void setUkeyProofs(List<UkeyLoginProof> ukeyProofs) {
this.ukeyProofs = ukeyProofs;
this.loginFactors = ukeyProofs;
}
}
@@ -0,0 +1,118 @@
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_full_account")
public class AuthFullAccountEntity extends BaseEntity {
private String roleCode;
private Integer uid;
private String accountName;
private String displayName;
private String passwordHash;
private String passwordSalt;
private String status;
private Boolean needChangePassword;
private Integer failedCount;
private LocalDateTime lockedUntil;
private LocalDateTime lastLoginAt;
private LocalDateTime lastActiveAt;
public String getRoleCode() {
return roleCode;
}
public void setRoleCode(String roleCode) {
this.roleCode = roleCode;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
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 Boolean getNeedChangePassword() {
return needChangePassword;
}
public void setNeedChangePassword(Boolean needChangePassword) {
this.needChangePassword = needChangePassword;
}
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;
}
}
@@ -18,7 +18,7 @@ public class AuthSessionEntity extends BaseEntity {
/**
* 本次角色会话在后端侧记录的认证账号列表。
*/
private String authenticatedUsersJson;
private String authenticatedPrincipalsJson;
/**
* 当前会话的认证方式,区分口令登录和 UKey 登录。
*/
@@ -48,12 +48,22 @@ public class AuthSessionEntity extends BaseEntity {
this.roleCode = roleCode;
}
public String getAuthenticatedUsersJson() {
return authenticatedUsersJson;
public String getAuthenticatedPrincipalsJson() {
return authenticatedPrincipalsJson;
}
public void setAuthenticatedPrincipalsJson(String authenticatedPrincipalsJson) {
this.authenticatedPrincipalsJson = authenticatedPrincipalsJson;
}
@Deprecated
public String getAuthenticatedUsersJson() {
return authenticatedPrincipalsJson;
}
@Deprecated
public void setAuthenticatedUsersJson(String authenticatedUsersJson) {
this.authenticatedUsersJson = authenticatedUsersJson;
this.authenticatedPrincipalsJson = authenticatedUsersJson;
}
public String getAuthMethod() {
@@ -13,6 +13,7 @@ public class AuthUserAccountEntity extends BaseEntity {
private String passwordHash;
private String passwordSalt;
private String status;
private Boolean needChangePassword;
private Integer failedCount;
private LocalDateTime lockedUntil;
private LocalDateTime lastLoginAt;
@@ -66,6 +67,14 @@ public class AuthUserAccountEntity extends BaseEntity {
this.status = status;
}
public Boolean getNeedChangePassword() {
return needChangePassword;
}
public void setNeedChangePassword(Boolean needChangePassword) {
this.needChangePassword = needChangePassword;
}
public Integer getFailedCount() {
return failedCount;
}
@@ -2,22 +2,13 @@ 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_role_account")
public class RoleAccountEntity extends BaseEntity {
private String roleCode;
private String displayName;
private Integer requiredUkeyCount;
private String passwordHash;
private String passwordSalt;
private String status;
private Boolean needChangePassword;
private Integer failedCount;
private LocalDateTime lockedUntil;
private LocalDateTime lastLoginAt;
private LocalDateTime lastActiveAt;
public String getRoleCode() {
return roleCode;
@@ -43,22 +34,6 @@ public class RoleAccountEntity extends BaseEntity {
this.requiredUkeyCount = requiredUkeyCount;
}
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;
}
@@ -66,44 +41,4 @@ public class RoleAccountEntity extends BaseEntity {
public void setStatus(String status) {
this.status = status;
}
public Boolean getNeedChangePassword() {
return needChangePassword;
}
public void setNeedChangePassword(Boolean needChangePassword) {
this.needChangePassword = needChangePassword;
}
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;
}
}
@@ -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.AuthFullAccountEntity;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface AuthFullAccountMapper extends BaseMapperX<AuthFullAccountEntity> {
AuthFullAccountEntity selectByRoleCodeAndUid(@Param("roleCode") String roleCode, @Param("uid") Integer uid);
List<AuthFullAccountEntity> selectByRoleCode(@Param("roleCode") String roleCode);
}
@@ -2,6 +2,8 @@ package com.cisd.tms.modules.auth.mapper;
import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX;
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
import java.time.LocalDateTime;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -9,4 +11,17 @@ import org.apache.ibatis.annotations.Param;
public interface AuthSessionMapper extends BaseMapperX<AuthSessionEntity> {
AuthSessionEntity selectBySessionToken(@Param("sessionToken") String sessionToken);
List<AuthSessionEntity> selectActiveByRoleCodeAndAuthLevel(
@Param("roleCode") String roleCode,
@Param("authLevel") String authLevel,
@Param("now") LocalDateTime now
);
List<AuthSessionEntity> selectActiveByRoleCodeAndAuthLevelAndAuthenticatedPrincipalsJson(
@Param("roleCode") String roleCode,
@Param("authLevel") String authLevel,
@Param("authenticatedPrincipalsJson") String authenticatedPrincipalsJson,
@Param("now") LocalDateTime now
);
}
@@ -0,0 +1,16 @@
package com.cisd.tms.modules.auth.repository;
import com.cisd.tms.modules.auth.entity.AuthFullAccountEntity;
import java.util.List;
import java.util.Optional;
public interface AuthFullAccountRepository {
Optional<AuthFullAccountEntity> findByRoleCodeAndUid(String roleCode, Integer uid);
List<AuthFullAccountEntity> findByRoleCode(String roleCode);
void save(AuthFullAccountEntity entity);
void update(AuthFullAccountEntity entity);
}
@@ -1,12 +1,23 @@
package com.cisd.tms.modules.auth.repository;
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
public interface AuthSessionRepository {
Optional<AuthSessionEntity> findBySessionToken(String sessionToken);
List<AuthSessionEntity> findActiveByRoleCodeAndAuthLevel(String roleCode, String authLevel, LocalDateTime now);
List<AuthSessionEntity> findActiveByRoleCodeAndAuthLevelAndAuthenticatedPrincipalsJson(
String roleCode,
String authLevel,
String authenticatedPrincipalsJson,
LocalDateTime now
);
void save(AuthSessionEntity entity);
void update(AuthSessionEntity entity);
@@ -0,0 +1,38 @@
package com.cisd.tms.modules.auth.repository.impl;
import com.cisd.tms.modules.auth.entity.AuthFullAccountEntity;
import com.cisd.tms.modules.auth.mapper.AuthFullAccountMapper;
import com.cisd.tms.modules.auth.repository.AuthFullAccountRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Repository;
@Repository
public class AuthFullAccountRepositoryImpl implements AuthFullAccountRepository {
private final AuthFullAccountMapper authFullAccountMapper;
public AuthFullAccountRepositoryImpl(AuthFullAccountMapper authFullAccountMapper) {
this.authFullAccountMapper = authFullAccountMapper;
}
@Override
public Optional<AuthFullAccountEntity> findByRoleCodeAndUid(String roleCode, Integer uid) {
return Optional.ofNullable(authFullAccountMapper.selectByRoleCodeAndUid(roleCode, uid));
}
@Override
public List<AuthFullAccountEntity> findByRoleCode(String roleCode) {
return authFullAccountMapper.selectByRoleCode(roleCode);
}
@Override
public void save(AuthFullAccountEntity entity) {
authFullAccountMapper.insert(entity);
}
@Override
public void update(AuthFullAccountEntity entity) {
authFullAccountMapper.updateById(entity);
}
}
@@ -3,6 +3,8 @@ package com.cisd.tms.modules.auth.repository.impl;
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
import com.cisd.tms.modules.auth.mapper.AuthSessionMapper;
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Repository;
@@ -20,6 +22,26 @@ public class AuthSessionRepositoryImpl implements AuthSessionRepository {
return Optional.ofNullable(authSessionMapper.selectBySessionToken(sessionToken));
}
@Override
public List<AuthSessionEntity> findActiveByRoleCodeAndAuthLevel(String roleCode, String authLevel, LocalDateTime now) {
return authSessionMapper.selectActiveByRoleCodeAndAuthLevel(roleCode, authLevel, now);
}
@Override
public List<AuthSessionEntity> findActiveByRoleCodeAndAuthLevelAndAuthenticatedPrincipalsJson(
String roleCode,
String authLevel,
String authenticatedPrincipalsJson,
LocalDateTime now
) {
return authSessionMapper.selectActiveByRoleCodeAndAuthLevelAndAuthenticatedPrincipalsJson(
roleCode,
authLevel,
authenticatedPrincipalsJson,
now
);
}
@Override
public void save(AuthSessionEntity entity) {
authSessionMapper.insert(entity);
@@ -27,5 +27,7 @@ public interface AuthService {
void logout(String sessionToken);
void changePassword(String sessionToken, String currentPassword, String newPassword);
void changeFullAccountPassword(String sessionToken, Integer uid, String currentPassword, String newPassword);
void changeLimitedAccountPassword(String sessionToken, String username, String currentPassword, String newPassword);
}
@@ -3,11 +3,13 @@ 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.AuthFullAccountEntity;
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.RoleAccountStatus;
import com.cisd.tms.modules.auth.enums.RoleCode;
import com.cisd.tms.modules.auth.repository.AuthFullAccountRepository;
import com.cisd.tms.modules.auth.repository.AuthUserAccountRepository;
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
@@ -36,6 +38,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
static final String DEFAULT_PASSWORD = "12345678";
private final RoleAccountRepository roleAccountRepository;
private final AuthFullAccountRepository authFullAccountRepository;
private final AuthUserAccountRepository authUserAccountRepository;
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
private final PasswordHasher passwordHasher;
@@ -48,24 +51,60 @@ public class AuthAdminServiceImpl implements AuthAdminService {
public void enableRole(String operatorRoleCode, String operatorAuthLevel, String targetRoleCode) {
RoleAccountEntity target = loadRole(targetRoleCode);
target.setStatus(RoleAccountStatus.ACTIVE.name());
target.setNeedChangePassword(Boolean.TRUE);
target.setLockedUntil(null);
target.setFailedCount(0);
roleAccountRepository.update(target);
List<AuthFullAccountEntity> fullAccounts = authFullAccountRepository.findByRoleCode(targetRoleCode);
if (fullAccounts.isEmpty()) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role full accounts not found");
}
for (AuthFullAccountEntity account : fullAccounts) {
account.setStatus(RoleAccountStatus.ACTIVE.name());
account.setNeedChangePassword(Boolean.TRUE);
account.setFailedCount(0);
account.setLockedUntil(null);
account.setLastActiveAt(null);
account.setLastLoginAt(null);
authFullAccountRepository.update(account);
}
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) {
account.setStatus(RoleAccountStatus.ACTIVE.name());
account.setNeedChangePassword(Boolean.TRUE);
account.setFailedCount(0);
account.setLockedUntil(null);
account.setLastActiveAt(null);
account.setLastLoginAt(null);
authUserAccountRepository.update(account);
}
}
@Override
public void resetPassword(String operatorRoleCode, String operatorAuthLevel, String targetRoleCode) {
RoleAccountEntity target = loadRole(targetRoleCode);
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<AuthFullAccountEntity> fullAccounts = authFullAccountRepository.findByRoleCode(targetRoleCode);
if (fullAccounts.isEmpty()) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role full accounts not found");
}
for (AuthFullAccountEntity account : fullAccounts) {
String newSalt = passwordSaltGenerator.nextSalt();
account.setPasswordSalt(newSalt);
account.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD, newSalt));
account.setStatus(RoleAccountStatus.ACTIVE.name());
account.setNeedChangePassword(Boolean.TRUE);
account.setFailedCount(0);
account.setLockedUntil(null);
account.setLastActiveAt(null);
account.setLastLoginAt(null);
authFullAccountRepository.update(account);
}
List<AuthUserAccountEntity> accounts = authUserAccountRepository.findByRoleCode(targetRoleCode);
if (accounts.isEmpty()) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role user accounts not found");
@@ -75,6 +114,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
account.setPasswordSalt(newSalt);
account.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD, newSalt));
account.setStatus(RoleAccountStatus.ACTIVE.name());
account.setNeedChangePassword(Boolean.TRUE);
account.setFailedCount(0);
account.setLockedUntil(null);
account.setLastActiveAt(null);
@@ -97,6 +137,8 @@ public class AuthAdminServiceImpl implements AuthAdminService {
if (uid == null || uid < 1 || uid > targetRole.getRequiredUkeyCount()) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "uid exceeds role ukey requirement");
}
authFullAccountRepository.findByRoleCodeAndUid(targetRoleCode, uid)
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role full account not found"));
RoleUkeyBindingEntity binding = roleUkeyBindingRepository
.findActiveByRoleCodeAndUid(targetRoleCode, uid)
@@ -2,8 +2,10 @@ 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.modules.auth.dto.AuthSessionPrincipal;
import com.cisd.tms.modules.auth.dto.CaptchaResponse;
import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
import com.cisd.tms.modules.auth.dto.FullLoginAccountRequest;
import com.cisd.tms.modules.auth.dto.LoginRequest;
import com.cisd.tms.modules.auth.dto.LoginResponse;
import com.cisd.tms.modules.auth.dto.PasswordLoginAccountRequest;
@@ -12,6 +14,7 @@ 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.AuthFullAccountEntity;
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
import com.cisd.tms.modules.auth.entity.AuthUserAccountEntity;
import com.cisd.tms.modules.auth.entity.AuthUserEntity;
@@ -21,6 +24,7 @@ import com.cisd.tms.modules.auth.enums.AuthLevel;
import com.cisd.tms.modules.auth.enums.AuthMethod;
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
import com.cisd.tms.modules.auth.enums.RoleCode;
import com.cisd.tms.modules.auth.repository.AuthFullAccountRepository;
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
import com.cisd.tms.modules.auth.repository.AuthUserAccountRepository;
import com.cisd.tms.modules.auth.repository.AuthUserRepository;
@@ -39,6 +43,8 @@ import com.cisd.tms.modules.mk.dto.UKeySignEntity;
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
import com.cisd.tms.modules.mk.service.LmkService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Clock;
import java.time.LocalDateTime;
@@ -63,6 +69,7 @@ public class AuthServiceImpl implements AuthService {
private final RoleAccountRepository roleAccountRepository;
private final AuthUserRepository authUserRepository;
private final AuthUserAccountRepository authUserAccountRepository;
private final AuthFullAccountRepository authFullAccountRepository;
private final AuthSessionRepository authSessionRepository;
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
private final PasswordHasher passwordHasher;
@@ -84,19 +91,24 @@ public class AuthServiceImpl implements AuthService {
validateRoleStatus(roleAccount);
AuthMethod authMethod = resolveAuthMethod(request.getUkeySerials());
List<AuthUserAccountEntity> validatedAccounts = new ArrayList<>();
List<AuthFullAccountEntity> validatedFullAccounts = new ArrayList<>();
if (AuthMethod.PASSWORD == authMethod) {
validatedAccounts = validateRoleAccounts(roleAccount, request.getAccounts());
resetValidatedAccounts(validatedAccounts);
} else {
validateRolePassword(roleAccount, request.getRolePassword());
resetRoleFailureState(roleAccount);
validatedFullAccounts = validateFullAccounts(roleAccount, request.getFullAccounts());
resetValidatedFullAccounts(validatedFullAccounts);
}
AuthLevel authLevel = resolveAuthLevel(roleAccount, request.getUkeySerials());
List<AuthSessionPrincipal> authenticatedPrincipals = AuthMethod.PASSWORD == authMethod
? normalizeAuthenticatedUsers(validatedAccounts)
: normalizeAuthenticatedFullAccounts(validatedFullAccounts);
expireConcurrentSessions(roleAccount.getRoleCode(), authLevel, authenticatedPrincipals);
AuthSessionEntity session = buildSession(
roleAccount.getRoleCode(),
authMethod,
authLevel,
validatedAccounts.isEmpty() ? null : validatedAccounts.stream().map(AuthUserAccountEntity::getUsername).toList()
authenticatedPrincipals
);
authSessionRepository.save(session);
@@ -105,14 +117,18 @@ public class AuthServiceImpl implements AuthService {
response.setAuthLevel(authLevel.name());
response.setToken(session.getSessionToken());
response.setExpiresAt(OffsetDateTime.of(session.getExpiresAt(), ZoneOffset.UTC).toString());
response.setNeedChangePassword(needsPasswordChange(roleAccount));
response.setNeedChangePassword(
AuthMethod.PASSWORD == authMethod
? needsPasswordChangeForLimitedAccounts(validatedAccounts)
: needsPasswordChangeForFullAccounts(validatedFullAccounts)
);
return response;
}
@Override
public LoginResponse passwordLogin(PasswordLoginRequest request) {
ensureMasterKeyReady();
captchaService.verify(request.getCaptchaId(), request.getCaptchaCode());
// captchaService.verify(request.getCaptchaId(), request.getCaptchaCode());
LoginRequest loginRequest = new LoginRequest();
loginRequest.setRoleCode(request.getRoleCode());
loginRequest.setAccounts(request.getAccounts());
@@ -133,22 +149,22 @@ public class AuthServiceImpl implements AuthService {
ensureMasterKeyReady();
RoleCode roleCode = RoleCode.valueOf(request.getRoleCode());
List<RoleUkeyBindingEntity> activeBindings = roleUkeyBindingRepository.findActiveByRoleCode(roleCode.getCode());
validateUkeyCount(roleCode, activeBindings, request.getUkeyProofs());
validateUkeyCount(roleCode, activeBindings, request.getLoginFactors());
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()
Set<Integer> requestUids = request.getLoginFactors().stream()
.map(UkeyLoginProof::getUid)
.collect(Collectors.toSet());
if (requestUids.size() != request.getUkeyProofs().size() || !bindingsByUid.keySet().containsAll(requestUids)) {
if (requestUids.size() != request.getLoginFactors().size() || !bindingsByUid.keySet().containsAll(requestUids)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey auth info does not match bound role");
}
ukeyLoginRandomService.assertIssued(
roleCode.getCode(),
request.getUkeyProofs().stream().map(UkeyLoginProof::getServerRandom).toList()
request.getLoginFactors().stream().map(UkeyLoginProof::getServerRandom).toList()
);
String authKeyPair = lmkService.exportIkPublicKeyHex();
List<String> matchedSerials = new ArrayList<>();
for (UkeyLoginProof proof : request.getUkeyProofs()) {
for (UkeyLoginProof proof : request.getLoginFactors()) {
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");
@@ -159,8 +175,8 @@ public class AuthServiceImpl implements AuthService {
}
LoginRequest loginRequest = new LoginRequest();
loginRequest.setRoleCode(request.getRoleCode());
loginRequest.setRolePassword(request.getRolePassword());
loginRequest.setUkeySerials(matchedSerials);
loginRequest.setFullAccounts(buildFixedUidAccounts(request.getRoleCode(), request.getLoginFactors()));
return login(loginRequest);
}
@@ -186,8 +202,10 @@ public class AuthServiceImpl implements AuthService {
AuthSessionEntity session = sessionToken == null ? null : authSessionRepository.findBySessionToken(sessionToken).orElse(null);
if (session != null) {
response.setAuthLevel(session.getAuthLevel());
response.setNeedChangePassword(needsPasswordChange(session));
} else {
response.setNeedChangePassword(Boolean.FALSE);
}
response.setNeedChangePassword(needsPasswordChange(roleAccount));
return response;
}
@@ -217,25 +235,66 @@ public class AuthServiceImpl implements AuthService {
}
@Override
public void changePassword(String sessionToken, String currentPassword, String newPassword) {
public void changeFullAccountPassword(String sessionToken, Integer uid, String currentPassword, String newPassword) {
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())) {
onRolePasswordFailed(roleAccount);
if (!AuthLevel.FULL.name().equals(session.getAuthLevel())) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full session is required");
}
if (uid == null || !containsAuthenticatedFullUid(session, uid)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account is not authenticated in current session");
}
AuthFullAccountEntity fullAccount = authFullAccountRepository.findByRoleCodeAndUid(session.getRoleCode(), uid)
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account not found"));
validateFullAccountStatus(fullAccount);
if (!passwordHasher.matches(currentPassword, fullAccount.getPasswordSalt(), fullAccount.getPasswordHash())) {
onFullPasswordFailed(fullAccount);
}
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);
fullAccount.setPasswordSalt(salt);
fullAccount.setPasswordHash(passwordHasher.hash(newPassword, salt));
fullAccount.setFailedCount(0);
fullAccount.setLockedUntil(null);
fullAccount.setStatus(RoleAccountStatus.ACTIVE.name());
fullAccount.setNeedChangePassword(Boolean.FALSE);
fullAccount.setLastLoginAt(current);
fullAccount.setLastActiveAt(current);
authFullAccountRepository.update(fullAccount);
session.setLastActiveAt(current);
session.setExpiresAt(current.plusMinutes(IDLE_TIMEOUT_MINUTES));
authSessionRepository.update(session);
}
@Override
public void changeLimitedAccountPassword(String sessionToken, String username, String currentPassword, String newPassword) {
AuthSessionEntity session = requireActiveSession(sessionToken);
if (!AuthLevel.LIMITED.name().equals(session.getAuthLevel())) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "limited session is required");
}
if (username == null || username.isBlank() || !containsAuthenticatedLimitedUsername(session, username)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "limited account is not authenticated in current session");
}
AuthUserAccountEntity userAccount = authUserAccountRepository.findByUsername(username)
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "user account not found"));
validateUserAccountRole(session.getRoleCode(), userAccount);
validateUserAccountStatus(userAccount);
if (!passwordHasher.matches(currentPassword, userAccount.getPasswordSalt(), userAccount.getPasswordHash())) {
onPasswordFailed(userAccount);
}
String salt = passwordSaltGenerator.nextSalt();
LocalDateTime current = now();
userAccount.setPasswordSalt(salt);
userAccount.setPasswordHash(passwordHasher.hash(newPassword, salt));
userAccount.setFailedCount(0);
userAccount.setLockedUntil(null);
userAccount.setStatus(RoleAccountStatus.ACTIVE.name());
userAccount.setNeedChangePassword(Boolean.FALSE);
userAccount.setLastLoginAt(current);
userAccount.setLastActiveAt(current);
authUserAccountRepository.update(userAccount);
session.setLastActiveAt(current);
session.setExpiresAt(current.plusMinutes(IDLE_TIMEOUT_MINUTES));
@@ -243,22 +302,9 @@ public class AuthServiceImpl implements AuthService {
}
private void validateRoleStatus(RoleAccountEntity roleAccount) {
if (RoleAccountStatus.UNENABLED.name().equals(roleAccount.getStatus())) {
if (!RoleAccountStatus.ACTIVE.name().equals(roleAccount.getStatus())) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role is not enabled");
}
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 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 List<AuthUserAccountEntity> validateRoleAccounts(
@@ -321,17 +367,27 @@ public class AuthServiceImpl implements AuthService {
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);
if (failedCount >= MAX_FAILED_ATTEMPTS) {
roleAccount.setStatus(RoleAccountStatus.LOCKED.name());
roleAccount.setLockedUntil(now().plusMinutes(IDLE_TIMEOUT_MINUTES));
roleAccountRepository.update(roleAccount);
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account is locked");
private void validateFullAccountStatus(AuthFullAccountEntity fullAccount) {
if (RoleAccountStatus.UNENABLED.name().equals(fullAccount.getStatus())) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account is not enabled");
}
roleAccountRepository.update(roleAccount);
if (RoleAccountStatus.LOCKED.name().equals(fullAccount.getStatus())
&& (fullAccount.getLockedUntil() == null || fullAccount.getLockedUntil().isAfter(now()))) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account is locked");
}
}
private void onFullPasswordFailed(AuthFullAccountEntity fullAccount) {
int failedCount = fullAccount.getFailedCount() == null ? 0 : fullAccount.getFailedCount();
failedCount++;
fullAccount.setFailedCount(failedCount);
if (failedCount >= MAX_FAILED_ATTEMPTS) {
fullAccount.setStatus(RoleAccountStatus.LOCKED.name());
fullAccount.setLockedUntil(now().plusMinutes(IDLE_TIMEOUT_MINUTES));
authFullAccountRepository.update(fullAccount);
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account is locked");
}
authFullAccountRepository.update(fullAccount);
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "password is incorrect");
}
@@ -347,19 +403,77 @@ public class AuthServiceImpl implements AuthService {
}
}
private void resetRoleFailureState(RoleAccountEntity roleAccount) {
roleAccount.setFailedCount(0);
roleAccount.setLockedUntil(null);
roleAccount.setStatus(RoleAccountStatus.ACTIVE.name());
roleAccount.setLastLoginAt(now());
roleAccount.setLastActiveAt(now());
roleAccountRepository.update(roleAccount);
private void resetValidatedFullAccounts(List<AuthFullAccountEntity> validatedAccounts) {
LocalDateTime current = now();
for (AuthFullAccountEntity account : validatedAccounts) {
account.setFailedCount(0);
account.setLockedUntil(null);
account.setStatus(RoleAccountStatus.ACTIVE.name());
account.setLastLoginAt(current);
account.setLastActiveAt(current);
authFullAccountRepository.update(account);
}
}
private int requiredPasswordAccountCount(String roleCode) {
return RoleCode.valueOf(roleCode).getRequiredUkeyCount();
}
private List<AuthFullAccountEntity> validateFullAccounts(
RoleAccountEntity roleAccount,
List<FullLoginAccountRequest> fullAccounts
) {
int expectedCount = authPolicyService.requiredUkeyCount(RoleCode.valueOf(roleAccount.getRoleCode()));
if (fullAccounts == null || fullAccounts.size() != expectedCount) {
throw new BizException(
ErrorCode.UNAUTHORIZED.getCode(),
roleAccount.getRoleCode() + " requires exactly " + expectedCount + " full-account passwords"
);
}
Set<Integer> uniqueUids = new LinkedHashSet<>();
List<AuthFullAccountEntity> validatedAccounts = new ArrayList<>();
for (FullLoginAccountRequest account : fullAccounts) {
Integer uid = account.getUid();
if (uid == null || !uniqueUids.add(uid)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "duplicate full-account uid submitted");
}
AuthFullAccountEntity fullAccount = authFullAccountRepository
.findByRoleCodeAndUid(roleAccount.getRoleCode(), uid)
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account not found"));
validateFullAccountStatus(fullAccount);
if (!passwordHasher.matches(account.getPassword(), fullAccount.getPasswordSalt(), fullAccount.getPasswordHash())) {
onFullPasswordFailed(fullAccount);
}
validatedAccounts.add(fullAccount);
}
return validatedAccounts;
}
private List<FullLoginAccountRequest> buildFixedUidAccounts(String roleCode, List<UkeyLoginProof> proofs) {
List<AuthFullAccountEntity> fixedAccounts = authFullAccountRepository.findByRoleCode(roleCode);
int requiredCount = authPolicyService.requiredUkeyCount(RoleCode.valueOf(roleCode));
if (fixedAccounts.size() < requiredCount) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role fixed account mapping is incomplete");
}
List<FullLoginAccountRequest> requests = new ArrayList<>();
for (UkeyLoginProof proof : proofs.stream().sorted(java.util.Comparator.comparing(UkeyLoginProof::getUid)).toList()) {
Integer uid = proof.getUid();
if (uid == null || uid < 1 || uid > fixedAccounts.size()) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "uid does not match fixed role account");
}
if (proof.getPassword() == null || proof.getPassword().isBlank()) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "account password is required");
}
FullLoginAccountRequest accountRequest = new FullLoginAccountRequest();
accountRequest.setUid(uid);
accountRequest.setPassword(proof.getPassword());
requests.add(accountRequest);
}
return requests;
}
private void ensureMasterKeyReady() {
MasterKeyStatus.StatusDetail status = lmkService.getMasterKeyStatus();
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
@@ -425,12 +539,12 @@ public class AuthServiceImpl implements AuthService {
String roleCode,
AuthMethod authMethod,
AuthLevel authLevel,
List<String> authenticatedUsers
List<AuthSessionPrincipal> authenticatedPrincipals
) {
LocalDateTime issuedAt = now();
AuthSessionEntity entity = new AuthSessionEntity();
entity.setRoleCode(roleCode);
entity.setAuthenticatedUsersJson(writeAuthenticatedUsers(authenticatedUsers));
entity.setAuthenticatedPrincipalsJson(writeAuthenticatedPrincipals(authenticatedPrincipals));
entity.setAuthMethod(authMethod.name());
entity.setAuthLevel(authLevel.name());
entity.setSessionToken(sessionTokenGenerator.nextToken());
@@ -440,23 +554,132 @@ public class AuthServiceImpl implements AuthService {
return entity;
}
private String writeAuthenticatedUsers(List<String> authenticatedUsers) {
if (authenticatedUsers == null) {
private void expireConcurrentSessions(String roleCode, AuthLevel authLevel, List<AuthSessionPrincipal> authenticatedPrincipals) {
LocalDateTime current = now();
List<AuthSessionEntity> sessionsToExpire;
if (AuthLevel.FULL == authLevel) {
sessionsToExpire = authSessionRepository.findActiveByRoleCodeAndAuthLevel(roleCode, authLevel.name(), current);
} else {
sessionsToExpire = authSessionRepository.findActiveByRoleCodeAndAuthLevelAndAuthenticatedPrincipalsJson(
roleCode,
authLevel.name(),
writeAuthenticatedPrincipals(authenticatedPrincipals),
current
);
}
for (AuthSessionEntity session : sessionsToExpire) {
session.setLogoutAt(current);
session.setExpiresAt(current);
authSessionRepository.update(session);
}
}
private List<AuthSessionPrincipal> normalizeAuthenticatedUsers(List<AuthUserAccountEntity> validatedAccounts) {
if (validatedAccounts == null || validatedAccounts.isEmpty()) {
return null;
}
return validatedAccounts.stream()
.map(AuthUserAccountEntity::getUsername)
.sorted()
.map(AuthSessionPrincipal::limited)
.toList();
}
private List<AuthSessionPrincipal> normalizeAuthenticatedFullAccounts(List<AuthFullAccountEntity> validatedAccounts) {
if (validatedAccounts == null || validatedAccounts.isEmpty()) {
return null;
}
return validatedAccounts.stream()
.map(AuthFullAccountEntity::getUid)
.sorted()
.map(AuthSessionPrincipal::full)
.toList();
}
private String writeAuthenticatedPrincipals(List<AuthSessionPrincipal> authenticatedPrincipals) {
if (authenticatedPrincipals == null) {
return null;
}
try {
return objectMapper.writeValueAsString(authenticatedUsers);
return objectMapper.writeValueAsString(authenticatedPrincipals);
} catch (JsonProcessingException ex) {
throw new IllegalStateException("serialize authenticated users failed", ex);
throw new IllegalStateException("serialize authenticated principals failed", ex);
}
}
private List<AuthSessionPrincipal> authenticatedPrincipals(AuthSessionEntity session) {
if (session.getAuthenticatedPrincipalsJson() == null || session.getAuthenticatedPrincipalsJson().isBlank()) {
return List.of();
}
try {
JsonNode root = objectMapper.readTree(session.getAuthenticatedPrincipalsJson());
if (!root.isArray() || root.isEmpty()) {
return List.of();
}
if (root.get(0).isTextual()) {
List<AuthSessionPrincipal> principals = new ArrayList<>();
for (JsonNode node : root) {
String value = node.asText();
if (value.startsWith("FULL:")) {
principals.add(AuthSessionPrincipal.full(Integer.parseInt(value.substring("FULL:".length()))));
} else {
principals.add(AuthSessionPrincipal.limited(value));
}
}
return principals;
}
return objectMapper.readValue(
session.getAuthenticatedPrincipalsJson(),
new TypeReference<List<AuthSessionPrincipal>>() { }
);
} catch (JsonProcessingException ex) {
throw new IllegalStateException("deserialize authenticated principals failed", ex);
}
}
private boolean containsAuthenticatedFullUid(AuthSessionEntity session, Integer uid) {
return authenticatedPrincipals(session).stream()
.anyMatch(principal -> "FULL".equals(principal.getType()) && uid.equals(principal.getUid()));
}
private boolean containsAuthenticatedLimitedUsername(AuthSessionEntity session, String username) {
return authenticatedPrincipals(session).stream()
.anyMatch(principal -> "LIMITED".equals(principal.getType()) && username.equals(principal.getUsername()));
}
private LocalDateTime now() {
return LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC);
}
private boolean needsPasswordChange(RoleAccountEntity roleAccount) {
return Boolean.TRUE.equals(roleAccount.getNeedChangePassword());
private boolean needsPasswordChangeForLimitedAccounts(List<AuthUserAccountEntity> accounts) {
return accounts != null && accounts.stream().anyMatch(account -> Boolean.TRUE.equals(account.getNeedChangePassword()));
}
private boolean needsPasswordChangeForFullAccounts(List<AuthFullAccountEntity> accounts) {
return accounts != null && accounts.stream().anyMatch(account -> Boolean.TRUE.equals(account.getNeedChangePassword()));
}
private boolean needsPasswordChange(AuthSessionEntity session) {
if (session == null) {
return false;
}
List<AuthSessionPrincipal> principals = authenticatedPrincipals(session);
if (principals.isEmpty()) {
return false;
}
if (AuthLevel.FULL.name().equals(session.getAuthLevel())) {
List<AuthFullAccountEntity> accounts = authFullAccountRepository.findByRoleCode(session.getRoleCode());
return accounts.stream()
.filter(account -> principals.stream()
.anyMatch(principal -> "FULL".equals(principal.getType()) && account.getUid().equals(principal.getUid())))
.anyMatch(account -> Boolean.TRUE.equals(account.getNeedChangePassword()));
}
List<AuthUserAccountEntity> accounts = authUserAccountRepository.findByRoleCode(session.getRoleCode());
return accounts.stream()
.filter(account -> principals.stream()
.anyMatch(principal -> "LIMITED".equals(principal.getType())
&& account.getUsername().equals(principal.getUsername())))
.anyMatch(account -> Boolean.TRUE.equals(account.getNeedChangePassword()));
}
private AuthSessionEntity requireActiveSession(String sessionToken) {
@@ -2,25 +2,52 @@ 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.integration.crypto.pcie.Gm0018AlgorithmIds;
import com.cisd.tms.integration.crypto.pcie.model.EccExternalVerifyRequest;
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService;
import com.cisd.tms.modules.auth.service.CompatUkeyVerifier;
import com.cisd.tms.modules.mk.service.LmkService;
import java.io.StringReader;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Component;
@Component
public class PcieCompatUkeyVerifier implements CompatUkeyVerifier {
private final LmkService lmkService;
private final PcieCryptoService pcieCryptoService;
private static final String PROVIDER = "BC";
private static final String SIGNATURE_ALGORITHM = "SM3withSM2";
private static final String SM2_CURVE = "sm2p256v1";
public PcieCompatUkeyVerifier(LmkService lmkService, PcieCryptoService pcieCryptoService) {
static {
if (Security.getProvider(PROVIDER) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
private final LmkService lmkService;
public PcieCompatUkeyVerifier(LmkService lmkService) {
this.lmkService = lmkService;
this.pcieCryptoService = pcieCryptoService;
}
@Override
@@ -36,32 +63,133 @@ public class PcieCompatUkeyVerifier implements CompatUkeyVerifier {
@Override
public void verifyLoginSignature(String pubKey, String loginSignData, String loginSign) {
EccExternalVerifyRequest request = new EccExternalVerifyRequest();
request.setAlgId(Gm0018AlgorithmIds.SM2_SIGN);
request.setPublicKeyBlob(decodeBlob(pubKey, "login public key is invalid"));
request.setData(loginSignData.getBytes(StandardCharsets.UTF_8));
request.setSignature(decodeBlob(loginSign, "login signature is invalid"));
try {
pcieCryptoService.eccExternalVerify(request);
PublicKey publicKey = parsePublicKey(pubKey);
byte[] signatureBytes = decodeSignature(loginSign);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, PROVIDER);
signature.initVerify(publicKey);
signature.update(loginSignData.getBytes(StandardCharsets.UTF_8));
if (!signature.verify(signatureBytes)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login signature verification failed");
}
} catch (BizException ex) {
throw ex;
} catch (IllegalArgumentException ex) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), ex.getMessage());
} catch (RuntimeException ex) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login signature verification failed");
} catch (Exception ex) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login signature verification failed");
}
}
private byte[] decodeBlob(String value, String message) {
String normalized = value == null ? "" : value.trim();
if (normalized.isEmpty()) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), message);
private PublicKey parsePublicKey(String value) throws Exception {
String trimmed = value == null ? "" : value.trim();
if (trimmed.isEmpty()) {
throw new IllegalArgumentException("login public key is invalid");
}
if (normalized.matches("(?i)^[0-9a-f]+$") && normalized.length() % 2 == 0) {
return Hex.decode(normalized);
if (trimmed.contains("BEGIN")) {
return parsePemPublicKey(trimmed);
}
String normalized = normalize(trimmed);
byte[] decoded = decodeHexOrBase64(normalized, "login public key is invalid");
try {
return Base64.getDecoder().decode(normalized);
} catch (IllegalArgumentException ex) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), message);
return KeyFactory.getInstance("EC", PROVIDER).generatePublic(new X509EncodedKeySpec(decoded));
} catch (Exception ignored) {
return parseUncompressedPointPublicKey(decoded);
}
}
private PublicKey parsePemPublicKey(String pemContent) throws Exception {
try (PEMParser parser = new PEMParser(new StringReader(pemContent))) {
Object parsed = parser.readObject();
if (!(parsed instanceof SubjectPublicKeyInfo publicKeyInfo)) {
throw new IllegalArgumentException("login public key is invalid");
}
return new JcaPEMKeyConverter().setProvider(PROVIDER).getPublicKey(publicKeyInfo);
}
}
private PublicKey parseUncompressedPointPublicKey(byte[] keyBytes) throws Exception {
byte[] pointBytes = keyBytes;
if (pointBytes.length == 64) {
pointBytes = new byte[65];
pointBytes[0] = 0x04;
System.arraycopy(keyBytes, 0, pointBytes, 1, keyBytes.length);
}
if (pointBytes.length != 65 || pointBytes[0] != 0x04) {
throw new IllegalArgumentException("login public key is invalid");
}
ECParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec(SM2_CURVE);
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(
parameterSpec.getCurve().decodePoint(pointBytes),
parameterSpec
);
return KeyFactory.getInstance("EC", PROVIDER).generatePublic(publicKeySpec);
}
private byte[] decodeSignature(String value) {
byte[] decoded = decodeHexOrBase64(normalize(value), "login signature is invalid");
if (isDerSignature(decoded)) {
return decoded;
}
if (decoded.length == 64) {
return rawRsToDer(decoded);
}
throw new IllegalArgumentException("login signature is invalid");
}
private boolean isDerSignature(byte[] signatureBytes) {
try (ASN1InputStream inputStream = new ASN1InputStream(signatureBytes)) {
ASN1Primitive primitive = inputStream.readObject();
if (!(primitive instanceof ASN1Sequence sequence) || sequence.size() != 2) {
return false;
}
return sequence.getObjectAt(0) instanceof ASN1Integer && sequence.getObjectAt(1) instanceof ASN1Integer;
} catch (Exception ex) {
return false;
}
}
private byte[] rawRsToDer(byte[] rawSignature) {
byte[] r = Arrays.copyOfRange(rawSignature, 0, 32);
byte[] s = Arrays.copyOfRange(rawSignature, 32, 64);
ASN1EncodableVector vector = new ASN1EncodableVector();
vector.add(new ASN1Integer(new BigInteger(1, r)));
vector.add(new ASN1Integer(new BigInteger(1, s)));
try {
return new DERSequence(vector).getEncoded(ASN1Encoding.DER);
} catch (Exception ex) {
throw new IllegalArgumentException("login signature is invalid", ex);
}
}
private byte[] decodeHexOrBase64(String value, String message) {
if (value.isEmpty()) {
throw new IllegalArgumentException(message);
}
if (value.matches("(?i)^[0-9a-f]+$") && value.length() % 2 == 0) {
return Hex.decode(value);
}
try {
return Base64.getDecoder().decode(value);
} catch (IllegalArgumentException ex) {
throw new IllegalArgumentException(message, ex);
}
}
private String normalize(String value) {
String normalized = value == null ? "" : value.trim();
if (!normalized.contains("BEGIN")) {
return normalized;
}
StringBuilder builder = new StringBuilder();
for (String line : normalized.split("\\R")) {
String trimmed = line.trim();
if (!trimmed.startsWith("-----")) {
builder.append(trimmed);
}
}
return builder.toString();
}
}
@@ -244,14 +244,7 @@ CREATE TABLE IF NOT EXISTS tms_role_account (
role_code VARCHAR(64) NOT NULL,
display_name VARCHAR(128) NOT NULL,
required_ukey_count INT NOT NULL,
password_hash VARCHAR(256) NOT NULL,
password_salt VARCHAR(128) NOT NULL,
status VARCHAR(32) NOT NULL,
need_change_password TINYINT(1) NOT NULL DEFAULT 1,
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_role_account_role_code (role_code)
@@ -281,6 +274,7 @@ CREATE TABLE IF NOT EXISTS tms_auth_user_account (
password_hash VARCHAR(256) NOT NULL,
password_salt VARCHAR(128) NOT NULL,
status VARCHAR(32) NOT NULL,
need_change_password TINYINT(1) NOT NULL DEFAULT 1,
failed_count INT NOT NULL DEFAULT 0,
locked_until DATETIME(3) NULL,
last_login_at DATETIME(3) NULL,
@@ -291,11 +285,32 @@ CREATE TABLE IF NOT EXISTS tms_auth_user_account (
KEY idx_tms_auth_user_account_role_code (role_code)
);
CREATE TABLE IF NOT EXISTS tms_auth_full_account (
id BIGINT PRIMARY KEY,
role_code VARCHAR(64) NOT NULL,
uid INT NOT NULL,
account_name VARCHAR(64) NOT NULL,
display_name VARCHAR(128) NOT NULL,
password_hash VARCHAR(256) NOT NULL,
password_salt VARCHAR(128) NOT NULL,
status VARCHAR(32) NOT NULL,
need_change_password TINYINT(1) NOT NULL DEFAULT 1,
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_full_account_role_uid (role_code, uid),
UNIQUE KEY uk_tms_auth_full_account_name (account_name),
KEY idx_tms_auth_full_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,
authenticated_principals_json JSON NULL,
auth_method VARCHAR(32) NOT NULL,
auth_level VARCHAR(32) NOT NULL,
issued_at DATETIME(3) NOT NULL,
@@ -379,14 +394,7 @@ INSERT IGNORE INTO tms_role_account (
role_code,
display_name,
required_ukey_count,
password_hash,
password_salt,
status,
need_change_password,
failed_count,
locked_until,
last_login_at,
last_active_at,
create_time,
update_time
)
@@ -396,14 +404,7 @@ VALUES
'SUPER_ADMIN',
'超级管理员',
2,
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
'init-super-admin-salt-20260325',
'UNENABLED',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
),
@@ -412,14 +413,7 @@ VALUES
'KEY_ADMIN',
'密钥管理员',
1,
'/xqmDy3A/q9X5p7XWznIPSBabQ6bxTLAPgtHQ6Be33c=',
'init-key-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
),
@@ -428,14 +422,7 @@ VALUES
'AUDIT_ADMIN',
'审计管理员',
1,
'j/J1LKyUOzVEJfbbHe2dwKBKNdabRk502olk/LWIwhg=',
'init-audit-admin-salt-20260325',
'UNENABLED',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
),
@@ -444,14 +431,7 @@ VALUES
'OPS_ADMIN',
'运维管理员',
1,
'2e9F4aCgsDb+AuD1LrBEqEg8yjy3ODo27UHtx06vj/A=',
'init-ops-admin-salt-20260325',
'UNENABLED',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
);
@@ -464,6 +444,7 @@ INSERT IGNORE INTO tms_auth_user_account (
password_hash,
password_salt,
status,
need_change_password,
failed_count,
locked_until,
last_login_at,
@@ -480,6 +461,7 @@ VALUES
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
'init-super-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
@@ -495,6 +477,7 @@ VALUES
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
'init-super-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
@@ -510,6 +493,7 @@ VALUES
'/xqmDy3A/q9X5p7XWznIPSBabQ6bxTLAPgtHQ6Be33c=',
'init-key-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
@@ -525,6 +509,7 @@ VALUES
'j/J1LKyUOzVEJfbbHe2dwKBKNdabRk502olk/LWIwhg=',
'init-audit-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
@@ -540,6 +525,111 @@ VALUES
'2e9F4aCgsDb+AuD1LrBEqEg8yjy3ODo27UHtx06vj/A=',
'init-ops-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
);
INSERT IGNORE INTO tms_auth_full_account (
id,
role_code,
uid,
account_name,
display_name,
password_hash,
password_salt,
status,
need_change_password,
failed_count,
locked_until,
last_login_at,
last_active_at,
create_time,
update_time
)
VALUES
(
12101,
'SUPER_ADMIN',
1,
'super-admin-full-01',
'超级管理员UKey席位一',
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
'init-super-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
),
(
12102,
'SUPER_ADMIN',
2,
'super-admin-full-02',
'超级管理员UKey席位二',
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
'init-super-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
),
(
12103,
'KEY_ADMIN',
1,
'key-admin-full-01',
'密钥管理员UKey席位',
'/xqmDy3A/q9X5p7XWznIPSBabQ6bxTLAPgtHQ6Be33c=',
'init-key-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
),
(
12104,
'AUDIT_ADMIN',
1,
'audit-admin-full-01',
'审计管理员UKey席位',
'j/J1LKyUOzVEJfbbHe2dwKBKNdabRk502olk/LWIwhg=',
'init-audit-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
),
(
12105,
'OPS_ADMIN',
1,
'ops-admin-full-01',
'运维管理员UKey席位',
'2e9F4aCgsDb+AuD1LrBEqEg8yjy3ODo27UHtx06vj/A=',
'init-ops-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
@@ -0,0 +1,67 @@
<?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.AuthFullAccountMapper">
<resultMap id="AuthFullAccountResultMap" type="com.cisd.tms.modules.auth.entity.AuthFullAccountEntity">
<id property="id" column="id"/>
<result property="roleCode" column="role_code"/>
<result property="uid" column="uid"/>
<result property="accountName" column="account_name"/>
<result property="displayName" column="display_name"/>
<result property="passwordHash" column="password_hash"/>
<result property="passwordSalt" column="password_salt"/>
<result property="status" column="status"/>
<result property="needChangePassword" column="need_change_password"/>
<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="selectByRoleCodeAndUid" resultMap="AuthFullAccountResultMap">
SELECT id,
role_code,
uid,
account_name,
display_name,
password_hash,
password_salt,
status,
need_change_password,
failed_count,
locked_until,
last_login_at,
last_active_at,
create_time,
update_time
FROM tms_auth_full_account
WHERE role_code = #{roleCode}
AND uid = #{uid}
LIMIT 1
</select>
<select id="selectByRoleCode" resultMap="AuthFullAccountResultMap">
SELECT id,
role_code,
uid,
account_name,
display_name,
password_hash,
password_salt,
status,
need_change_password,
failed_count,
locked_until,
last_login_at,
last_active_at,
create_time,
update_time
FROM tms_auth_full_account
WHERE role_code = #{roleCode}
ORDER BY uid ASC
</select>
</mapper>
@@ -8,7 +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="authenticatedPrincipalsJson" column="authenticated_principals_json"/>
<result property="authMethod" column="auth_method"/>
<result property="authLevel" column="auth_level"/>
<result property="issuedAt" column="issued_at"/>
@@ -23,7 +23,7 @@
SELECT id,
session_token,
role_code,
authenticated_users_json,
authenticated_principals_json,
auth_method,
auth_level,
issued_at,
@@ -36,4 +36,47 @@
WHERE session_token = #{sessionToken}
LIMIT 1
</select>
<select id="selectActiveByRoleCodeAndAuthLevel" resultMap="AuthSessionResultMap">
SELECT id,
session_token,
role_code,
authenticated_principals_json,
auth_method,
auth_level,
issued_at,
last_active_at,
expires_at,
logout_at,
create_time,
update_time
FROM tms_auth_session
WHERE role_code = #{roleCode}
AND auth_level = #{authLevel}
AND logout_at IS NULL
AND expires_at IS NOT NULL
AND expires_at > #{now}
</select>
<select id="selectActiveByRoleCodeAndAuthLevelAndAuthenticatedPrincipalsJson" resultMap="AuthSessionResultMap">
SELECT id,
session_token,
role_code,
authenticated_principals_json,
auth_method,
auth_level,
issued_at,
last_active_at,
expires_at,
logout_at,
create_time,
update_time
FROM tms_auth_session
WHERE role_code = #{roleCode}
AND auth_level = #{authLevel}
AND authenticated_principals_json = #{authenticatedPrincipalsJson}
AND logout_at IS NULL
AND expires_at IS NOT NULL
AND expires_at > #{now}
</select>
</mapper>
@@ -12,6 +12,7 @@
<result property="passwordHash" column="password_hash"/>
<result property="passwordSalt" column="password_salt"/>
<result property="status" column="status"/>
<result property="needChangePassword" column="need_change_password"/>
<result property="failedCount" column="failed_count"/>
<result property="lockedUntil" column="locked_until"/>
<result property="lastLoginAt" column="last_login_at"/>
@@ -28,6 +29,7 @@
password_hash,
password_salt,
status,
need_change_password,
failed_count,
locked_until,
last_login_at,
@@ -47,6 +49,7 @@
password_hash,
password_salt,
status,
need_change_password,
failed_count,
locked_until,
last_login_at,
@@ -9,14 +9,7 @@
<result property="roleCode" column="role_code"/>
<result property="displayName" column="display_name"/>
<result property="requiredUkeyCount" column="required_ukey_count"/>
<result property="passwordHash" column="password_hash"/>
<result property="passwordSalt" column="password_salt"/>
<result property="status" column="status"/>
<result property="needChangePassword" column="need_change_password"/>
<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>
@@ -26,14 +19,7 @@
role_code,
display_name,
required_ukey_count,
password_hash,
password_salt,
status,
need_change_password,
failed_count,
locked_until,
last_login_at,
last_active_at,
create_time,
update_time
FROM tms_role_account