fix:角色设计修改
This commit is contained in:
@@ -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);
|
||||
|
||||
+38
@@ -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);
|
||||
}
|
||||
}
|
||||
+22
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user