fix:登录流程改造
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
# TMS 初始化 SQL 执行说明
|
||||
|
||||
当前推荐使用 [V1__tms_schema_full.sql](/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/db/migration/V1__tms_schema_full.sql)。
|
||||
空库初始化当前推荐使用 [V2__tms_schema_full.sql](/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/db/migration/V2__tms_schema_full.sql)。
|
||||
|
||||
这份脚本已经整理成“最终库结构初始化版”,适合空库直接初始化,不再按 migration 逐步执行 `ALTER / RENAME`。
|
||||
[V1__tms_schema_full.sql](/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/db/migration/V1__tms_schema_full.sql) 仅用于旧数据库迁移:它会在已有 `tms_auth_full_account` 表上新增 `password_digest`,把固定角色席位统一重置为默认口令 `Sunyard@123`,并置为强制改密。旧库不能用 `V2` 直接覆盖执行。
|
||||
|
||||
这份脚本已经整理成“最终库结构初始化版”,适合空库直接初始化,不再按 migration 逐步执行 `ALTER / RENAME`,也不依赖后置 `UPDATE` 修补种子数据。
|
||||
|
||||
如果你在 TiDB 客户端里一次性执行整份文件,出现下面这类报错:
|
||||
|
||||
@@ -18,7 +20,7 @@ SQL Error [8130] [HY000]: client has multi-statement capability disabled
|
||||
|
||||
优先用客户端的 `Run Script` / `Execute Script` 模式执行:
|
||||
|
||||
- [V1__tms_schema_full.sql](/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/db/migration/V1__tms_schema_full.sql)
|
||||
- [V2__tms_schema_full.sql](/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/db/migration/V2__tms_schema_full.sql)
|
||||
|
||||
### 方式二:用“脚本执行”而不是“单次执行”
|
||||
|
||||
@@ -30,13 +32,13 @@ SQL Error [8130] [HY000]: client has multi-statement capability disabled
|
||||
例如:
|
||||
|
||||
```bash
|
||||
mysql -h <host> -P <port> -u <user> -p tms < /path/to/V1__tms_schema_full.sql
|
||||
mysql -h <host> -P <port> -u <user> -p tms < /path/to/V2__tms_schema_full.sql
|
||||
```
|
||||
|
||||
或者先登录后:
|
||||
|
||||
```sql
|
||||
source /path/to/V1__tms_schema_full.sql;
|
||||
source /path/to/V2__tms_schema_full.sql;
|
||||
```
|
||||
|
||||
### 方式四:实在只能单条执行时
|
||||
@@ -51,7 +53,7 @@ CREATE DATABASE IF NOT EXISTS tms
|
||||
USE tms;
|
||||
```
|
||||
|
||||
然后把 [V1__tms_schema_full.sql](/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/db/migration/V1__tms_schema_full.sql) 中的语句按分号逐条执行。
|
||||
然后把 [V2__tms_schema_full.sql](/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/db/migration/V2__tms_schema_full.sql) 中的语句按分号逐条执行。
|
||||
|
||||
## 不推荐但可选
|
||||
|
||||
@@ -72,7 +74,7 @@ SET GLOBAL tidb_multi_statement_mode='ON';
|
||||
- `AUDIT_ADMIN`
|
||||
- `OPS_ADMIN`
|
||||
|
||||
默认口令均为 `12345678`,其中:
|
||||
默认口令均为 `Sunyard@123`,其中:
|
||||
|
||||
- `KEY_ADMIN` 默认 `ACTIVE`
|
||||
- 其他角色默认 `UNENABLED`
|
||||
|
||||
@@ -24,6 +24,7 @@ public class AuthController {
|
||||
private final AuthService authService;
|
||||
|
||||
@PostMapping("/password-login")
|
||||
@Deprecated
|
||||
@Operation(summary = "口令登录", description = "按旧系统受限登录流程签发 LIMITED 会话。")
|
||||
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.LOGIN, summary = "口令登录")
|
||||
public ApiResponse<LoginResponse> passwordLogin(@Valid @RequestBody PasswordLoginRequest request) {
|
||||
@@ -79,9 +80,23 @@ public class AuthController {
|
||||
authService.changeAccountPassword(
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN),
|
||||
uid,
|
||||
request.getCurrentPassword(),
|
||||
request.getNewPassword()
|
||||
request.getChangeToken(),
|
||||
request.getOldPasswordDynamicDigest(),
|
||||
request.getNewPasswordDigest()
|
||||
);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/accounts/{uid}/change-password/prepare")
|
||||
@Operation(summary = "准备修改当前登录席位口令", description = "生成改密确认令牌、旧口令挑战随机数和新口令盐值。")
|
||||
@ReplayProtected
|
||||
public ApiResponse<PasswordChangePrepareResponse> prepareAccountPasswordChange(
|
||||
@PathVariable("uid") Integer uid,
|
||||
HttpServletRequest httpRequest
|
||||
) {
|
||||
return ApiResponse.success(authService.prepareAccountPasswordChange(
|
||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN),
|
||||
uid
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,58 @@
|
||||
package com.cisd.tms.modules.auth.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "管理员修改账号口令请求")
|
||||
public class AdminChangePasswordRequest {
|
||||
|
||||
@NotBlank(message = "oldPassword不能为空")
|
||||
@Schema(description = "旧口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||
private String oldPassword;
|
||||
@NotBlank(message = "oldPasswordDigest不能为空")
|
||||
@JsonAlias("oldPasswordDynamicDigest")
|
||||
@Schema(description = "旧口令静态摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||
private String oldPasswordDigest;
|
||||
|
||||
@NotBlank(message = "newPassword不能为空")
|
||||
@Schema(description = "新口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||
private String newPassword;
|
||||
@NotBlank(message = "newPasswordDigest不能为空")
|
||||
@Schema(description = "新口令静态摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||
private String newPasswordDigest;
|
||||
|
||||
public String getOldPassword() {
|
||||
return oldPassword;
|
||||
return oldPasswordDigest;
|
||||
}
|
||||
|
||||
public void setOldPassword(String oldPassword) {
|
||||
this.oldPassword = oldPassword;
|
||||
this.oldPasswordDigest = oldPassword;
|
||||
}
|
||||
|
||||
public String getNewPassword() {
|
||||
return newPassword;
|
||||
return newPasswordDigest;
|
||||
}
|
||||
|
||||
public void setNewPassword(String newPassword) {
|
||||
this.newPassword = newPassword;
|
||||
this.newPasswordDigest = newPassword;
|
||||
}
|
||||
|
||||
public String getOldPasswordDynamicDigest() {
|
||||
return oldPasswordDigest;
|
||||
}
|
||||
|
||||
public void setOldPasswordDynamicDigest(String oldPasswordDynamicDigest) {
|
||||
this.oldPasswordDigest = oldPasswordDynamicDigest;
|
||||
}
|
||||
|
||||
public String getOldPasswordDigest() {
|
||||
return oldPasswordDigest;
|
||||
}
|
||||
|
||||
public void setOldPasswordDigest(String oldPasswordDigest) {
|
||||
this.oldPasswordDigest = oldPasswordDigest;
|
||||
}
|
||||
|
||||
public String getNewPasswordDigest() {
|
||||
return newPasswordDigest;
|
||||
}
|
||||
|
||||
public void setNewPasswordDigest(String newPasswordDigest) {
|
||||
this.newPasswordDigest = newPasswordDigest;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,38 +7,67 @@ import jakarta.validation.constraints.NotBlank;
|
||||
@Schema(description = "修改当前认证账户口令请求")
|
||||
public class ChangePasswordRequest {
|
||||
|
||||
@NotBlank(message = "oldPassword不能为空")
|
||||
@JsonAlias("currentPassword")
|
||||
@Schema(description = "旧口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||
private String oldPassword;
|
||||
@NotBlank(message = "changeToken不能为空")
|
||||
@Schema(description = "改密准备接口返回的确认令牌")
|
||||
private String changeToken;
|
||||
|
||||
@NotBlank(message = "newPassword不能为空")
|
||||
@Schema(description = "新口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||
private String newPassword;
|
||||
@NotBlank(message = "oldPasswordDynamicDigest不能为空")
|
||||
@JsonAlias({"oldPassword", "currentPassword"})
|
||||
@Schema(description = "旧口令动态摘要:SM3(oldPasswordDigest + oldServerRandom)", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||
private String oldPasswordDynamicDigest;
|
||||
|
||||
@NotBlank(message = "newPasswordDigest不能为空")
|
||||
@JsonAlias("newPassword")
|
||||
@Schema(description = "新口令静态摘要:SM3(newPasswordSalt + newPassword)", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||
private String newPasswordDigest;
|
||||
|
||||
public String getOldPassword() {
|
||||
return oldPassword;
|
||||
return oldPasswordDynamicDigest;
|
||||
}
|
||||
|
||||
public void setOldPassword(String oldPassword) {
|
||||
this.oldPassword = oldPassword;
|
||||
this.oldPasswordDynamicDigest = oldPassword;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getCurrentPassword() {
|
||||
return oldPassword;
|
||||
return oldPasswordDynamicDigest;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setCurrentPassword(String currentPassword) {
|
||||
this.oldPassword = currentPassword;
|
||||
this.oldPasswordDynamicDigest = currentPassword;
|
||||
}
|
||||
|
||||
public String getNewPassword() {
|
||||
return newPassword;
|
||||
return newPasswordDigest;
|
||||
}
|
||||
|
||||
public void setNewPassword(String newPassword) {
|
||||
this.newPassword = newPassword;
|
||||
this.newPasswordDigest = newPassword;
|
||||
}
|
||||
|
||||
public String getChangeToken() {
|
||||
return changeToken;
|
||||
}
|
||||
|
||||
public void setChangeToken(String changeToken) {
|
||||
this.changeToken = changeToken;
|
||||
}
|
||||
|
||||
public String getOldPasswordDynamicDigest() {
|
||||
return oldPasswordDynamicDigest;
|
||||
}
|
||||
|
||||
public void setOldPasswordDynamicDigest(String oldPasswordDynamicDigest) {
|
||||
this.oldPasswordDynamicDigest = oldPasswordDynamicDigest;
|
||||
}
|
||||
|
||||
public String getNewPasswordDigest() {
|
||||
return newPasswordDigest;
|
||||
}
|
||||
|
||||
public void setNewPasswordDigest(String newPasswordDigest) {
|
||||
this.newPasswordDigest = newPasswordDigest;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ public class FullLoginAccountRequest {
|
||||
|
||||
private Integer uid;
|
||||
private String password;
|
||||
private String serverRandom;
|
||||
|
||||
public Integer getUid() {
|
||||
return uid;
|
||||
@@ -20,4 +21,12 @@ public class FullLoginAccountRequest {
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getServerRandom() {
|
||||
return serverRandom;
|
||||
}
|
||||
|
||||
public void setServerRandom(String serverRandom) {
|
||||
this.serverRandom = serverRandom;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@@ -28,8 +29,10 @@ public class UkeyLoginProof {
|
||||
@NotBlank(message = "loginSignature不能为空")
|
||||
private String loginSignature;
|
||||
|
||||
@Schema(description = "当前 UKey 固定席位对应账号口令的SM3摘要,字段名保持password兼容前端", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||
private String password;
|
||||
@NotBlank(message = "passwordDynamicDigest不能为空")
|
||||
@JsonAlias("password")
|
||||
@Schema(description = "当前 UKey 固定席位对应账号口令动态摘要:SM3(passwordDigest + serverRandom)", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||
private String passwordDynamicDigest;
|
||||
|
||||
public String getPubKey() {
|
||||
return pubKey;
|
||||
@@ -80,10 +83,18 @@ public class UkeyLoginProof {
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
return passwordDynamicDigest;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
this.passwordDynamicDigest = password;
|
||||
}
|
||||
|
||||
public String getPasswordDynamicDigest() {
|
||||
return passwordDynamicDigest;
|
||||
}
|
||||
|
||||
public void setPasswordDynamicDigest(String passwordDynamicDigest) {
|
||||
this.passwordDynamicDigest = passwordDynamicDigest;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class AuthFullAccountEntity extends BaseEntity {
|
||||
private Integer uid;
|
||||
private String accountName;
|
||||
private String displayName;
|
||||
private String passwordHash;
|
||||
private String passwordDigest;
|
||||
private String passwordSalt;
|
||||
private String status;
|
||||
private Boolean needChangePassword;
|
||||
@@ -53,12 +53,12 @@ public class AuthFullAccountEntity extends BaseEntity {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getPasswordHash() {
|
||||
return passwordHash;
|
||||
public String getPasswordDigest() {
|
||||
return passwordDigest;
|
||||
}
|
||||
|
||||
public void setPasswordHash(String passwordHash) {
|
||||
this.passwordHash = passwordHash;
|
||||
public void setPasswordDigest(String passwordDigest) {
|
||||
this.passwordDigest = passwordDigest;
|
||||
}
|
||||
|
||||
public String getPasswordSalt() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
|
||||
import com.cisd.tms.modules.auth.dto.LoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.PasswordChangePrepareResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
|
||||
@@ -27,5 +28,13 @@ public interface AuthService {
|
||||
|
||||
void logout(String sessionToken);
|
||||
|
||||
void changeAccountPassword(String sessionToken, Integer uid, String currentPassword, String newPassword);
|
||||
PasswordChangePrepareResponse prepareAccountPasswordChange(String sessionToken, Integer uid);
|
||||
|
||||
void changeAccountPassword(
|
||||
String sessionToken,
|
||||
Integer uid,
|
||||
String changeToken,
|
||||
String oldPasswordDynamicDigest,
|
||||
String newPasswordDigest
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
|
||||
import com.cisd.tms.modules.auth.service.AuthAdminService;
|
||||
import com.cisd.tms.modules.auth.service.PasswordDigestValidator;
|
||||
import com.cisd.tms.modules.auth.service.PasswordHasher;
|
||||
import com.cisd.tms.modules.auth.service.PasswordSaltGenerator;
|
||||
import com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
||||
import com.cisd.tms.modules.mk.dto.UKeySignEntity;
|
||||
@@ -43,7 +42,6 @@ import org.springframework.stereotype.Service;
|
||||
public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
|
||||
static final String DEFAULT_PASSWORD = "Sunyard@123";
|
||||
private static final String DEFAULT_PASSWORD_DIGEST = PasswordDigestSupport.sm3Hex(DEFAULT_PASSWORD);
|
||||
|
||||
private final RoleAccountRepository roleAccountRepository;
|
||||
private final AuthFullAccountRepository authFullAccountRepository;
|
||||
@@ -52,7 +50,6 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
private final LmkService lmkService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final Clock clock;
|
||||
private final PasswordSaltGenerator passwordSaltGenerator;
|
||||
|
||||
private void enableRoleAfterUkeyBindingIfReady(RoleCode targetRole) {
|
||||
if (!hasRequiredActiveUkeyBindings(targetRole)) {
|
||||
@@ -105,9 +102,8 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在");
|
||||
}
|
||||
for (AuthFullAccountEntity account : fullAccounts) {
|
||||
String newSalt = passwordSaltGenerator.nextSalt();
|
||||
account.setPasswordSalt(newSalt);
|
||||
account.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD_DIGEST, newSalt));
|
||||
String salt = account.getPasswordSalt();
|
||||
account.setPasswordDigest(passwordHasher.hash(PasswordDigestSupport.sm3Hex(salt + DEFAULT_PASSWORD), salt));
|
||||
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
account.setNeedChangePassword(Boolean.TRUE);
|
||||
account.setPasswordChangedAt(current);
|
||||
@@ -170,7 +166,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
}
|
||||
AuthFullAccountEntity account = authFullAccountRepository.findByRoleCodeAndUid(targetRoleCode, uid)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在"));
|
||||
if (!passwordHasher.matches(oldPassword, account.getPasswordSalt(), account.getPasswordHash())) {
|
||||
if (!passwordHasher.matches(oldPassword, account.getPasswordSalt(), account.getPasswordDigest())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "旧密码错误");
|
||||
}
|
||||
applyAdminPasswordChange(account, newPassword);
|
||||
@@ -431,6 +427,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
enableRoleAfterUkeyBindingIfReady(targetRole);
|
||||
return UKeySignResult.builder()
|
||||
.sign(signValue)
|
||||
.passwordSalt(loadAccount(targetRole.getCode(), request.getUid()).getPasswordSalt())
|
||||
.backupPacket(backupPacket)
|
||||
.build();
|
||||
}
|
||||
@@ -466,9 +463,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
}
|
||||
|
||||
private void applyAdminPasswordChange(AuthFullAccountEntity account, String newPassword) {
|
||||
String newSalt = passwordSaltGenerator.nextSalt();
|
||||
account.setPasswordSalt(newSalt);
|
||||
account.setPasswordHash(passwordHasher.hash(newPassword, newSalt));
|
||||
account.setPasswordDigest(passwordHasher.hash(newPassword, account.getPasswordSalt()));
|
||||
account.setNeedChangePassword(Boolean.TRUE);
|
||||
account.setPasswordChangedAt(now());
|
||||
account.setFailedCount(0);
|
||||
@@ -484,6 +479,11 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
||||
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色账号不存在"));
|
||||
}
|
||||
|
||||
private AuthFullAccountEntity loadAccount(String roleCode, Integer uid) {
|
||||
return authFullAccountRepository.findByRoleCodeAndUid(roleCode, uid)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在"));
|
||||
}
|
||||
|
||||
private void validateRoleUid(RoleCode targetRole, Integer uid) {
|
||||
if (uid == null || uid < 1 || uid > targetRole.getRequiredUkeyCount()) {
|
||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "uid超出角色UKey席位要求");
|
||||
|
||||
+7
-7
@@ -82,7 +82,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
||||
account.setUid(seed.uid());
|
||||
account.setAccountName(seed.accountName());
|
||||
account.setDisplayName(seed.displayName());
|
||||
account.setPasswordHash(seed.passwordHash());
|
||||
account.setPasswordDigest(seed.passwordDigest());
|
||||
account.setPasswordSalt(seed.passwordSalt());
|
||||
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
account.setNeedChangePassword(Boolean.TRUE);
|
||||
@@ -120,7 +120,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
||||
1,
|
||||
"super-admin-full-01",
|
||||
"超级管理员UKey席位一",
|
||||
"vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=",
|
||||
"00718a8319e67ef6cf9fc389c14ce16784278d55439a7a30a63fa7be8e42bbdf",
|
||||
"3df85c2988a5de2b9eeaa8109639641a"
|
||||
),
|
||||
new SeedFullAccount(
|
||||
@@ -129,7 +129,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
||||
2,
|
||||
"super-admin-full-02",
|
||||
"超级管理员UKey席位二",
|
||||
"25e83ejpUmjU64cYAKcDV5hhqClEu6aol8YyR3Jey7E=",
|
||||
"169731d7c897649a16a073cdfddf48d2e98f72f10da5cead5cbbd51f8a9ffea6",
|
||||
"50de03346bb1f6afc74938acf5e88560"
|
||||
),
|
||||
new SeedFullAccount(
|
||||
@@ -138,7 +138,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
||||
1,
|
||||
"key-admin-full-01",
|
||||
"密钥管理员UKey席位",
|
||||
"l65Pr7MxOvqPOe0u6ozGGZcT4raTXUgDYMQfLTINTyE=",
|
||||
"6d386b2a13550befb821795dcc0e9bda3d1cffc0d565ad539de16117e28f6a81",
|
||||
"073cd5fd30701edfcf033e8f501b2b43"
|
||||
),
|
||||
new SeedFullAccount(
|
||||
@@ -147,7 +147,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
||||
1,
|
||||
"audit-admin-full-01",
|
||||
"审计管理员UKey席位",
|
||||
"ZRwdn3TOotyXAytO4zeSpxRm/68Rm8VcB7w7VJmZPYc=",
|
||||
"72a53b0cb8dd15775516f5085c7a3ee09d65a130dc1b17e18545d24930826d0c",
|
||||
"57e2616077bd8af5cb90fc9790f69573"
|
||||
),
|
||||
new SeedFullAccount(
|
||||
@@ -156,7 +156,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
||||
1,
|
||||
"ops-admin-full-01",
|
||||
"运维管理员UKey席位",
|
||||
"5Wu8Mf5hC3kq4gm/62/T7pO+aZIw8Y7uYoMIztWyT1w=",
|
||||
"58655960343a1efb0c1b281ce4c384ac699d01ab61c2c710d35e54b798f6e4c5",
|
||||
"a6bea7601138e90a540b39c73d53df81"
|
||||
)
|
||||
);
|
||||
@@ -171,7 +171,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
||||
int uid,
|
||||
String accountName,
|
||||
String displayName,
|
||||
String passwordHash,
|
||||
String passwordDigest,
|
||||
String passwordSalt
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.cisd.tms.modules.auth.dto.LoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||
import com.cisd.tms.modules.auth.dto.PasswordLoginAccountRequest;
|
||||
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
||||
import com.cisd.tms.modules.auth.dto.PasswordChangePrepareResponse;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginProof;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
|
||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||
@@ -52,6 +53,8 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -79,6 +82,7 @@ AuthServiceImpl implements AuthService {
|
||||
private final SessionTokenGenerator sessionTokenGenerator;
|
||||
private final PasswordSaltGenerator passwordSaltGenerator;
|
||||
private final AuthPolicyService authPolicyService;
|
||||
private final Map<String, PendingPasswordChange> pendingPasswordChanges = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public LoginResponse login(LoginRequest request) {
|
||||
@@ -205,9 +209,7 @@ AuthServiceImpl implements AuthService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeAccountPassword(String sessionToken, Integer uid, String currentPassword, String newPassword) {
|
||||
PasswordDigestValidator.validate(currentPassword);
|
||||
PasswordDigestValidator.validate(newPassword);
|
||||
public PasswordChangePrepareResponse prepareAccountPasswordChange(String sessionToken, Integer uid) {
|
||||
AuthSessionEntity session = requireActiveSession(sessionToken);
|
||||
if (uid == null || !containsAuthenticatedUid(session, uid)) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "当前会话未完成账号认证");
|
||||
@@ -215,14 +217,60 @@ AuthServiceImpl implements AuthService {
|
||||
AuthFullAccountEntity fullAccount = authFullAccountRepository.findByRoleCodeAndUid(session.getRoleCode(), uid)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在"));
|
||||
validateFullAccountStatus(fullAccount);
|
||||
if (!passwordHasher.matches(currentPassword, fullAccount.getPasswordSalt(), fullAccount.getPasswordHash())) {
|
||||
String changeToken = UUID.randomUUID().toString().replace("-", "");
|
||||
String newPasswordSalt = passwordSaltGenerator.nextSalt();
|
||||
String oldServerRandom = UUID.randomUUID().toString().replace("-", "");
|
||||
pendingPasswordChanges.put(changeToken, new PendingPasswordChange(
|
||||
sessionToken,
|
||||
session.getRoleCode(),
|
||||
uid,
|
||||
newPasswordSalt,
|
||||
oldServerRandom,
|
||||
now().plusMinutes(IDLE_TIMEOUT_MINUTES)
|
||||
));
|
||||
PasswordChangePrepareResponse response = new PasswordChangePrepareResponse();
|
||||
response.setChangeToken(changeToken);
|
||||
response.setNewPasswordSalt(newPasswordSalt);
|
||||
response.setOldServerRandom(oldServerRandom);
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeAccountPassword(
|
||||
String sessionToken,
|
||||
Integer uid,
|
||||
String changeToken,
|
||||
String oldPasswordDynamicDigest,
|
||||
String newPasswordDigest
|
||||
) {
|
||||
PasswordDigestValidator.validate(oldPasswordDynamicDigest);
|
||||
PasswordDigestValidator.validate(newPasswordDigest);
|
||||
AuthSessionEntity session = requireActiveSession(sessionToken);
|
||||
if (uid == null || !containsAuthenticatedUid(session, uid)) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "当前会话未完成账号认证");
|
||||
}
|
||||
PendingPasswordChange pending = pendingPasswordChanges.remove(changeToken);
|
||||
if (pending == null
|
||||
|| pending.expiresAt().isBefore(now())
|
||||
|| !sessionToken.equals(pending.sessionToken())
|
||||
|| !session.getRoleCode().equals(pending.roleCode())
|
||||
|| !uid.equals(pending.uid())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "改密确认令牌不存在或已过期");
|
||||
}
|
||||
AuthFullAccountEntity fullAccount = authFullAccountRepository.findByRoleCodeAndUid(session.getRoleCode(), uid)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在"));
|
||||
validateFullAccountStatus(fullAccount);
|
||||
if (!PasswordDigestSupport.matchesDynamicDigest(
|
||||
fullAccount.getPasswordDigest(),
|
||||
pending.oldServerRandom(),
|
||||
oldPasswordDynamicDigest
|
||||
)) {
|
||||
onFullPasswordFailed(fullAccount);
|
||||
}
|
||||
|
||||
String salt = passwordSaltGenerator.nextSalt();
|
||||
LocalDateTime current = now();
|
||||
fullAccount.setPasswordSalt(salt);
|
||||
fullAccount.setPasswordHash(passwordHasher.hash(newPassword, salt));
|
||||
fullAccount.setPasswordSalt(pending.newPasswordSalt());
|
||||
fullAccount.setPasswordDigest(passwordHasher.hash(newPasswordDigest, pending.newPasswordSalt()));
|
||||
fullAccount.setFailedCount(0);
|
||||
fullAccount.setLockedUntil(null);
|
||||
fullAccount.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
@@ -267,7 +315,7 @@ AuthServiceImpl implements AuthService {
|
||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在"));
|
||||
validateFullAccountStatus(roleAccountSeat);
|
||||
PasswordDigestValidator.validate(account.getPassword());
|
||||
if (!passwordHasher.matches(account.getPassword(), roleAccountSeat.getPasswordSalt(), roleAccountSeat.getPasswordHash())) {
|
||||
if (!passwordHasher.matches(account.getPassword(), roleAccountSeat.getPasswordSalt(), roleAccountSeat.getPasswordDigest())) {
|
||||
onFullPasswordFailed(roleAccountSeat);
|
||||
}
|
||||
validatedAccounts.add(roleAccountSeat);
|
||||
@@ -339,7 +387,11 @@ AuthServiceImpl implements AuthService {
|
||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "完整账号不存在"));
|
||||
validateFullAccountStatus(fullAccount);
|
||||
PasswordDigestValidator.validate(account.getPassword());
|
||||
if (!passwordHasher.matches(account.getPassword(), fullAccount.getPasswordSalt(), fullAccount.getPasswordHash())) {
|
||||
if (!PasswordDigestSupport.matchesDynamicDigest(
|
||||
fullAccount.getPasswordDigest(),
|
||||
account.getServerRandom(),
|
||||
account.getPassword()
|
||||
)) {
|
||||
onFullPasswordFailed(fullAccount);
|
||||
}
|
||||
validatedAccounts.add(fullAccount);
|
||||
@@ -365,12 +417,23 @@ AuthServiceImpl implements AuthService {
|
||||
}
|
||||
FullLoginAccountRequest accountRequest = new FullLoginAccountRequest();
|
||||
accountRequest.setUid(uid);
|
||||
accountRequest.setPassword(proof.getPassword());
|
||||
accountRequest.setPassword(proof.getPasswordDynamicDigest());
|
||||
accountRequest.setServerRandom(proof.getServerRandom());
|
||||
requests.add(accountRequest);
|
||||
}
|
||||
return requests;
|
||||
}
|
||||
|
||||
private record PendingPasswordChange(
|
||||
String sessionToken,
|
||||
String roleCode,
|
||||
Integer uid,
|
||||
String newPasswordSalt,
|
||||
String oldServerRandom,
|
||||
LocalDateTime expiresAt
|
||||
) {
|
||||
}
|
||||
|
||||
private void ensureMasterKeyReady() {
|
||||
MasterKeyStatus.StatusDetail status = lmkService.getMasterKeyStatus();
|
||||
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cisd.tms.modules.auth.service.impl;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
@@ -17,4 +18,15 @@ final class PasswordDigestSupport {
|
||||
digest.doFinal(result, 0);
|
||||
return Hex.toHexString(result);
|
||||
}
|
||||
|
||||
static boolean matchesDigest(String actual, String expected) {
|
||||
byte[] actualBytes = (actual == null ? "" : actual).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] expectedBytes = (expected == null ? "" : expected).getBytes(StandardCharsets.UTF_8);
|
||||
return MessageDigest.isEqual(actualBytes, expectedBytes);
|
||||
}
|
||||
|
||||
static boolean matchesDynamicDigest(String storedPasswordDigest, String serverRandom, String actualDynamicDigest) {
|
||||
return matchesDigest(sm3Hex((storedPasswordDigest == null ? "" : storedPasswordDigest)
|
||||
+ (serverRandom == null ? "" : serverRandom)), actualDynamicDigest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,18 @@
|
||||
package com.cisd.tms.modules.auth.service.impl;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
|
||||
import com.cisd.tms.modules.auth.service.PasswordHasher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class Pbkdf2PasswordHasher implements PasswordHasher {
|
||||
|
||||
private static final int ITERATIONS = 65_536;
|
||||
private static final int KEY_LENGTH = 256;
|
||||
|
||||
@Override
|
||||
public String hash(String password, String salt) {
|
||||
try {
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(StandardCharsets.UTF_8), ITERATIONS, KEY_LENGTH);
|
||||
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
return Base64.getEncoder().encodeToString(keyFactory.generateSecret(spec).getEncoded());
|
||||
} catch (GeneralSecurityException ex) {
|
||||
throw new IllegalStateException("密码哈希计算失败", ex);
|
||||
}
|
||||
public String hash(String passwordDigest, String salt) {
|
||||
return passwordDigest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String rawPassword, String salt, String expectedHash) {
|
||||
byte[] actual = hash(rawPassword, salt).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] expected = expectedHash.getBytes(StandardCharsets.UTF_8);
|
||||
return MessageDigest.isEqual(actual, expected);
|
||||
public boolean matches(String actualPasswordDigest, String salt, String expectedPasswordDigest) {
|
||||
return PasswordDigestSupport.matchesDigest(actualPasswordDigest, expectedPasswordDigest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ public class UKeySignResult {
|
||||
*/
|
||||
private String sign;
|
||||
|
||||
/**
|
||||
* 当前角色席位口令盐值。前端写入 UKey,登录时用于计算静态口令摘要。
|
||||
*/
|
||||
private String passwordSalt;
|
||||
|
||||
/**
|
||||
* 当发卡流程需要同时下发主密钥备份信息时,统一通过备份包承载。
|
||||
*/
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- TMS 全量库结构初始化脚本
|
||||
-- 适用范围:空库初始化 / Flyway 首次建库
|
||||
-- TMS 旧库口令摘要迁移脚本
|
||||
-- 适用范围:已经存在旧认证表的数据库,从旧 password_hash 方案迁移到
|
||||
-- password_digest = SM3(password_salt + 原始口令) 方案。
|
||||
-- 数据库:TiDB / MySQL 8+
|
||||
--
|
||||
-- 说明:
|
||||
-- 1. 本文件是当前仓库唯一保留的数据库初始化 SQL。
|
||||
-- 2. 结构已按当前最终状态汇总,不再依赖历史 V1~V12 逐步迁移。
|
||||
-- 3. 执行前请先选中目标数据库,本文件不再包含 CREATE DATABASE / USE。
|
||||
-- 重要说明:
|
||||
-- 1. 本文件不是空库全量初始化脚本。空库初始化请使用 V2__tms_schema_full.sql。
|
||||
-- 2. 旧 password_hash 通常是不可逆摘要,无法还原用户原始口令后生成新摘要。
|
||||
-- 3. 因此迁移时将固定角色席位统一重置为默认口令 Sunyard@123,并置为强制改密。
|
||||
-- 4. 旧 password_hash 字段保留,便于现场回滚或排查;新代码只读取 password_digest。
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS TMS
|
||||
@@ -15,764 +17,66 @@ CREATE DATABASE IF NOT EXISTS TMS
|
||||
|
||||
USE TMS;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 初始化任务
|
||||
-- -----------------------------------------------------------------------------
|
||||
ALTER TABLE tms_auth_full_account
|
||||
ADD COLUMN password_digest VARCHAR(64) NULL COMMENT '口令静态摘要:SM3(password_salt + 原始口令)' AFTER password_hash;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_init_task (
|
||||
id BIGINT PRIMARY KEY,
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
task_type VARCHAR(32) NOT NULL DEFAULT 'INIT' COMMENT '任务类型:INIT/RESET',
|
||||
version VARCHAR(32) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
product_type VARCHAR(16) NULL,
|
||||
org_code VARCHAR(32) NULL,
|
||||
mq_type VARCHAR(32) NULL,
|
||||
deploy_mode VARCHAR(16) NULL,
|
||||
init_plan_json LONGTEXT NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_init_task_task_id (task_id)
|
||||
) COMMENT='初始化任务表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_init_task_step (
|
||||
id BIGINT PRIMARY KEY,
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
step_no INT NOT NULL,
|
||||
step_code VARCHAR(64) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
message VARCHAR(512) NULL,
|
||||
command_line VARCHAR(2048) NULL,
|
||||
log_path VARCHAR(512) NULL,
|
||||
exit_code INT NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_init_task_step_task_no (task_id, step_no),
|
||||
KEY idx_tms_init_task_step_task_id (task_id)
|
||||
) COMMENT='初始化任务步骤表';
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 资源备份与恢复
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_resource_backup_task (
|
||||
id BIGINT PRIMARY KEY,
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
backup_id VARCHAR(64) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
product_type VARCHAR(16) NOT NULL,
|
||||
mq_type VARCHAR(32) NULL,
|
||||
org_code VARCHAR(32) NULL,
|
||||
source_device_id VARCHAR(128) NULL,
|
||||
package_name VARCHAR(255) NULL,
|
||||
package_path VARCHAR(1024) NULL,
|
||||
package_size BIGINT NULL,
|
||||
package_hash VARCHAR(128) NULL,
|
||||
storage_enc_key_fingerprint VARCHAR(128) NOT NULL,
|
||||
authenticity_key_fingerprint VARCHAR(128) NOT NULL,
|
||||
manifest_summary_json JSON NULL,
|
||||
error_code VARCHAR(64) NULL,
|
||||
error_message VARCHAR(1024) NULL,
|
||||
remark VARCHAR(1024) NULL,
|
||||
create_by VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
start_time DATETIME(3) NULL,
|
||||
finish_time DATETIME(3) NULL,
|
||||
UNIQUE KEY uk_tms_resource_backup_task_task_id (task_id),
|
||||
UNIQUE KEY uk_tms_resource_backup_task_backup_id (backup_id),
|
||||
KEY idx_tms_resource_backup_task_status (status),
|
||||
KEY idx_tms_resource_backup_task_create_time (create_time)
|
||||
) COMMENT='资源备份任务表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_resource_restore_task (
|
||||
id BIGINT PRIMARY KEY,
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
precheck_id VARCHAR(64) NOT NULL,
|
||||
backup_id VARCHAR(64) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
source_device_id VARCHAR(128) NULL,
|
||||
source_product_type VARCHAR(16) NOT NULL,
|
||||
target_product_type VARCHAR(16) NOT NULL,
|
||||
package_file_id VARCHAR(64) NOT NULL,
|
||||
package_path VARCHAR(1024) NULL,
|
||||
staging_dir VARCHAR(1024) NULL,
|
||||
runner_state_path VARCHAR(1024) NULL,
|
||||
storage_enc_key_fingerprint VARCHAR(128) NOT NULL,
|
||||
authenticity_key_fingerprint VARCHAR(128) NOT NULL,
|
||||
restore_plan_json JSON NULL,
|
||||
consistency_report_json JSON NULL,
|
||||
error_code VARCHAR(64) NULL,
|
||||
error_message VARCHAR(1024) NULL,
|
||||
remark VARCHAR(1024) NULL,
|
||||
create_by VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
start_time DATETIME(3) NULL,
|
||||
finish_time DATETIME(3) NULL,
|
||||
UNIQUE KEY uk_tms_resource_restore_task_task_id (task_id),
|
||||
UNIQUE KEY uk_tms_resource_restore_task_precheck_id (precheck_id),
|
||||
KEY idx_tms_resource_restore_task_status (status),
|
||||
KEY idx_tms_resource_restore_task_create_time (create_time)
|
||||
) COMMENT='资源恢复任务表';
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 认证与设备基础表
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_device_node (
|
||||
id BIGINT PRIMARY KEY,
|
||||
node_id VARCHAR(64) NOT NULL,
|
||||
host VARCHAR(128) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_device_node_node_id (node_id)
|
||||
) COMMENT='设备节点表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_file_record (
|
||||
id BIGINT PRIMARY KEY,
|
||||
file_id VARCHAR(64) NOT NULL,
|
||||
original_filename VARCHAR(255) NOT NULL,
|
||||
storage_path VARCHAR(1024) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_file_record_file_id (file_id)
|
||||
) COMMENT='文件记录表';
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 设备软件版本
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_device_software_version (
|
||||
id BIGINT PRIMARY KEY,
|
||||
component_code VARCHAR(64) NOT NULL,
|
||||
component_name VARCHAR(128) NOT NULL,
|
||||
current_version VARCHAR(128) NOT NULL,
|
||||
source_type VARCHAR(32) NOT NULL,
|
||||
detected_at DATETIME NULL,
|
||||
remarks VARCHAR(255) NULL,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_tms_device_software_version_component_code (component_code)
|
||||
) COMMENT='设备软件版本表';
|
||||
|
||||
INSERT INTO tms_device_software_version
|
||||
(id, component_code, component_name, current_version, source_type, detected_at, remarks)
|
||||
VALUES
|
||||
(1001, 'RABBITMQ', '消息队列', 'V3.8.21', 'MANUAL', NOW(), '出厂预置'),
|
||||
(1002, 'TLQ', '消息队列', 'V8.1.11', 'MANUAL', NOW(), '出厂预置'),
|
||||
(1003, 'TIDB', '数据库', 'V8.1.0', 'MANUAL', NOW(), '出厂预置'),
|
||||
(1004, 'NGINX', 'Nginx', 'V1.20', 'MANUAL', NOW(), '出厂预置'),
|
||||
(1005, 'RECEIVER', '标准收发器', 'V1.3.2', 'MANUAL', NOW(), '出厂预置'),
|
||||
(1006, 'TMS', 'CISD终端管理服务', 'V1.0.0', 'MANUAL', NOW(), '出厂预置')
|
||||
ON DUPLICATE KEY UPDATE
|
||||
component_name = VALUES(component_name),
|
||||
current_version = VALUES(current_version),
|
||||
source_type = VALUES(source_type),
|
||||
detected_at = VALUES(detected_at),
|
||||
remarks = VALUES(remarks),
|
||||
update_time = CURRENT_TIMESTAMP;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 升级任务
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_upgrade_task (
|
||||
id BIGINT PRIMARY KEY,
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
upgrade_mode VARCHAR(16) NOT NULL,
|
||||
task_type VARCHAR(32) NOT NULL,
|
||||
product_type VARCHAR(16) NULL,
|
||||
package_file_id VARCHAR(64) NOT NULL,
|
||||
package_name VARCHAR(255) NOT NULL,
|
||||
package_hash VARCHAR(128) NOT NULL,
|
||||
current_version VARCHAR(64) NOT NULL,
|
||||
target_version VARCHAR(64) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
verify_passed TINYINT(1) NOT NULL DEFAULT 0,
|
||||
verify_message VARCHAR(1024) NULL,
|
||||
manifest_json TEXT NULL,
|
||||
detail_log_path VARCHAR(1024) NULL,
|
||||
error_message VARCHAR(1024) NULL,
|
||||
operator VARCHAR(64) NULL,
|
||||
started_at DATETIME(3) NULL,
|
||||
finished_at DATETIME(3) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_upgrade_task_task_id (task_id),
|
||||
KEY idx_tms_upgrade_task_status (status),
|
||||
KEY idx_tms_upgrade_task_task_type (task_type),
|
||||
KEY idx_tms_upgrade_task_create_time (create_time)
|
||||
) COMMENT='升级任务表';
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 新认证体系
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_role_account (
|
||||
id BIGINT PRIMARY KEY,
|
||||
role_code VARCHAR(64) NOT NULL,
|
||||
display_name VARCHAR(128) NOT NULL,
|
||||
required_ukey_count INT NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_role_account_role_code (role_code)
|
||||
) COMMENT='角色账号表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_role_ukey_binding (
|
||||
id BIGINT PRIMARY KEY,
|
||||
role_code VARCHAR(64) NOT NULL,
|
||||
uid INT NOT NULL,
|
||||
ukey_serial VARCHAR(128) NOT NULL,
|
||||
ukey_pubkey TEXT NOT NULL,
|
||||
issuer_sign TEXT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
bound_at DATETIME(3) NOT NULL,
|
||||
unbound_at DATETIME(3) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_role_ukey_binding_role_uid_serial (role_code, uid, ukey_serial),
|
||||
KEY idx_tms_role_ukey_binding_role_uid_status (role_code, uid, status),
|
||||
KEY idx_tms_role_ukey_binding_role_status (role_code, status)
|
||||
) COMMENT='角色UKey绑定表';
|
||||
|
||||
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,
|
||||
password_changed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
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)
|
||||
) COMMENT='完整认证账号表';
|
||||
|
||||
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_principals_json JSON NULL,
|
||||
auth_method VARCHAR(32) NOT NULL,
|
||||
auth_level VARCHAR(32) NOT NULL,
|
||||
issued_at DATETIME(3) NOT NULL,
|
||||
last_active_at DATETIME(3) NOT NULL,
|
||||
expires_at DATETIME(3) NOT NULL,
|
||||
logout_at DATETIME(3) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_auth_session_token (session_token),
|
||||
KEY idx_tms_auth_session_role_code (role_code)
|
||||
) COMMENT='认证会话表';
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 防重放与安全事件
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_replay_nonce (
|
||||
id BIGINT PRIMARY KEY,
|
||||
scope VARCHAR(32) NOT NULL COMMENT '防重放作用域:OPENAPI / INTERNAL_API',
|
||||
principal_id VARCHAR(128) NOT NULL COMMENT '主体标识:appId / sessionToken',
|
||||
nonce VARCHAR(128) NOT NULL,
|
||||
request_timestamp BIGINT NOT NULL,
|
||||
request_method VARCHAR(16) NULL,
|
||||
request_path VARCHAR(256) NULL,
|
||||
body_hash VARCHAR(128) NULL,
|
||||
remote_ip VARCHAR(64) NULL,
|
||||
user_agent VARCHAR(512) NULL,
|
||||
expires_at DATETIME(3) NOT NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_replay_nonce_scope_principal_nonce (scope, principal_id, nonce),
|
||||
KEY idx_tms_replay_nonce_expires_at (expires_at),
|
||||
KEY idx_tms_replay_nonce_principal_time (principal_id, create_time)
|
||||
) COMMENT='防重放nonce表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_security_event (
|
||||
id BIGINT PRIMARY KEY,
|
||||
event_type VARCHAR(64) NOT NULL,
|
||||
scope VARCHAR(32) NOT NULL,
|
||||
principal_id VARCHAR(128) NULL,
|
||||
request_method VARCHAR(16) NULL,
|
||||
request_path VARCHAR(256) NULL,
|
||||
nonce VARCHAR(128) NULL,
|
||||
remote_ip VARCHAR(64) NULL,
|
||||
detail_json JSON NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
KEY idx_tms_security_event_event_type (event_type),
|
||||
KEY idx_tms_security_event_scope_principal (scope, principal_id)
|
||||
) COMMENT='安全事件表';
|
||||
|
||||
INSERT IGNORE INTO tms_role_account (
|
||||
id,
|
||||
role_code,
|
||||
display_name,
|
||||
required_ukey_count,
|
||||
status,
|
||||
create_time,
|
||||
update_time
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
11001,
|
||||
'SUPER_ADMIN',
|
||||
'超级管理员',
|
||||
2,
|
||||
'ACTIVE',
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
),
|
||||
(
|
||||
11002,
|
||||
'KEY_ADMIN',
|
||||
'密钥管理员',
|
||||
1,
|
||||
'UNENABLED',
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
),
|
||||
(
|
||||
11003,
|
||||
'AUDIT_ADMIN',
|
||||
'审计管理员',
|
||||
1,
|
||||
'UNENABLED',
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
),
|
||||
(
|
||||
11004,
|
||||
'OPS_ADMIN',
|
||||
'运维管理员',
|
||||
1,
|
||||
'UNENABLED',
|
||||
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,
|
||||
password_changed_at,
|
||||
failed_count,
|
||||
locked_until,
|
||||
last_login_at,
|
||||
last_active_at,
|
||||
create_time,
|
||||
update_time
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
12101,
|
||||
'SUPER_ADMIN',
|
||||
1,
|
||||
'super-admin-full-01',
|
||||
'超级管理员UKey席位一',
|
||||
'vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=',
|
||||
'3df85c2988a5de2b9eeaa8109639641a',
|
||||
'ACTIVE',
|
||||
1,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
),
|
||||
(
|
||||
12102,
|
||||
'SUPER_ADMIN',
|
||||
2,
|
||||
'super-admin-full-02',
|
||||
'超级管理员UKey席位二',
|
||||
'25e83ejpUmjU64cYAKcDV5hhqClEu6aol8YyR3Jey7E=',
|
||||
'50de03346bb1f6afc74938acf5e88560',
|
||||
'ACTIVE',
|
||||
1,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
),
|
||||
(
|
||||
12103,
|
||||
'KEY_ADMIN',
|
||||
1,
|
||||
'key-admin-full-01',
|
||||
'密钥管理员UKey席位',
|
||||
'l65Pr7MxOvqPOe0u6ozGGZcT4raTXUgDYMQfLTINTyE=',
|
||||
'073cd5fd30701edfcf033e8f501b2b43',
|
||||
'ACTIVE',
|
||||
1,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
),
|
||||
(
|
||||
12104,
|
||||
'AUDIT_ADMIN',
|
||||
1,
|
||||
'audit-admin-full-01',
|
||||
'审计管理员UKey席位',
|
||||
'ZRwdn3TOotyXAytO4zeSpxRm/68Rm8VcB7w7VJmZPYc=',
|
||||
'57e2616077bd8af5cb90fc9790f69573',
|
||||
'ACTIVE',
|
||||
1,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
),
|
||||
(
|
||||
12105,
|
||||
'OPS_ADMIN',
|
||||
1,
|
||||
'ops-admin-full-01',
|
||||
'运维管理员UKey席位',
|
||||
'5Wu8Mf5hC3kq4gm/62/T7pO+aZIw8Y7uYoMIztWyT1w=',
|
||||
'a6bea7601138e90a540b39c73d53df81',
|
||||
'ACTIVE',
|
||||
1,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
CURRENT_TIMESTAMP(3)
|
||||
);
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 兼容旧库默认口令摘要升级
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 旧版本默认账号保存的是 PBKDF2('Sunyard@123', password_salt)。
|
||||
-- 当前版本前端提交 password=SM3('Sunyard@123'),因此默认账号统一重置为
|
||||
-- 固定 salt + PBKDF2(SM3('Sunyard@123'), salt),便于旧库直接切换到摘要口径。
|
||||
-- 默认口令摘要:SM3(password_salt + 'Sunyard@123')。
|
||||
-- 迁移会把固定席位盐值同步为后端当前标准盐值,后续 issue-sign 会返回该盐值供前端写入 UKey。
|
||||
UPDATE tms_auth_full_account
|
||||
SET password_hash = 'vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=',
|
||||
password_salt = '3df85c2988a5de2b9eeaa8109639641a',
|
||||
SET password_salt = '3df85c2988a5de2b9eeaa8109639641a',
|
||||
password_digest = '00718a8319e67ef6cf9fc389c14ce16784278d55439a7a30a63fa7be8e42bbdf',
|
||||
need_change_password = 1,
|
||||
failed_count = 0,
|
||||
locked_until = NULL,
|
||||
password_changed_at = CURRENT_TIMESTAMP(3),
|
||||
update_time = CURRENT_TIMESTAMP(3)
|
||||
WHERE role_code = 'SUPER_ADMIN'
|
||||
AND uid = 1;
|
||||
|
||||
UPDATE tms_auth_full_account
|
||||
SET password_hash = '25e83ejpUmjU64cYAKcDV5hhqClEu6aol8YyR3Jey7E=',
|
||||
password_salt = '50de03346bb1f6afc74938acf5e88560',
|
||||
SET password_salt = '50de03346bb1f6afc74938acf5e88560',
|
||||
password_digest = '169731d7c897649a16a073cdfddf48d2e98f72f10da5cead5cbbd51f8a9ffea6',
|
||||
need_change_password = 1,
|
||||
failed_count = 0,
|
||||
locked_until = NULL,
|
||||
password_changed_at = CURRENT_TIMESTAMP(3),
|
||||
update_time = CURRENT_TIMESTAMP(3)
|
||||
WHERE role_code = 'SUPER_ADMIN'
|
||||
AND uid = 2;
|
||||
|
||||
UPDATE tms_auth_full_account
|
||||
SET password_hash = 'l65Pr7MxOvqPOe0u6ozGGZcT4raTXUgDYMQfLTINTyE=',
|
||||
password_salt = '073cd5fd30701edfcf033e8f501b2b43',
|
||||
SET password_salt = '073cd5fd30701edfcf033e8f501b2b43',
|
||||
password_digest = '6d386b2a13550befb821795dcc0e9bda3d1cffc0d565ad539de16117e28f6a81',
|
||||
need_change_password = 1,
|
||||
failed_count = 0,
|
||||
locked_until = NULL,
|
||||
password_changed_at = CURRENT_TIMESTAMP(3),
|
||||
update_time = CURRENT_TIMESTAMP(3)
|
||||
WHERE role_code = 'KEY_ADMIN'
|
||||
AND uid = 1;
|
||||
|
||||
UPDATE tms_auth_full_account
|
||||
SET password_hash = 'ZRwdn3TOotyXAytO4zeSpxRm/68Rm8VcB7w7VJmZPYc=',
|
||||
password_salt = '57e2616077bd8af5cb90fc9790f69573',
|
||||
SET password_salt = '57e2616077bd8af5cb90fc9790f69573',
|
||||
password_digest = '72a53b0cb8dd15775516f5085c7a3ee09d65a130dc1b17e18545d24930826d0c',
|
||||
need_change_password = 1,
|
||||
failed_count = 0,
|
||||
locked_until = NULL,
|
||||
password_changed_at = CURRENT_TIMESTAMP(3),
|
||||
update_time = CURRENT_TIMESTAMP(3)
|
||||
WHERE role_code = 'AUDIT_ADMIN'
|
||||
AND uid = 1;
|
||||
|
||||
UPDATE tms_auth_full_account
|
||||
SET password_hash = '5Wu8Mf5hC3kq4gm/62/T7pO+aZIw8Y7uYoMIztWyT1w=',
|
||||
password_salt = 'a6bea7601138e90a540b39c73d53df81',
|
||||
SET password_salt = 'a6bea7601138e90a540b39c73d53df81',
|
||||
password_digest = '58655960343a1efb0c1b281ce4c384ac699d01ab61c2c710d35e54b798f6e4c5',
|
||||
need_change_password = 1,
|
||||
failed_count = 0,
|
||||
locked_until = NULL,
|
||||
password_changed_at = CURRENT_TIMESTAMP(3),
|
||||
update_time = CURRENT_TIMESTAMP(3)
|
||||
WHERE role_code = 'OPS_ADMIN'
|
||||
AND uid = 1;
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- IP白名单表
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS tms_access_whitelist (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
ip VARCHAR(32) NOT NULL,
|
||||
mask VARCHAR(10) NOT NULL,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
|
||||
) COMMENT='访问白名单表';
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- Cert module tables
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS tms_key_entity (
|
||||
id BIGINT PRIMARY KEY,
|
||||
org_code VARCHAR(64) NOT NULL COMMENT 'organization code',
|
||||
org_name VARCHAR(255) NOT NULL COMMENT 'organization name',
|
||||
algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 / RSA',
|
||||
key_len INT NOT NULL COMMENT 'SM2 fixed 256 in V1',
|
||||
key_idx INT NOT NULL COMMENT 'card key index, allowed range [2..8]',
|
||||
backup_data TEXT NOT NULL COMMENT 'SDFE_BackupUserKey output',
|
||||
public_key VARCHAR(500) NOT NULL COMMENT 'public key, Base64/PEM',
|
||||
p10_data VARCHAR(2000) NULL COMMENT 'PKCS#10 PEM content',
|
||||
subject_dn VARCHAR(500) NULL COMMENT 'DN used when generating P10',
|
||||
cert_mode VARCHAR(16) NULL COMMENT 'SINGLE / DOUBLE',
|
||||
status VARCHAR(32) NOT NULL COMMENT 'CREATED / P10_GENERATED / CERT_IMPORTED',
|
||||
allow_ips VARCHAR(500) NULL COMMENT 'allowed source IP list',
|
||||
sign_data VARCHAR(512) NULL COMMENT 'reserved, not used in V1',
|
||||
create_by VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_key_entity_key_idx (key_idx),
|
||||
KEY idx_tms_key_entity_org_code (org_code),
|
||||
KEY idx_tms_key_entity_algo (algo_type),
|
||||
KEY idx_tms_key_entity_status (status),
|
||||
KEY idx_tms_key_entity_create_time (create_time)
|
||||
) COMMENT='密钥实体表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_certificate (
|
||||
id BIGINT PRIMARY KEY,
|
||||
entity_id BIGINT NULL COMMENT 'required when cert_type=ENTITY',
|
||||
cert_type VARCHAR(16) NOT NULL COMMENT 'ENTITY / USER',
|
||||
subject_dn VARCHAR(500) NOT NULL,
|
||||
issuer_dn VARCHAR(1024) NOT NULL,
|
||||
serial_number VARCHAR(128) NOT NULL,
|
||||
algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1',
|
||||
fingerprint VARCHAR(128) NOT NULL COMMENT 'SHA-256 fingerprint',
|
||||
cert_data VARCHAR(2000) NOT NULL COMMENT 'certificate PEM content',
|
||||
valid_from DATETIME(3) NOT NULL,
|
||||
valid_to DATETIME(3) NOT NULL,
|
||||
import_time DATETIME(3) NOT NULL,
|
||||
sign_data VARCHAR(512) NULL COMMENT 'reserved, not used in V1',
|
||||
create_by VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_certificate_fingerprint (fingerprint),
|
||||
KEY idx_tms_certificate_entity_id (entity_id),
|
||||
KEY idx_tms_certificate_cert_type (cert_type),
|
||||
KEY idx_tms_certificate_issuer_dn (issuer_dn(512)),
|
||||
KEY idx_tms_certificate_valid_to (valid_to),
|
||||
KEY idx_tms_certificate_import_time (import_time)
|
||||
) COMMENT='证书表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_trusted_cert (
|
||||
id BIGINT PRIMARY KEY,
|
||||
alias VARCHAR(128) NULL,
|
||||
subject_dn VARCHAR(1024) NOT NULL,
|
||||
issuer_dn VARCHAR(1024) NOT NULL,
|
||||
serial_number VARCHAR(128) NOT NULL,
|
||||
algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1',
|
||||
fingerprint VARCHAR(128) NOT NULL COMMENT 'SHA-256 fingerprint',
|
||||
cert_data VARCHAR(2000) NOT NULL COMMENT 'certificate PEM content',
|
||||
ca_level TINYINT NOT NULL COMMENT '0:Root, 1:Intermediate',
|
||||
valid_from DATETIME(3) NOT NULL,
|
||||
valid_to DATETIME(3) NOT NULL,
|
||||
import_time DATETIME(3) NOT NULL,
|
||||
sign_data VARCHAR(512) NULL COMMENT 'reserved, not used in V1',
|
||||
create_by VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_trusted_cert_fingerprint (fingerprint),
|
||||
KEY idx_tms_trusted_cert_subject_dn (subject_dn(512)),
|
||||
KEY idx_tms_trusted_cert_issuer_dn (issuer_dn(512)),
|
||||
KEY idx_tms_trusted_cert_valid_to (valid_to)
|
||||
) COMMENT='可信证书表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_certificate_crl (
|
||||
id BIGINT PRIMARY KEY,
|
||||
issuer_dn VARCHAR(1024) NOT NULL,
|
||||
crl_number VARCHAR(128) NULL,
|
||||
algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1',
|
||||
fingerprint VARCHAR(128) NOT NULL COMMENT 'SHA-256 fingerprint',
|
||||
crl_data MEDIUMTEXT NOT NULL COMMENT 'reserved legacy CRL PEM content; large CRLs are not stored inline',
|
||||
this_update DATETIME(3) NOT NULL,
|
||||
next_update DATETIME(3) NULL,
|
||||
revoked_count INT NOT NULL,
|
||||
import_time DATETIME(3) NOT NULL,
|
||||
create_by VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_certificate_crl_fingerprint (fingerprint),
|
||||
KEY idx_tms_certificate_crl_issuer_dn (issuer_dn(512)),
|
||||
KEY idx_tms_certificate_crl_this_update (this_update),
|
||||
KEY idx_tms_certificate_crl_next_update (next_update)
|
||||
) COMMENT='证书CRL表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_certificate_crl_revoked (
|
||||
id BIGINT PRIMARY KEY,
|
||||
crl_id BIGINT NOT NULL,
|
||||
issuer_dn VARCHAR(1024) NOT NULL,
|
||||
serial_number VARCHAR(128) NOT NULL,
|
||||
subject_dn VARCHAR(1024) NULL,
|
||||
algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1',
|
||||
crl_number VARCHAR(128) NULL,
|
||||
revocation_time DATETIME(3) NOT NULL,
|
||||
revocation_reason VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
KEY idx_tms_certificate_crl_revoked_crl_id (crl_id),
|
||||
KEY idx_tms_certificate_crl_revoked_issuer_serial (issuer_dn(512), serial_number),
|
||||
KEY idx_tms_certificate_crl_revoked_subject_dn (subject_dn(512)),
|
||||
KEY idx_tms_certificate_crl_revoked_revocation_time (revocation_time)
|
||||
) COMMENT='证书CRL吊销明细表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_certificate_crl_import_task (
|
||||
id BIGINT PRIMARY KEY,
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
original_filename VARCHAR(255) NULL,
|
||||
file_size BIGINT NULL,
|
||||
status VARCHAR(32) NOT NULL COMMENT 'PENDING/RUNNING/SUCCESS/FAILED',
|
||||
fingerprint VARCHAR(128) NULL COMMENT 'SHA-256 fingerprint after successful parse',
|
||||
crl_id BIGINT NULL,
|
||||
revoked_count INT NOT NULL DEFAULT 0,
|
||||
error_message VARCHAR(512) NULL,
|
||||
started_at DATETIME(3) NULL,
|
||||
finished_at DATETIME(3) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_tms_certificate_crl_import_task_task_id (task_id),
|
||||
KEY idx_tms_certificate_crl_import_task_status (status),
|
||||
KEY idx_tms_certificate_crl_import_task_create_time (create_time)
|
||||
) COMMENT='证书CRL导入任务表';
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 日志表
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS tms_operation_audit_log (
|
||||
id BIGINT PRIMARY KEY,
|
||||
log_id VARCHAR(64) NOT NULL COMMENT '日志唯一标识',
|
||||
operator_role_code VARCHAR(64) NULL COMMENT '操作角色编码',
|
||||
operator_auth_level VARCHAR(32) NULL COMMENT '操作时认证等级',
|
||||
module_code VARCHAR(32) NOT NULL COMMENT '业务模块',
|
||||
action_type VARCHAR(32) NOT NULL COMMENT '操作动作',
|
||||
remote_ip VARCHAR(64) NULL COMMENT '来源IP',
|
||||
operation_result VARCHAR(16) NOT NULL COMMENT '操作结果: SUCCESS/FAILED/DENIED',
|
||||
summary VARCHAR(512) NOT NULL COMMENT '操作摘要',
|
||||
error_message VARCHAR(1024) NULL COMMENT '失败原因',
|
||||
audit_status VARCHAR(16) NOT NULL COMMENT '审计状态: PENDING/REVIEWED',
|
||||
audit_result VARCHAR(16) NULL COMMENT '审计结果: PASS/REJECT/NEED_VERIFY',
|
||||
audit_comment VARCHAR(1024) NULL COMMENT '审计意见',
|
||||
audited_by VARCHAR(64) NULL COMMENT '审计人',
|
||||
audited_at DATETIME(3) NULL COMMENT '审计时间',
|
||||
payload_hash VARCHAR(128) NOT NULL COMMENT 'SM3摘要',
|
||||
sign_value TEXT NOT NULL COMMENT 'SM2签名值',
|
||||
occurred_at DATETIME(3) NOT NULL COMMENT '操作发生时间',
|
||||
create_time DATETIME(3) NOT NULL COMMENT '落库时间',
|
||||
UNIQUE KEY uk_tms_operation_audit_log_log_id (log_id),
|
||||
KEY idx_tms_operation_audit_log_role_code (operator_role_code),
|
||||
KEY idx_tms_operation_audit_log_module_code (module_code),
|
||||
KEY idx_tms_operation_audit_log_action_type (action_type),
|
||||
KEY idx_tms_operation_audit_log_result (operation_result),
|
||||
KEY idx_tms_operation_audit_log_audit_status (audit_status),
|
||||
KEY idx_tms_operation_audit_log_occurred_at (occurred_at)
|
||||
) COMMENT='操作审计日志表';
|
||||
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 日志备份记录表
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS tms_log_backup_record (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
record_id VARCHAR(64) NOT NULL COMMENT '备份记录ID',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '备份文件名',
|
||||
backup_type VARCHAR(32) NOT NULL COMMENT '备份类型(如:AUTO/MANUAL)',
|
||||
file_path VARCHAR(500) NOT NULL COMMENT '文件存储路径',
|
||||
file_hash VARCHAR(128) NOT NULL COMMENT '文件哈希值',
|
||||
file_sign VARCHAR(256) DEFAULT NULL COMMENT '文件签名',
|
||||
backup_result VARCHAR(32) NOT NULL COMMENT '备份结果(SUCCESS/FAILED)',
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
sign_data TEXT DEFAULT NULL COMMENT '其他字段签名',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_record_id` (`record_id`),
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
KEY `idx_backup_type` (`backup_type`)
|
||||
) COMMENT='日志备份记录表';
|
||||
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 日志备份配置表
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS tms_backup_config (
|
||||
id int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
enable tinyint(4) NOT NULL DEFAULT '0' COMMENT '启用状态 1:启用 0:禁用',
|
||||
cron_exp varchar(64) DEFAULT NULL COMMENT 'Cron表达式',
|
||||
retention_count int(11) DEFAULT '1' COMMENT '保留份数',
|
||||
last_backup_time datetime DEFAULT NULL COMMENT '上次执行时间',
|
||||
sign_data varchar(255) NOT NULL COMMENT '其他字段的签名值',
|
||||
PRIMARY KEY (`id`)
|
||||
) COMMENT='日志备份配置表';
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tms_master_key_activate (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
|
||||
ever_initialized BOOLEAN NOT NULL,
|
||||
|
||||
activation_status BOOLEAN NOT NULL,
|
||||
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
|
||||
) COMMENT='主密钥激活状态表';
|
||||
|
||||
INSERT IGNORE INTO tms_master_key_activate (
|
||||
id,
|
||||
ever_initialized,
|
||||
activation_status
|
||||
) VALUES (1, true, true);
|
||||
|
||||
|
||||
ALTER TABLE tms_access_whitelist ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_auth_audit_log ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_auth_challenge ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_auth_full_account ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_auth_session ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_backup_config ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_certificate ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_certificate_crl ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_certificate_crl_import_task ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_certificate_crl_revoked ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_device_node ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_device_software_version ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_file_record ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_init_task ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_init_task_step ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_key_entity ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_log_backup_record ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_master_key_activate ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_operation_audit_log ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_replay_nonce ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_resource_backup_task ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_resource_restore_task ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_role_account ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_role_ukey_binding ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_security_event ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_trusted_cert ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
ALTER TABLE tms_upgrade_task ADD signature varchar(250) NULL COMMENT '数据真实性签名值';
|
||||
-- 如果这里失败,说明旧库中存在不属于上述固定席位的认证账号,需要先明确其迁移策略。
|
||||
ALTER TABLE tms_auth_full_account
|
||||
MODIFY COLUMN password_digest VARCHAR(64) NOT NULL COMMENT '口令静态摘要:SM3(password_salt + 原始口令)';
|
||||
|
||||
@@ -247,7 +247,7 @@ CREATE TABLE IF NOT EXISTS tms_auth_full_account (
|
||||
uid INT NOT NULL,
|
||||
account_name VARCHAR(64) NOT NULL,
|
||||
display_name VARCHAR(128) NOT NULL,
|
||||
password_hash VARCHAR(256) NOT NULL,
|
||||
password_digest VARCHAR(64) NOT NULL,
|
||||
password_salt VARCHAR(128) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
need_change_password TINYINT(1) NOT NULL DEFAULT 1,
|
||||
@@ -376,7 +376,7 @@ INSERT IGNORE INTO tms_auth_full_account (
|
||||
uid,
|
||||
account_name,
|
||||
display_name,
|
||||
password_hash,
|
||||
password_digest,
|
||||
password_salt,
|
||||
status,
|
||||
need_change_password,
|
||||
@@ -395,7 +395,7 @@ VALUES
|
||||
1,
|
||||
'super-admin-full-01',
|
||||
'超级管理员UKey席位一',
|
||||
'vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=',
|
||||
'00718a8319e67ef6cf9fc389c14ce16784278d55439a7a30a63fa7be8e42bbdf',
|
||||
'3df85c2988a5de2b9eeaa8109639641a',
|
||||
'ACTIVE',
|
||||
1,
|
||||
@@ -413,7 +413,7 @@ VALUES
|
||||
2,
|
||||
'super-admin-full-02',
|
||||
'超级管理员UKey席位二',
|
||||
'25e83ejpUmjU64cYAKcDV5hhqClEu6aol8YyR3Jey7E=',
|
||||
'169731d7c897649a16a073cdfddf48d2e98f72f10da5cead5cbbd51f8a9ffea6',
|
||||
'50de03346bb1f6afc74938acf5e88560',
|
||||
'ACTIVE',
|
||||
1,
|
||||
@@ -431,7 +431,7 @@ VALUES
|
||||
1,
|
||||
'key-admin-full-01',
|
||||
'密钥管理员UKey席位',
|
||||
'l65Pr7MxOvqPOe0u6ozGGZcT4raTXUgDYMQfLTINTyE=',
|
||||
'6d386b2a13550befb821795dcc0e9bda3d1cffc0d565ad539de16117e28f6a81',
|
||||
'073cd5fd30701edfcf033e8f501b2b43',
|
||||
'ACTIVE',
|
||||
1,
|
||||
@@ -449,7 +449,7 @@ VALUES
|
||||
1,
|
||||
'audit-admin-full-01',
|
||||
'审计管理员UKey席位',
|
||||
'ZRwdn3TOotyXAytO4zeSpxRm/68Rm8VcB7w7VJmZPYc=',
|
||||
'72a53b0cb8dd15775516f5085c7a3ee09d65a130dc1b17e18545d24930826d0c',
|
||||
'57e2616077bd8af5cb90fc9790f69573',
|
||||
'ACTIVE',
|
||||
1,
|
||||
@@ -467,7 +467,7 @@ VALUES
|
||||
1,
|
||||
'ops-admin-full-01',
|
||||
'运维管理员UKey席位',
|
||||
'5Wu8Mf5hC3kq4gm/62/T7pO+aZIw8Y7uYoMIztWyT1w=',
|
||||
'58655960343a1efb0c1b281ce4c384ac699d01ab61c2c710d35e54b798f6e4c5',
|
||||
'a6bea7601138e90a540b39c73d53df81',
|
||||
'ACTIVE',
|
||||
1,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<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="passwordDigest" column="password_digest"/>
|
||||
<result property="passwordSalt" column="password_salt"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="needChangePassword" column="need_change_password"/>
|
||||
@@ -29,7 +29,7 @@
|
||||
uid,
|
||||
account_name,
|
||||
display_name,
|
||||
password_hash,
|
||||
password_digest,
|
||||
password_salt,
|
||||
status,
|
||||
need_change_password,
|
||||
@@ -52,7 +52,7 @@
|
||||
uid,
|
||||
account_name,
|
||||
display_name,
|
||||
password_hash,
|
||||
password_digest,
|
||||
password_salt,
|
||||
status,
|
||||
need_change_password,
|
||||
@@ -73,7 +73,7 @@
|
||||
uid,
|
||||
account_name,
|
||||
display_name,
|
||||
password_hash,
|
||||
password_digest,
|
||||
password_salt,
|
||||
status,
|
||||
need_change_password,
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.cisd.tms;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
@@ -19,8 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
@AutoConfigureMockMvc
|
||||
class TmsApplicationTests {
|
||||
|
||||
private static final String DEFAULT_PASSWORD_DIGEST =
|
||||
"fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b";
|
||||
private static final String DEFAULT_PASSWORD = "Sunyard@123";
|
||||
|
||||
private static final Pattern AUTH_FULL_ACCOUNT_ROW = Pattern.compile(
|
||||
"\\(\\s*1210[1-5],.*?'([^']+)'\\s*,\\s*'([0-9a-f]{32})'",
|
||||
@@ -114,16 +115,23 @@ class TmsApplicationTests {
|
||||
|
||||
Matcher matcher = AUTH_FULL_ACCOUNT_ROW.matcher(sql);
|
||||
java.util.Set<String> salts = new java.util.HashSet<>();
|
||||
com.cisd.tms.modules.auth.service.impl.Pbkdf2PasswordHasher hasher =
|
||||
new com.cisd.tms.modules.auth.service.impl.Pbkdf2PasswordHasher();
|
||||
int rows = 0;
|
||||
while (matcher.find()) {
|
||||
rows++;
|
||||
String hash = matcher.group(1);
|
||||
String digest = matcher.group(1);
|
||||
String salt = matcher.group(2);
|
||||
org.junit.jupiter.api.Assertions.assertTrue(salts.add(salt), "duplicate salt: " + salt);
|
||||
org.junit.jupiter.api.Assertions.assertTrue(hasher.matches(DEFAULT_PASSWORD_DIGEST, salt, hash), "hash mismatch for salt: " + salt);
|
||||
org.junit.jupiter.api.Assertions.assertEquals(sm3Hex(salt + DEFAULT_PASSWORD), digest, "digest mismatch for salt: " + salt);
|
||||
}
|
||||
org.junit.jupiter.api.Assertions.assertEquals(5, rows);
|
||||
}
|
||||
|
||||
private static String sm3Hex(String value) {
|
||||
byte[] input = value.getBytes(StandardCharsets.UTF_8);
|
||||
SM3Digest digest = new SM3Digest();
|
||||
digest.update(input, 0, input.length);
|
||||
byte[] result = new byte[digest.getDigestSize()];
|
||||
digest.doFinal(result, 0);
|
||||
return Hex.toHexString(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ class AuthControllerTest {
|
||||
"0fffff81e971fa3f09107abf77931463fc0710bfb8962efeae3d5654b073bb0c";
|
||||
private static final String DIGEST_ABC1234 =
|
||||
"6b66c27d356b7fef3e2c2a986043031a410bb564205f22525ba022591671a7e5";
|
||||
private static final String DYNAMIC_DIGEST =
|
||||
"55f6d97a52f5f3f27541e3137297a8f807d1ffdfb6596cdd562c8989bc58dc7f";
|
||||
|
||||
@Test
|
||||
void shouldLoginWithPasswordPayload() throws Exception {
|
||||
@@ -118,7 +120,7 @@ class AuthControllerTest {
|
||||
{
|
||||
"pubKey": "PUB-1",
|
||||
"uid": 1,
|
||||
"password": "%s",
|
||||
"passwordDynamicDigest": "%s",
|
||||
"serverRandom": "RB-1",
|
||||
"issueSignature": "ISSUE-1",
|
||||
"loginPayload": "LOGIN-DATA-1",
|
||||
@@ -127,7 +129,7 @@ class AuthControllerTest {
|
||||
{
|
||||
"pubKey": "PUB-2",
|
||||
"uid": 2,
|
||||
"password": "%s",
|
||||
"passwordDynamicDigest": "%s",
|
||||
"serverRandom": "RB-2",
|
||||
"issueSignature": "ISSUE-2",
|
||||
"loginPayload": "LOGIN-DATA-2",
|
||||
@@ -135,7 +137,7 @@ class AuthControllerTest {
|
||||
}
|
||||
]
|
||||
}
|
||||
""".formatted(DIGEST_12345678, DIGEST_ABC1234)))
|
||||
""".formatted(DYNAMIC_DIGEST, DYNAMIC_DIGEST)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"token\":\"token-ukey-001\"")))
|
||||
.andExpect(content().string(containsString("\"authLevel\":\"FULL\"")));
|
||||
@@ -220,14 +222,15 @@ class AuthControllerTest {
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"oldPassword": "%s",
|
||||
"newPassword": "%s"
|
||||
"changeToken": "change-token-001",
|
||||
"oldPasswordDynamicDigest": "%s",
|
||||
"newPasswordDigest": "%s"
|
||||
}
|
||||
""".formatted(DIGEST_12345678, DIGEST_ABC1234)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"success\":true")));
|
||||
|
||||
Mockito.verify(authService).changeAccountPassword("token-change-001", 1, DIGEST_12345678, DIGEST_ABC1234);
|
||||
Mockito.verify(authService).changeAccountPassword("token-change-001", 1, "change-token-001", DIGEST_12345678, DIGEST_ABC1234);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -246,8 +249,8 @@ class AuthControllerTest {
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"oldPassword": "%s",
|
||||
"newPassword": "%s"
|
||||
"oldPasswordDigest": "%s",
|
||||
"newPasswordDigest": "%s"
|
||||
}
|
||||
""".formatted(DIGEST_12345678, DIGEST_ABC1234)))
|
||||
.andExpect(status().isOk())
|
||||
@@ -303,7 +306,7 @@ class AuthControllerTest {
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
MasterKeyBackupPacket packet = new MasterKeyBackupPacket();
|
||||
packet.setPacketIndex(1);
|
||||
UKeySignResult response = UKeySignResult.builder().sign("ISSUE-SIGN-001").backupPacket(packet).build();
|
||||
UKeySignResult response = UKeySignResult.builder().sign("ISSUE-SIGN-001").passwordSalt("SALT-001").backupPacket(packet).build();
|
||||
Mockito.when(authAdminService.issueUkeyBindingSign(
|
||||
ArgumentMatchers.isNull(),
|
||||
ArgumentMatchers.isNull(),
|
||||
@@ -328,6 +331,7 @@ class AuthControllerTest {
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"sign\":\"ISSUE-SIGN-001\"")))
|
||||
.andExpect(content().string(containsString("\"passwordSalt\":\"SALT-001\"")))
|
||||
.andExpect(content().string(containsString("\"packetIndex\":1")));
|
||||
}
|
||||
|
||||
@@ -335,7 +339,7 @@ class AuthControllerTest {
|
||||
void shouldIssueNormalAdminUkeyBindingSignThroughAdminEndpoint() throws Exception {
|
||||
AuthService authService = Mockito.mock(AuthService.class);
|
||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||
UKeySignResult response = UKeySignResult.builder().sign("ISSUE-SIGN-KEY-001").build();
|
||||
UKeySignResult response = UKeySignResult.builder().sign("ISSUE-SIGN-KEY-001").passwordSalt("SALT-KEY").build();
|
||||
Mockito.when(authAdminService.issueUkeyBindingSign(
|
||||
ArgumentMatchers.eq("SUPER_ADMIN"),
|
||||
ArgumentMatchers.eq("FULL"),
|
||||
@@ -360,7 +364,8 @@ class AuthControllerTest {
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("\"sign\":\"ISSUE-SIGN-KEY-001\"")));
|
||||
.andExpect(content().string(containsString("\"sign\":\"ISSUE-SIGN-KEY-001\"")))
|
||||
.andExpect(content().string(containsString("\"passwordSalt\":\"SALT-KEY\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.mockito.Mockito;
|
||||
class AuthAdminServiceTest {
|
||||
|
||||
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-03-23T02:00:00Z"), ZoneOffset.UTC);
|
||||
private static final String DEFAULT_PASSWORD_DIGEST = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b";
|
||||
private static final String DIGEST_OLD1234 = "5237f20185252395f6ce351f8e742085d6ab1207ddda94b085b409e41026fc04";
|
||||
private static final String DIGEST_NEW1234 = "6b66c27d356b7fef3e2c2a986043031a410bb564205f22525ba022591671a7e5";
|
||||
private static final String DIGEST_BAD_PASSWORD = "17cb3cec9c337826293158dd5623c75268a2eee06123ba53b9aef5b790c04e51";
|
||||
@@ -52,10 +51,10 @@ class AuthAdminServiceTest {
|
||||
|
||||
AuthFullAccountEntity first = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow();
|
||||
AuthFullAccountEntity second = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 2).orElseThrow();
|
||||
Assertions.assertEquals("salt-reset-1", first.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:" + DEFAULT_PASSWORD_DIGEST + ":salt-reset-1", first.getPasswordHash());
|
||||
Assertions.assertEquals("salt-reset-2", second.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:" + DEFAULT_PASSWORD_DIGEST + ":salt-reset-2", second.getPasswordHash());
|
||||
Assertions.assertEquals("OLD-SALT-1", first.getPasswordSalt());
|
||||
Assertions.assertEquals("434685979fe125d142bfa1568d61ca1c6349bb54befb766617a1502d07bdc6c3", first.getPasswordDigest());
|
||||
Assertions.assertEquals("OLD-SALT-2", second.getPasswordSalt());
|
||||
Assertions.assertEquals("dd6eae63d8a5776a4ed2b01cfac3d0b31ae15f331dfa507cd833da3ea5b504e4", second.getPasswordDigest());
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(first.getNeedChangePassword()));
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(second.getNeedChangePassword()));
|
||||
}
|
||||
@@ -65,7 +64,7 @@ class AuthAdminServiceTest {
|
||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
||||
roles.save(role(RoleCode.KEY_ADMIN, RoleAccountStatus.ACTIVE));
|
||||
accounts.save(account(RoleCode.KEY_ADMIN, 1, "key-admin-01", "HASH:" + DIGEST_OLD1234 + ":SALT-OLD", "SALT-OLD"));
|
||||
accounts.save(account(RoleCode.KEY_ADMIN, 1, "key-admin-01", DIGEST_OLD1234, "SALT-OLD"));
|
||||
|
||||
service(roles, accounts, new InMemoryRoleUkeyBindingRepository(), Mockito.mock(LmkService.class))
|
||||
.changeAccountPassword(
|
||||
@@ -78,8 +77,8 @@ class AuthAdminServiceTest {
|
||||
);
|
||||
|
||||
AuthFullAccountEntity changed = accounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
||||
Assertions.assertEquals("salt-reset-1", changed.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:" + DIGEST_NEW1234 + ":salt-reset-1", changed.getPasswordHash());
|
||||
Assertions.assertEquals("SALT-OLD", changed.getPasswordSalt());
|
||||
Assertions.assertEquals(DIGEST_NEW1234, changed.getPasswordDigest());
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||
}
|
||||
|
||||
@@ -88,7 +87,7 @@ class AuthAdminServiceTest {
|
||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
||||
roles.save(role(RoleCode.KEY_ADMIN, RoleAccountStatus.ACTIVE));
|
||||
accounts.save(account(RoleCode.KEY_ADMIN, 1, "key-admin-01", "HASH:" + DIGEST_OLD1234 + ":SALT-OLD", "SALT-OLD"));
|
||||
accounts.save(account(RoleCode.KEY_ADMIN, 1, "key-admin-01", DIGEST_OLD1234, "SALT-OLD"));
|
||||
|
||||
BizException exception = Assertions.assertThrows(BizException.class,
|
||||
() -> service(roles, accounts, new InMemoryRoleUkeyBindingRepository(), Mockito.mock(LmkService.class))
|
||||
@@ -132,6 +131,7 @@ class AuthAdminServiceTest {
|
||||
.issueUkeyBindingSign(RoleCode.SUPER_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.SUPER_ADMIN.getCode(), dto);
|
||||
|
||||
Assertions.assertEquals("ISSUE-SIGN-REBIND", result.getSign());
|
||||
Assertions.assertEquals("SALT-OLD", result.getPasswordSalt());
|
||||
Assertions.assertNotNull(result.getBackupPacket());
|
||||
List<RoleUkeyBindingEntity> activeUidBindings = bindings.findActiveByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1);
|
||||
Assertions.assertEquals(2, activeUidBindings.size());
|
||||
@@ -184,6 +184,7 @@ class AuthAdminServiceTest {
|
||||
.issueUkeyBindingSign(RoleCode.SUPER_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.KEY_ADMIN.getCode(), dto);
|
||||
|
||||
Assertions.assertEquals("ISSUE-SIGN-NEW", result.getSign());
|
||||
Assertions.assertEquals("SALT-OLD", result.getPasswordSalt());
|
||||
Assertions.assertNull(result.getBackupPacket());
|
||||
List<RoleUkeyBindingEntity> activeBindings = bindings.findActiveByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1);
|
||||
Assertions.assertEquals(1, activeBindings.size());
|
||||
@@ -394,8 +395,7 @@ class AuthAdminServiceTest {
|
||||
new FakePasswordHasher(),
|
||||
lmkService,
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
new FixedSaltSupplier("salt-reset-1", "salt-reset-2", "salt-reset-3")
|
||||
FIXED_CLOCK
|
||||
);
|
||||
}
|
||||
|
||||
@@ -416,7 +416,7 @@ class AuthAdminServiceTest {
|
||||
entity.setUid(uid);
|
||||
entity.setAccountName(accountName);
|
||||
entity.setDisplayName(accountName);
|
||||
entity.setPasswordHash(passwordHash);
|
||||
entity.setPasswordDigest(passwordHash);
|
||||
entity.setPasswordSalt(passwordSalt);
|
||||
entity.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
entity.setNeedChangePassword(Boolean.FALSE);
|
||||
@@ -440,29 +440,12 @@ class AuthAdminServiceTest {
|
||||
private static class FakePasswordHasher implements PasswordHasher {
|
||||
@Override
|
||||
public String hash(String password, String salt) {
|
||||
return "HASH:" + password + ":" + salt;
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String rawPassword, String salt, String expectedHash) {
|
||||
return hash(rawPassword, salt).equals(expectedHash);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FixedSaltSupplier implements PasswordSaltGenerator {
|
||||
private final List<String> salts;
|
||||
private int index = 0;
|
||||
|
||||
private FixedSaltSupplier(String... salts) {
|
||||
this.salts = List.of(salts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nextSalt() {
|
||||
if (index >= salts.size()) {
|
||||
return salts.get(salts.size() - 1);
|
||||
}
|
||||
return salts.get(index++);
|
||||
return rawPassword.equals(expectedHash);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ import org.mockito.Mockito;
|
||||
class AuthSecurityResetServiceTest {
|
||||
|
||||
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-05-13T08:00:00Z"), ZoneOffset.UTC);
|
||||
private static final String FIRST_SUPER_ADMIN_PASSWORD_HASH = "vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=";
|
||||
private static final String FIRST_SUPER_ADMIN_PASSWORD_DIGEST =
|
||||
"00718a8319e67ef6cf9fc389c14ce16784278d55439a7a30a63fa7be8e42bbdf";
|
||||
|
||||
@Test
|
||||
void shouldClearBindingsSessionsAndRestoreSeededRolesAndPasswords() {
|
||||
@@ -89,7 +90,7 @@ class AuthSecurityResetServiceTest {
|
||||
Assertions.assertEquals("super-admin-full-01", firstSuperAdmin.getAccountName());
|
||||
Assertions.assertEquals("超级管理员UKey席位一", firstSuperAdmin.getDisplayName());
|
||||
Assertions.assertEquals("3df85c2988a5de2b9eeaa8109639641a", firstSuperAdmin.getPasswordSalt());
|
||||
Assertions.assertEquals(FIRST_SUPER_ADMIN_PASSWORD_HASH, firstSuperAdmin.getPasswordHash());
|
||||
Assertions.assertEquals(FIRST_SUPER_ADMIN_PASSWORD_DIGEST, firstSuperAdmin.getPasswordDigest());
|
||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), firstSuperAdmin.getStatus());
|
||||
Assertions.assertTrue(Boolean.TRUE.equals(firstSuperAdmin.getNeedChangePassword()));
|
||||
Assertions.assertEquals(0, firstSuperAdmin.getFailedCount());
|
||||
@@ -114,7 +115,7 @@ class AuthSecurityResetServiceTest {
|
||||
entity.setUid(uid);
|
||||
entity.setAccountName("dirty");
|
||||
entity.setDisplayName("dirty");
|
||||
entity.setPasswordHash("dirty");
|
||||
entity.setPasswordDigest("dirty");
|
||||
entity.setPasswordSalt("dirty");
|
||||
entity.setStatus(RoleAccountStatus.LOCKED.name());
|
||||
entity.setNeedChangePassword(Boolean.FALSE);
|
||||
|
||||
@@ -36,6 +36,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -99,7 +101,7 @@ class AuthServiceTest {
|
||||
|
||||
UkeyLoginRequest ukeyRequest = new UkeyLoginRequest();
|
||||
ukeyRequest.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
||||
ukeyRequest.setLoginFactors(List.of(proofWithPassword("PUB-1", 1, DIGEST_ABC1234, "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1")));
|
||||
ukeyRequest.setLoginFactors(List.of(proofWithPassword("PUB-1", 1, sm3Hex(DIGEST_ABC1234 + "RB-1"), "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1")));
|
||||
LoginResponse response = service.ukeyLogin(ukeyRequest);
|
||||
|
||||
Assertions.assertEquals(AuthLevel.FULL.name(), response.getAuthLevel());
|
||||
@@ -109,6 +111,34 @@ class AuthServiceTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangePasswordWithBackendIssuedNewSaltAndDynamicOldDigest() {
|
||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
||||
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
||||
roles.save(activeRole(RoleCode.KEY_ADMIN));
|
||||
accounts.save(activeAccount(RoleCode.KEY_ADMIN, 1, "key-admin-01", DIGEST_OLD1234, "SALT-OLD", true));
|
||||
AuthSessionEntity session = session("token-change-prepare", RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name());
|
||||
session.setAuthenticatedPrincipalsJson("[{\"type\":\"FULL\",\"uid\":1,\"username\":\"key-admin-01\"}]");
|
||||
sessions.save(session);
|
||||
|
||||
AuthService service = newAuthService(roles, accounts, sessions, new InMemoryRoleUkeyBindingRepository(), () -> "unused");
|
||||
|
||||
var prepare = service.prepareAccountPasswordChange("token-change-prepare", 1);
|
||||
service.changeAccountPassword(
|
||||
"token-change-prepare",
|
||||
1,
|
||||
prepare.getChangeToken(),
|
||||
sm3Hex(DIGEST_OLD1234 + prepare.getOldServerRandom()),
|
||||
DIGEST_NEW1234
|
||||
);
|
||||
|
||||
AuthFullAccountEntity changed = accounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
||||
Assertions.assertEquals("salt-test", changed.getPasswordSalt());
|
||||
Assertions.assertEquals(DIGEST_NEW1234, changed.getPasswordDigest());
|
||||
Assertions.assertFalse(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangeUnifiedPasswordFromLimitedSessionAndAffectUkeyLogin() {
|
||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||
@@ -124,11 +154,18 @@ class AuthServiceTest {
|
||||
|
||||
AuthService service = newAuthService(roles, accounts, sessions, bindings, () -> "token-after-change");
|
||||
|
||||
service.changeAccountPassword("token-change-001", 1, DIGEST_OLD1234, DIGEST_NEW1234);
|
||||
var prepare = service.prepareAccountPasswordChange("token-change-001", 1);
|
||||
service.changeAccountPassword(
|
||||
"token-change-001",
|
||||
1,
|
||||
prepare.getChangeToken(),
|
||||
sm3Hex(DIGEST_OLD1234 + prepare.getOldServerRandom()),
|
||||
DIGEST_NEW1234
|
||||
);
|
||||
|
||||
AuthFullAccountEntity changed = accounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
||||
Assertions.assertEquals("salt-test", changed.getPasswordSalt());
|
||||
Assertions.assertEquals("HASH:" + DIGEST_NEW1234 + ":salt-test", changed.getPasswordHash());
|
||||
Assertions.assertEquals(DIGEST_NEW1234, changed.getPasswordDigest());
|
||||
Assertions.assertFalse(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||
}
|
||||
|
||||
@@ -198,8 +235,8 @@ class AuthServiceTest {
|
||||
UkeyLoginRequest request = new UkeyLoginRequest();
|
||||
request.setRoleCode(RoleCode.SUPER_ADMIN.getCode());
|
||||
request.setLoginFactors(List.of(
|
||||
proofWithPassword("PUB-1-BACKUP", 1, DIGEST_11111111, "RB-1", "ISSUE-1B", "LOGIN-DATA-1B", "LOGIN-SIGN-1B"),
|
||||
proofWithPassword("PUB-2-BACKUP", 2, DIGEST_22222222, "RB-2", "ISSUE-2B", "LOGIN-DATA-2B", "LOGIN-SIGN-2B")
|
||||
proofWithPassword("PUB-1-BACKUP", 1, sm3Hex(DIGEST_11111111 + "RB-1"), "RB-1", "ISSUE-1B", "LOGIN-DATA-1B", "LOGIN-SIGN-1B"),
|
||||
proofWithPassword("PUB-2-BACKUP", 2, sm3Hex(DIGEST_22222222 + "RB-2"), "RB-2", "ISSUE-2B", "LOGIN-DATA-2B", "LOGIN-SIGN-2B")
|
||||
));
|
||||
|
||||
LoginResponse response = service.ukeyLogin(request);
|
||||
@@ -223,7 +260,7 @@ class AuthServiceTest {
|
||||
|
||||
BizException exception = Assertions.assertThrows(BizException.class,
|
||||
() -> newAuthService(roles, accounts, sessions, new InMemoryRoleUkeyBindingRepository(), () -> "unused")
|
||||
.changeAccountPassword("token-expired-001", 1, DIGEST_12345678, DIGEST_ABC1234));
|
||||
.prepareAccountPasswordChange("token-expired-001", 1));
|
||||
|
||||
Assertions.assertEquals(ErrorCode.SESSION_INVALID.getCode(), exception.getCode());
|
||||
Assertions.assertEquals("会话已过期", exception.getMessage());
|
||||
@@ -328,7 +365,7 @@ class AuthServiceTest {
|
||||
entity.setAccountName(accountName);
|
||||
entity.setDisplayName(accountName);
|
||||
entity.setPasswordSalt(salt);
|
||||
entity.setPasswordHash("HASH:" + password + ":" + salt);
|
||||
entity.setPasswordDigest(password);
|
||||
entity.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||
entity.setNeedChangePassword(needChangePassword);
|
||||
entity.setPasswordChangedAt(LocalDateTime.of(2026, 3, 23, 1, 0));
|
||||
@@ -387,15 +424,24 @@ class AuthServiceTest {
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static String sm3Hex(String value) {
|
||||
byte[] input = value.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
SM3Digest digest = new SM3Digest();
|
||||
digest.update(input, 0, input.length);
|
||||
byte[] result = new byte[digest.getDigestSize()];
|
||||
digest.doFinal(result, 0);
|
||||
return Hex.toHexString(result);
|
||||
}
|
||||
|
||||
private static class FakePasswordHasher implements PasswordHasher {
|
||||
@Override
|
||||
public String hash(String password, String salt) {
|
||||
return "HASH:" + password + ":" + salt;
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String rawPassword, String salt, String expectedHash) {
|
||||
return hash(rawPassword, salt).equals(expectedHash);
|
||||
return rawPassword.equals(expectedHash);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user