fix:登录流程改造
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
# TMS 初始化 SQL 执行说明
|
# 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 客户端里一次性执行整份文件,出现下面这类报错:
|
如果你在 TiDB 客户端里一次性执行整份文件,出现下面这类报错:
|
||||||
|
|
||||||
@@ -18,7 +20,7 @@ SQL Error [8130] [HY000]: client has multi-statement capability disabled
|
|||||||
|
|
||||||
优先用客户端的 `Run Script` / `Execute Script` 模式执行:
|
优先用客户端的 `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
|
```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
|
```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;
|
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`
|
- `AUDIT_ADMIN`
|
||||||
- `OPS_ADMIN`
|
- `OPS_ADMIN`
|
||||||
|
|
||||||
默认口令均为 `12345678`,其中:
|
默认口令均为 `Sunyard@123`,其中:
|
||||||
|
|
||||||
- `KEY_ADMIN` 默认 `ACTIVE`
|
- `KEY_ADMIN` 默认 `ACTIVE`
|
||||||
- 其他角色默认 `UNENABLED`
|
- 其他角色默认 `UNENABLED`
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public class AuthController {
|
|||||||
private final AuthService authService;
|
private final AuthService authService;
|
||||||
|
|
||||||
@PostMapping("/password-login")
|
@PostMapping("/password-login")
|
||||||
|
@Deprecated
|
||||||
@Operation(summary = "口令登录", description = "按旧系统受限登录流程签发 LIMITED 会话。")
|
@Operation(summary = "口令登录", description = "按旧系统受限登录流程签发 LIMITED 会话。")
|
||||||
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.LOGIN, summary = "口令登录")
|
@AuditedOperation(module = ModuleCode.AUTH, action = ActionType.LOGIN, summary = "口令登录")
|
||||||
public ApiResponse<LoginResponse> passwordLogin(@Valid @RequestBody PasswordLoginRequest request) {
|
public ApiResponse<LoginResponse> passwordLogin(@Valid @RequestBody PasswordLoginRequest request) {
|
||||||
@@ -79,9 +80,23 @@ public class AuthController {
|
|||||||
authService.changeAccountPassword(
|
authService.changeAccountPassword(
|
||||||
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN),
|
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN),
|
||||||
uid,
|
uid,
|
||||||
request.getCurrentPassword(),
|
request.getChangeToken(),
|
||||||
request.getNewPassword()
|
request.getOldPasswordDynamicDigest(),
|
||||||
|
request.getNewPasswordDigest()
|
||||||
);
|
);
|
||||||
return ApiResponse.success();
|
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;
|
package com.cisd.tms.modules.auth.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
@Schema(description = "管理员修改账号口令请求")
|
@Schema(description = "管理员修改账号口令请求")
|
||||||
public class AdminChangePasswordRequest {
|
public class AdminChangePasswordRequest {
|
||||||
|
|
||||||
@NotBlank(message = "oldPassword不能为空")
|
@NotBlank(message = "oldPasswordDigest不能为空")
|
||||||
@Schema(description = "旧口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
@JsonAlias("oldPasswordDynamicDigest")
|
||||||
private String oldPassword;
|
@Schema(description = "旧口令静态摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||||
|
private String oldPasswordDigest;
|
||||||
|
|
||||||
@NotBlank(message = "newPassword不能为空")
|
@NotBlank(message = "newPasswordDigest不能为空")
|
||||||
@Schema(description = "新口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
@Schema(description = "新口令静态摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||||
private String newPassword;
|
private String newPasswordDigest;
|
||||||
|
|
||||||
public String getOldPassword() {
|
public String getOldPassword() {
|
||||||
return oldPassword;
|
return oldPasswordDigest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOldPassword(String oldPassword) {
|
public void setOldPassword(String oldPassword) {
|
||||||
this.oldPassword = oldPassword;
|
this.oldPasswordDigest = oldPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNewPassword() {
|
public String getNewPassword() {
|
||||||
return newPassword;
|
return newPasswordDigest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNewPassword(String newPassword) {
|
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 = "修改当前认证账户口令请求")
|
@Schema(description = "修改当前认证账户口令请求")
|
||||||
public class ChangePasswordRequest {
|
public class ChangePasswordRequest {
|
||||||
|
|
||||||
@NotBlank(message = "oldPassword不能为空")
|
@NotBlank(message = "changeToken不能为空")
|
||||||
@JsonAlias("currentPassword")
|
@Schema(description = "改密准备接口返回的确认令牌")
|
||||||
@Schema(description = "旧口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
private String changeToken;
|
||||||
private String oldPassword;
|
|
||||||
|
|
||||||
@NotBlank(message = "newPassword不能为空")
|
@NotBlank(message = "oldPasswordDynamicDigest不能为空")
|
||||||
@Schema(description = "新口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
@JsonAlias({"oldPassword", "currentPassword"})
|
||||||
private String newPassword;
|
@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() {
|
public String getOldPassword() {
|
||||||
return oldPassword;
|
return oldPasswordDynamicDigest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOldPassword(String oldPassword) {
|
public void setOldPassword(String oldPassword) {
|
||||||
this.oldPassword = oldPassword;
|
this.oldPasswordDynamicDigest = oldPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public String getCurrentPassword() {
|
public String getCurrentPassword() {
|
||||||
return oldPassword;
|
return oldPasswordDynamicDigest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public void setCurrentPassword(String currentPassword) {
|
public void setCurrentPassword(String currentPassword) {
|
||||||
this.oldPassword = currentPassword;
|
this.oldPasswordDynamicDigest = currentPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNewPassword() {
|
public String getNewPassword() {
|
||||||
return newPassword;
|
return newPasswordDigest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNewPassword(String newPassword) {
|
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 Integer uid;
|
||||||
private String password;
|
private String password;
|
||||||
|
private String serverRandom;
|
||||||
|
|
||||||
public Integer getUid() {
|
public Integer getUid() {
|
||||||
return uid;
|
return uid;
|
||||||
@@ -20,4 +21,12 @@ public class FullLoginAccountRequest {
|
|||||||
public void setPassword(String password) {
|
public void setPassword(String password) {
|
||||||
this.password = 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;
|
package com.cisd.tms.modules.auth.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
@@ -28,8 +29,10 @@ public class UkeyLoginProof {
|
|||||||
@NotBlank(message = "loginSignature不能为空")
|
@NotBlank(message = "loginSignature不能为空")
|
||||||
private String loginSignature;
|
private String loginSignature;
|
||||||
|
|
||||||
@Schema(description = "当前 UKey 固定席位对应账号口令的SM3摘要,字段名保持password兼容前端", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
@NotBlank(message = "passwordDynamicDigest不能为空")
|
||||||
private String password;
|
@JsonAlias("password")
|
||||||
|
@Schema(description = "当前 UKey 固定席位对应账号口令动态摘要:SM3(passwordDigest + serverRandom)", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
|
||||||
|
private String passwordDynamicDigest;
|
||||||
|
|
||||||
public String getPubKey() {
|
public String getPubKey() {
|
||||||
return pubKey;
|
return pubKey;
|
||||||
@@ -80,10 +83,18 @@ public class UkeyLoginProof {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getPassword() {
|
public String getPassword() {
|
||||||
return password;
|
return passwordDynamicDigest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPassword(String password) {
|
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 Integer uid;
|
||||||
private String accountName;
|
private String accountName;
|
||||||
private String displayName;
|
private String displayName;
|
||||||
private String passwordHash;
|
private String passwordDigest;
|
||||||
private String passwordSalt;
|
private String passwordSalt;
|
||||||
private String status;
|
private String status;
|
||||||
private Boolean needChangePassword;
|
private Boolean needChangePassword;
|
||||||
@@ -53,12 +53,12 @@ public class AuthFullAccountEntity extends BaseEntity {
|
|||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPasswordHash() {
|
public String getPasswordDigest() {
|
||||||
return passwordHash;
|
return passwordDigest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPasswordHash(String passwordHash) {
|
public void setPasswordDigest(String passwordDigest) {
|
||||||
this.passwordHash = passwordHash;
|
this.passwordDigest = passwordDigest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPasswordSalt() {
|
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.LoginRequest;
|
||||||
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
import com.cisd.tms.modules.auth.dto.LoginResponse;
|
||||||
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
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.UkeyLoginRandomRequest;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginRequest;
|
||||||
@@ -27,5 +28,13 @@ public interface AuthService {
|
|||||||
|
|
||||||
void logout(String sessionToken);
|
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.AuthAdminService;
|
||||||
import com.cisd.tms.modules.auth.service.PasswordDigestValidator;
|
import com.cisd.tms.modules.auth.service.PasswordDigestValidator;
|
||||||
import com.cisd.tms.modules.auth.service.PasswordHasher;
|
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.MasterKeyBackupPacket;
|
||||||
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
|
||||||
import com.cisd.tms.modules.mk.dto.UKeySignEntity;
|
import com.cisd.tms.modules.mk.dto.UKeySignEntity;
|
||||||
@@ -43,7 +42,6 @@ import org.springframework.stereotype.Service;
|
|||||||
public class AuthAdminServiceImpl implements AuthAdminService {
|
public class AuthAdminServiceImpl implements AuthAdminService {
|
||||||
|
|
||||||
static final String DEFAULT_PASSWORD = "Sunyard@123";
|
static final String DEFAULT_PASSWORD = "Sunyard@123";
|
||||||
private static final String DEFAULT_PASSWORD_DIGEST = PasswordDigestSupport.sm3Hex(DEFAULT_PASSWORD);
|
|
||||||
|
|
||||||
private final RoleAccountRepository roleAccountRepository;
|
private final RoleAccountRepository roleAccountRepository;
|
||||||
private final AuthFullAccountRepository authFullAccountRepository;
|
private final AuthFullAccountRepository authFullAccountRepository;
|
||||||
@@ -52,7 +50,6 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
private final LmkService lmkService;
|
private final LmkService lmkService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final PasswordSaltGenerator passwordSaltGenerator;
|
|
||||||
|
|
||||||
private void enableRoleAfterUkeyBindingIfReady(RoleCode targetRole) {
|
private void enableRoleAfterUkeyBindingIfReady(RoleCode targetRole) {
|
||||||
if (!hasRequiredActiveUkeyBindings(targetRole)) {
|
if (!hasRequiredActiveUkeyBindings(targetRole)) {
|
||||||
@@ -105,9 +102,8 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在");
|
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在");
|
||||||
}
|
}
|
||||||
for (AuthFullAccountEntity account : fullAccounts) {
|
for (AuthFullAccountEntity account : fullAccounts) {
|
||||||
String newSalt = passwordSaltGenerator.nextSalt();
|
String salt = account.getPasswordSalt();
|
||||||
account.setPasswordSalt(newSalt);
|
account.setPasswordDigest(passwordHasher.hash(PasswordDigestSupport.sm3Hex(salt + DEFAULT_PASSWORD), salt));
|
||||||
account.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD_DIGEST, newSalt));
|
|
||||||
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
account.setNeedChangePassword(Boolean.TRUE);
|
account.setNeedChangePassword(Boolean.TRUE);
|
||||||
account.setPasswordChangedAt(current);
|
account.setPasswordChangedAt(current);
|
||||||
@@ -170,7 +166,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
}
|
}
|
||||||
AuthFullAccountEntity account = authFullAccountRepository.findByRoleCodeAndUid(targetRoleCode, uid)
|
AuthFullAccountEntity account = authFullAccountRepository.findByRoleCodeAndUid(targetRoleCode, uid)
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在"));
|
.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(), "旧密码错误");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "旧密码错误");
|
||||||
}
|
}
|
||||||
applyAdminPasswordChange(account, newPassword);
|
applyAdminPasswordChange(account, newPassword);
|
||||||
@@ -431,6 +427,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
enableRoleAfterUkeyBindingIfReady(targetRole);
|
enableRoleAfterUkeyBindingIfReady(targetRole);
|
||||||
return UKeySignResult.builder()
|
return UKeySignResult.builder()
|
||||||
.sign(signValue)
|
.sign(signValue)
|
||||||
|
.passwordSalt(loadAccount(targetRole.getCode(), request.getUid()).getPasswordSalt())
|
||||||
.backupPacket(backupPacket)
|
.backupPacket(backupPacket)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@@ -466,9 +463,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applyAdminPasswordChange(AuthFullAccountEntity account, String newPassword) {
|
private void applyAdminPasswordChange(AuthFullAccountEntity account, String newPassword) {
|
||||||
String newSalt = passwordSaltGenerator.nextSalt();
|
account.setPasswordDigest(passwordHasher.hash(newPassword, account.getPasswordSalt()));
|
||||||
account.setPasswordSalt(newSalt);
|
|
||||||
account.setPasswordHash(passwordHasher.hash(newPassword, newSalt));
|
|
||||||
account.setNeedChangePassword(Boolean.TRUE);
|
account.setNeedChangePassword(Boolean.TRUE);
|
||||||
account.setPasswordChangedAt(now());
|
account.setPasswordChangedAt(now());
|
||||||
account.setFailedCount(0);
|
account.setFailedCount(0);
|
||||||
@@ -484,6 +479,11 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色账号不存在"));
|
.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) {
|
private void validateRoleUid(RoleCode targetRole, Integer uid) {
|
||||||
if (uid == null || uid < 1 || uid > targetRole.getRequiredUkeyCount()) {
|
if (uid == null || uid < 1 || uid > targetRole.getRequiredUkeyCount()) {
|
||||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "uid超出角色UKey席位要求");
|
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.setUid(seed.uid());
|
||||||
account.setAccountName(seed.accountName());
|
account.setAccountName(seed.accountName());
|
||||||
account.setDisplayName(seed.displayName());
|
account.setDisplayName(seed.displayName());
|
||||||
account.setPasswordHash(seed.passwordHash());
|
account.setPasswordDigest(seed.passwordDigest());
|
||||||
account.setPasswordSalt(seed.passwordSalt());
|
account.setPasswordSalt(seed.passwordSalt());
|
||||||
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
account.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
account.setNeedChangePassword(Boolean.TRUE);
|
account.setNeedChangePassword(Boolean.TRUE);
|
||||||
@@ -120,7 +120,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
|||||||
1,
|
1,
|
||||||
"super-admin-full-01",
|
"super-admin-full-01",
|
||||||
"超级管理员UKey席位一",
|
"超级管理员UKey席位一",
|
||||||
"vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=",
|
"00718a8319e67ef6cf9fc389c14ce16784278d55439a7a30a63fa7be8e42bbdf",
|
||||||
"3df85c2988a5de2b9eeaa8109639641a"
|
"3df85c2988a5de2b9eeaa8109639641a"
|
||||||
),
|
),
|
||||||
new SeedFullAccount(
|
new SeedFullAccount(
|
||||||
@@ -129,7 +129,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
|||||||
2,
|
2,
|
||||||
"super-admin-full-02",
|
"super-admin-full-02",
|
||||||
"超级管理员UKey席位二",
|
"超级管理员UKey席位二",
|
||||||
"25e83ejpUmjU64cYAKcDV5hhqClEu6aol8YyR3Jey7E=",
|
"169731d7c897649a16a073cdfddf48d2e98f72f10da5cead5cbbd51f8a9ffea6",
|
||||||
"50de03346bb1f6afc74938acf5e88560"
|
"50de03346bb1f6afc74938acf5e88560"
|
||||||
),
|
),
|
||||||
new SeedFullAccount(
|
new SeedFullAccount(
|
||||||
@@ -138,7 +138,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
|||||||
1,
|
1,
|
||||||
"key-admin-full-01",
|
"key-admin-full-01",
|
||||||
"密钥管理员UKey席位",
|
"密钥管理员UKey席位",
|
||||||
"l65Pr7MxOvqPOe0u6ozGGZcT4raTXUgDYMQfLTINTyE=",
|
"6d386b2a13550befb821795dcc0e9bda3d1cffc0d565ad539de16117e28f6a81",
|
||||||
"073cd5fd30701edfcf033e8f501b2b43"
|
"073cd5fd30701edfcf033e8f501b2b43"
|
||||||
),
|
),
|
||||||
new SeedFullAccount(
|
new SeedFullAccount(
|
||||||
@@ -147,7 +147,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
|||||||
1,
|
1,
|
||||||
"audit-admin-full-01",
|
"audit-admin-full-01",
|
||||||
"审计管理员UKey席位",
|
"审计管理员UKey席位",
|
||||||
"ZRwdn3TOotyXAytO4zeSpxRm/68Rm8VcB7w7VJmZPYc=",
|
"72a53b0cb8dd15775516f5085c7a3ee09d65a130dc1b17e18545d24930826d0c",
|
||||||
"57e2616077bd8af5cb90fc9790f69573"
|
"57e2616077bd8af5cb90fc9790f69573"
|
||||||
),
|
),
|
||||||
new SeedFullAccount(
|
new SeedFullAccount(
|
||||||
@@ -156,7 +156,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
|||||||
1,
|
1,
|
||||||
"ops-admin-full-01",
|
"ops-admin-full-01",
|
||||||
"运维管理员UKey席位",
|
"运维管理员UKey席位",
|
||||||
"5Wu8Mf5hC3kq4gm/62/T7pO+aZIw8Y7uYoMIztWyT1w=",
|
"58655960343a1efb0c1b281ce4c384ac699d01ab61c2c710d35e54b798f6e4c5",
|
||||||
"a6bea7601138e90a540b39c73d53df81"
|
"a6bea7601138e90a540b39c73d53df81"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -171,7 +171,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
|
|||||||
int uid,
|
int uid,
|
||||||
String accountName,
|
String accountName,
|
||||||
String displayName,
|
String displayName,
|
||||||
String passwordHash,
|
String passwordDigest,
|
||||||
String passwordSalt
|
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.LoginResponse;
|
||||||
import com.cisd.tms.modules.auth.dto.PasswordLoginAccountRequest;
|
import com.cisd.tms.modules.auth.dto.PasswordLoginAccountRequest;
|
||||||
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
import com.cisd.tms.modules.auth.dto.PasswordLoginRequest;
|
||||||
|
import com.cisd.tms.modules.auth.dto.PasswordChangePrepareResponse;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginProof;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginProof;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomRequest;
|
||||||
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
|
||||||
@@ -52,6 +53,8 @@ import java.util.LinkedHashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -79,6 +82,7 @@ AuthServiceImpl implements AuthService {
|
|||||||
private final SessionTokenGenerator sessionTokenGenerator;
|
private final SessionTokenGenerator sessionTokenGenerator;
|
||||||
private final PasswordSaltGenerator passwordSaltGenerator;
|
private final PasswordSaltGenerator passwordSaltGenerator;
|
||||||
private final AuthPolicyService authPolicyService;
|
private final AuthPolicyService authPolicyService;
|
||||||
|
private final Map<String, PendingPasswordChange> pendingPasswordChanges = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoginResponse login(LoginRequest request) {
|
public LoginResponse login(LoginRequest request) {
|
||||||
@@ -205,9 +209,7 @@ AuthServiceImpl implements AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void changeAccountPassword(String sessionToken, Integer uid, String currentPassword, String newPassword) {
|
public PasswordChangePrepareResponse prepareAccountPasswordChange(String sessionToken, Integer uid) {
|
||||||
PasswordDigestValidator.validate(currentPassword);
|
|
||||||
PasswordDigestValidator.validate(newPassword);
|
|
||||||
AuthSessionEntity session = requireActiveSession(sessionToken);
|
AuthSessionEntity session = requireActiveSession(sessionToken);
|
||||||
if (uid == null || !containsAuthenticatedUid(session, uid)) {
|
if (uid == null || !containsAuthenticatedUid(session, uid)) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "当前会话未完成账号认证");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "当前会话未完成账号认证");
|
||||||
@@ -215,14 +217,60 @@ AuthServiceImpl implements AuthService {
|
|||||||
AuthFullAccountEntity fullAccount = authFullAccountRepository.findByRoleCodeAndUid(session.getRoleCode(), uid)
|
AuthFullAccountEntity fullAccount = authFullAccountRepository.findByRoleCodeAndUid(session.getRoleCode(), uid)
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在"));
|
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在"));
|
||||||
validateFullAccountStatus(fullAccount);
|
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);
|
onFullPasswordFailed(fullAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
String salt = passwordSaltGenerator.nextSalt();
|
|
||||||
LocalDateTime current = now();
|
LocalDateTime current = now();
|
||||||
fullAccount.setPasswordSalt(salt);
|
fullAccount.setPasswordSalt(pending.newPasswordSalt());
|
||||||
fullAccount.setPasswordHash(passwordHasher.hash(newPassword, salt));
|
fullAccount.setPasswordDigest(passwordHasher.hash(newPasswordDigest, pending.newPasswordSalt()));
|
||||||
fullAccount.setFailedCount(0);
|
fullAccount.setFailedCount(0);
|
||||||
fullAccount.setLockedUntil(null);
|
fullAccount.setLockedUntil(null);
|
||||||
fullAccount.setStatus(RoleAccountStatus.ACTIVE.name());
|
fullAccount.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
@@ -267,7 +315,7 @@ AuthServiceImpl implements AuthService {
|
|||||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在"));
|
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在"));
|
||||||
validateFullAccountStatus(roleAccountSeat);
|
validateFullAccountStatus(roleAccountSeat);
|
||||||
PasswordDigestValidator.validate(account.getPassword());
|
PasswordDigestValidator.validate(account.getPassword());
|
||||||
if (!passwordHasher.matches(account.getPassword(), roleAccountSeat.getPasswordSalt(), roleAccountSeat.getPasswordHash())) {
|
if (!passwordHasher.matches(account.getPassword(), roleAccountSeat.getPasswordSalt(), roleAccountSeat.getPasswordDigest())) {
|
||||||
onFullPasswordFailed(roleAccountSeat);
|
onFullPasswordFailed(roleAccountSeat);
|
||||||
}
|
}
|
||||||
validatedAccounts.add(roleAccountSeat);
|
validatedAccounts.add(roleAccountSeat);
|
||||||
@@ -339,7 +387,11 @@ AuthServiceImpl implements AuthService {
|
|||||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "完整账号不存在"));
|
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "完整账号不存在"));
|
||||||
validateFullAccountStatus(fullAccount);
|
validateFullAccountStatus(fullAccount);
|
||||||
PasswordDigestValidator.validate(account.getPassword());
|
PasswordDigestValidator.validate(account.getPassword());
|
||||||
if (!passwordHasher.matches(account.getPassword(), fullAccount.getPasswordSalt(), fullAccount.getPasswordHash())) {
|
if (!PasswordDigestSupport.matchesDynamicDigest(
|
||||||
|
fullAccount.getPasswordDigest(),
|
||||||
|
account.getServerRandom(),
|
||||||
|
account.getPassword()
|
||||||
|
)) {
|
||||||
onFullPasswordFailed(fullAccount);
|
onFullPasswordFailed(fullAccount);
|
||||||
}
|
}
|
||||||
validatedAccounts.add(fullAccount);
|
validatedAccounts.add(fullAccount);
|
||||||
@@ -365,12 +417,23 @@ AuthServiceImpl implements AuthService {
|
|||||||
}
|
}
|
||||||
FullLoginAccountRequest accountRequest = new FullLoginAccountRequest();
|
FullLoginAccountRequest accountRequest = new FullLoginAccountRequest();
|
||||||
accountRequest.setUid(uid);
|
accountRequest.setUid(uid);
|
||||||
accountRequest.setPassword(proof.getPassword());
|
accountRequest.setPassword(proof.getPasswordDynamicDigest());
|
||||||
|
accountRequest.setServerRandom(proof.getServerRandom());
|
||||||
requests.add(accountRequest);
|
requests.add(accountRequest);
|
||||||
}
|
}
|
||||||
return requests;
|
return requests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record PendingPasswordChange(
|
||||||
|
String sessionToken,
|
||||||
|
String roleCode,
|
||||||
|
Integer uid,
|
||||||
|
String newPasswordSalt,
|
||||||
|
String oldServerRandom,
|
||||||
|
LocalDateTime expiresAt
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureMasterKeyReady() {
|
private void ensureMasterKeyReady() {
|
||||||
MasterKeyStatus.StatusDetail status = lmkService.getMasterKeyStatus();
|
MasterKeyStatus.StatusDetail status = lmkService.getMasterKeyStatus();
|
||||||
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
|
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.cisd.tms.modules.auth.service.impl;
|
package com.cisd.tms.modules.auth.service.impl;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
import org.bouncycastle.crypto.digests.SM3Digest;
|
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
|
||||||
@@ -17,4 +18,15 @@ final class PasswordDigestSupport {
|
|||||||
digest.doFinal(result, 0);
|
digest.doFinal(result, 0);
|
||||||
return Hex.toHexString(result);
|
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;
|
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 com.cisd.tms.modules.auth.service.PasswordHasher;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class Pbkdf2PasswordHasher implements PasswordHasher {
|
public class Pbkdf2PasswordHasher implements PasswordHasher {
|
||||||
|
|
||||||
private static final int ITERATIONS = 65_536;
|
|
||||||
private static final int KEY_LENGTH = 256;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String hash(String password, String salt) {
|
public String hash(String passwordDigest, String salt) {
|
||||||
try {
|
return passwordDigest;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean matches(String rawPassword, String salt, String expectedHash) {
|
public boolean matches(String actualPasswordDigest, String salt, String expectedPasswordDigest) {
|
||||||
byte[] actual = hash(rawPassword, salt).getBytes(StandardCharsets.UTF_8);
|
return PasswordDigestSupport.matchesDigest(actualPasswordDigest, expectedPasswordDigest);
|
||||||
byte[] expected = expectedHash.getBytes(StandardCharsets.UTF_8);
|
|
||||||
return MessageDigest.isEqual(actual, expected);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ public class UKeySignResult {
|
|||||||
*/
|
*/
|
||||||
private String sign;
|
private String sign;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前角色席位口令盐值。前端写入 UKey,登录时用于计算静态口令摘要。
|
||||||
|
*/
|
||||||
|
private String passwordSalt;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当发卡流程需要同时下发主密钥备份信息时,统一通过备份包承载。
|
* 当发卡流程需要同时下发主密钥备份信息时,统一通过备份包承载。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
-- -----------------------------------------------------------------------------
|
-- -----------------------------------------------------------------------------
|
||||||
-- TMS 全量库结构初始化脚本
|
-- TMS 旧库口令摘要迁移脚本
|
||||||
-- 适用范围:空库初始化 / Flyway 首次建库
|
-- 适用范围:已经存在旧认证表的数据库,从旧 password_hash 方案迁移到
|
||||||
|
-- password_digest = SM3(password_salt + 原始口令) 方案。
|
||||||
-- 数据库:TiDB / MySQL 8+
|
-- 数据库:TiDB / MySQL 8+
|
||||||
--
|
--
|
||||||
-- 说明:
|
-- 重要说明:
|
||||||
-- 1. 本文件是当前仓库唯一保留的数据库初始化 SQL。
|
-- 1. 本文件不是空库全量初始化脚本。空库初始化请使用 V2__tms_schema_full.sql。
|
||||||
-- 2. 结构已按当前最终状态汇总,不再依赖历史 V1~V12 逐步迁移。
|
-- 2. 旧 password_hash 通常是不可逆摘要,无法还原用户原始口令后生成新摘要。
|
||||||
-- 3. 执行前请先选中目标数据库,本文件不再包含 CREATE DATABASE / USE。
|
-- 3. 因此迁移时将固定角色席位统一重置为默认口令 Sunyard@123,并置为强制改密。
|
||||||
|
-- 4. 旧 password_hash 字段保留,便于现场回滚或排查;新代码只读取 password_digest。
|
||||||
-- -----------------------------------------------------------------------------
|
-- -----------------------------------------------------------------------------
|
||||||
|
|
||||||
CREATE DATABASE IF NOT EXISTS TMS
|
CREATE DATABASE IF NOT EXISTS TMS
|
||||||
@@ -15,764 +17,66 @@ CREATE DATABASE IF NOT EXISTS TMS
|
|||||||
|
|
||||||
USE 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 (
|
-- 默认口令摘要:SM3(password_salt + 'Sunyard@123')。
|
||||||
id BIGINT PRIMARY KEY,
|
-- 迁移会把固定席位盐值同步为后端当前标准盐值,后续 issue-sign 会返回该盐值供前端写入 UKey。
|
||||||
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),便于旧库直接切换到摘要口径。
|
|
||||||
UPDATE tms_auth_full_account
|
UPDATE tms_auth_full_account
|
||||||
SET password_hash = 'vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=',
|
SET password_salt = '3df85c2988a5de2b9eeaa8109639641a',
|
||||||
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)
|
update_time = CURRENT_TIMESTAMP(3)
|
||||||
WHERE role_code = 'SUPER_ADMIN'
|
WHERE role_code = 'SUPER_ADMIN'
|
||||||
AND uid = 1;
|
AND uid = 1;
|
||||||
|
|
||||||
UPDATE tms_auth_full_account
|
UPDATE tms_auth_full_account
|
||||||
SET password_hash = '25e83ejpUmjU64cYAKcDV5hhqClEu6aol8YyR3Jey7E=',
|
SET password_salt = '50de03346bb1f6afc74938acf5e88560',
|
||||||
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)
|
update_time = CURRENT_TIMESTAMP(3)
|
||||||
WHERE role_code = 'SUPER_ADMIN'
|
WHERE role_code = 'SUPER_ADMIN'
|
||||||
AND uid = 2;
|
AND uid = 2;
|
||||||
|
|
||||||
UPDATE tms_auth_full_account
|
UPDATE tms_auth_full_account
|
||||||
SET password_hash = 'l65Pr7MxOvqPOe0u6ozGGZcT4raTXUgDYMQfLTINTyE=',
|
SET password_salt = '073cd5fd30701edfcf033e8f501b2b43',
|
||||||
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)
|
update_time = CURRENT_TIMESTAMP(3)
|
||||||
WHERE role_code = 'KEY_ADMIN'
|
WHERE role_code = 'KEY_ADMIN'
|
||||||
AND uid = 1;
|
AND uid = 1;
|
||||||
|
|
||||||
UPDATE tms_auth_full_account
|
UPDATE tms_auth_full_account
|
||||||
SET password_hash = 'ZRwdn3TOotyXAytO4zeSpxRm/68Rm8VcB7w7VJmZPYc=',
|
SET password_salt = '57e2616077bd8af5cb90fc9790f69573',
|
||||||
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)
|
update_time = CURRENT_TIMESTAMP(3)
|
||||||
WHERE role_code = 'AUDIT_ADMIN'
|
WHERE role_code = 'AUDIT_ADMIN'
|
||||||
AND uid = 1;
|
AND uid = 1;
|
||||||
|
|
||||||
UPDATE tms_auth_full_account
|
UPDATE tms_auth_full_account
|
||||||
SET password_hash = '5Wu8Mf5hC3kq4gm/62/T7pO+aZIw8Y7uYoMIztWyT1w=',
|
SET password_salt = 'a6bea7601138e90a540b39c73d53df81',
|
||||||
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)
|
update_time = CURRENT_TIMESTAMP(3)
|
||||||
WHERE role_code = 'OPS_ADMIN'
|
WHERE role_code = 'OPS_ADMIN'
|
||||||
AND uid = 1;
|
AND uid = 1;
|
||||||
|
|
||||||
|
-- 如果这里失败,说明旧库中存在不属于上述固定席位的认证账号,需要先明确其迁移策略。
|
||||||
-- -----------------------------------------------------------------------------
|
ALTER TABLE tms_auth_full_account
|
||||||
-- IP白名单表
|
MODIFY COLUMN password_digest VARCHAR(64) NOT NULL COMMENT '口令静态摘要:SM3(password_salt + 原始口令)';
|
||||||
-- -----------------------------------------------------------------------------
|
|
||||||
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 '数据真实性签名值';
|
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ CREATE TABLE IF NOT EXISTS tms_auth_full_account (
|
|||||||
uid INT NOT NULL,
|
uid INT NOT NULL,
|
||||||
account_name VARCHAR(64) NOT NULL,
|
account_name VARCHAR(64) NOT NULL,
|
||||||
display_name VARCHAR(128) 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,
|
password_salt VARCHAR(128) NOT NULL,
|
||||||
status VARCHAR(32) NOT NULL,
|
status VARCHAR(32) NOT NULL,
|
||||||
need_change_password TINYINT(1) NOT NULL DEFAULT 1,
|
need_change_password TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
@@ -376,7 +376,7 @@ INSERT IGNORE INTO tms_auth_full_account (
|
|||||||
uid,
|
uid,
|
||||||
account_name,
|
account_name,
|
||||||
display_name,
|
display_name,
|
||||||
password_hash,
|
password_digest,
|
||||||
password_salt,
|
password_salt,
|
||||||
status,
|
status,
|
||||||
need_change_password,
|
need_change_password,
|
||||||
@@ -395,7 +395,7 @@ VALUES
|
|||||||
1,
|
1,
|
||||||
'super-admin-full-01',
|
'super-admin-full-01',
|
||||||
'超级管理员UKey席位一',
|
'超级管理员UKey席位一',
|
||||||
'vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=',
|
'00718a8319e67ef6cf9fc389c14ce16784278d55439a7a30a63fa7be8e42bbdf',
|
||||||
'3df85c2988a5de2b9eeaa8109639641a',
|
'3df85c2988a5de2b9eeaa8109639641a',
|
||||||
'ACTIVE',
|
'ACTIVE',
|
||||||
1,
|
1,
|
||||||
@@ -413,7 +413,7 @@ VALUES
|
|||||||
2,
|
2,
|
||||||
'super-admin-full-02',
|
'super-admin-full-02',
|
||||||
'超级管理员UKey席位二',
|
'超级管理员UKey席位二',
|
||||||
'25e83ejpUmjU64cYAKcDV5hhqClEu6aol8YyR3Jey7E=',
|
'169731d7c897649a16a073cdfddf48d2e98f72f10da5cead5cbbd51f8a9ffea6',
|
||||||
'50de03346bb1f6afc74938acf5e88560',
|
'50de03346bb1f6afc74938acf5e88560',
|
||||||
'ACTIVE',
|
'ACTIVE',
|
||||||
1,
|
1,
|
||||||
@@ -431,7 +431,7 @@ VALUES
|
|||||||
1,
|
1,
|
||||||
'key-admin-full-01',
|
'key-admin-full-01',
|
||||||
'密钥管理员UKey席位',
|
'密钥管理员UKey席位',
|
||||||
'l65Pr7MxOvqPOe0u6ozGGZcT4raTXUgDYMQfLTINTyE=',
|
'6d386b2a13550befb821795dcc0e9bda3d1cffc0d565ad539de16117e28f6a81',
|
||||||
'073cd5fd30701edfcf033e8f501b2b43',
|
'073cd5fd30701edfcf033e8f501b2b43',
|
||||||
'ACTIVE',
|
'ACTIVE',
|
||||||
1,
|
1,
|
||||||
@@ -449,7 +449,7 @@ VALUES
|
|||||||
1,
|
1,
|
||||||
'audit-admin-full-01',
|
'audit-admin-full-01',
|
||||||
'审计管理员UKey席位',
|
'审计管理员UKey席位',
|
||||||
'ZRwdn3TOotyXAytO4zeSpxRm/68Rm8VcB7w7VJmZPYc=',
|
'72a53b0cb8dd15775516f5085c7a3ee09d65a130dc1b17e18545d24930826d0c',
|
||||||
'57e2616077bd8af5cb90fc9790f69573',
|
'57e2616077bd8af5cb90fc9790f69573',
|
||||||
'ACTIVE',
|
'ACTIVE',
|
||||||
1,
|
1,
|
||||||
@@ -467,7 +467,7 @@ VALUES
|
|||||||
1,
|
1,
|
||||||
'ops-admin-full-01',
|
'ops-admin-full-01',
|
||||||
'运维管理员UKey席位',
|
'运维管理员UKey席位',
|
||||||
'5Wu8Mf5hC3kq4gm/62/T7pO+aZIw8Y7uYoMIztWyT1w=',
|
'58655960343a1efb0c1b281ce4c384ac699d01ab61c2c710d35e54b798f6e4c5',
|
||||||
'a6bea7601138e90a540b39c73d53df81',
|
'a6bea7601138e90a540b39c73d53df81',
|
||||||
'ACTIVE',
|
'ACTIVE',
|
||||||
1,
|
1,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<result property="uid" column="uid"/>
|
<result property="uid" column="uid"/>
|
||||||
<result property="accountName" column="account_name"/>
|
<result property="accountName" column="account_name"/>
|
||||||
<result property="displayName" column="display_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="passwordSalt" column="password_salt"/>
|
||||||
<result property="status" column="status"/>
|
<result property="status" column="status"/>
|
||||||
<result property="needChangePassword" column="need_change_password"/>
|
<result property="needChangePassword" column="need_change_password"/>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
uid,
|
uid,
|
||||||
account_name,
|
account_name,
|
||||||
display_name,
|
display_name,
|
||||||
password_hash,
|
password_digest,
|
||||||
password_salt,
|
password_salt,
|
||||||
status,
|
status,
|
||||||
need_change_password,
|
need_change_password,
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
uid,
|
uid,
|
||||||
account_name,
|
account_name,
|
||||||
display_name,
|
display_name,
|
||||||
password_hash,
|
password_digest,
|
||||||
password_salt,
|
password_salt,
|
||||||
status,
|
status,
|
||||||
need_change_password,
|
need_change_password,
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
uid,
|
uid,
|
||||||
account_name,
|
account_name,
|
||||||
display_name,
|
display_name,
|
||||||
password_hash,
|
password_digest,
|
||||||
password_salt,
|
password_salt,
|
||||||
status,
|
status,
|
||||||
need_change_password,
|
need_change_password,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.cisd.tms;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
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.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
@@ -19,8 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
class TmsApplicationTests {
|
class TmsApplicationTests {
|
||||||
|
|
||||||
private static final String DEFAULT_PASSWORD_DIGEST =
|
private static final String DEFAULT_PASSWORD = "Sunyard@123";
|
||||||
"fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b";
|
|
||||||
|
|
||||||
private static final Pattern AUTH_FULL_ACCOUNT_ROW = Pattern.compile(
|
private static final Pattern AUTH_FULL_ACCOUNT_ROW = Pattern.compile(
|
||||||
"\\(\\s*1210[1-5],.*?'([^']+)'\\s*,\\s*'([0-9a-f]{32})'",
|
"\\(\\s*1210[1-5],.*?'([^']+)'\\s*,\\s*'([0-9a-f]{32})'",
|
||||||
@@ -114,16 +115,23 @@ class TmsApplicationTests {
|
|||||||
|
|
||||||
Matcher matcher = AUTH_FULL_ACCOUNT_ROW.matcher(sql);
|
Matcher matcher = AUTH_FULL_ACCOUNT_ROW.matcher(sql);
|
||||||
java.util.Set<String> salts = new java.util.HashSet<>();
|
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;
|
int rows = 0;
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
rows++;
|
rows++;
|
||||||
String hash = matcher.group(1);
|
String digest = matcher.group(1);
|
||||||
String salt = matcher.group(2);
|
String salt = matcher.group(2);
|
||||||
org.junit.jupiter.api.Assertions.assertTrue(salts.add(salt), "duplicate salt: " + salt);
|
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);
|
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";
|
"0fffff81e971fa3f09107abf77931463fc0710bfb8962efeae3d5654b073bb0c";
|
||||||
private static final String DIGEST_ABC1234 =
|
private static final String DIGEST_ABC1234 =
|
||||||
"6b66c27d356b7fef3e2c2a986043031a410bb564205f22525ba022591671a7e5";
|
"6b66c27d356b7fef3e2c2a986043031a410bb564205f22525ba022591671a7e5";
|
||||||
|
private static final String DYNAMIC_DIGEST =
|
||||||
|
"55f6d97a52f5f3f27541e3137297a8f807d1ffdfb6596cdd562c8989bc58dc7f";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldLoginWithPasswordPayload() throws Exception {
|
void shouldLoginWithPasswordPayload() throws Exception {
|
||||||
@@ -118,7 +120,7 @@ class AuthControllerTest {
|
|||||||
{
|
{
|
||||||
"pubKey": "PUB-1",
|
"pubKey": "PUB-1",
|
||||||
"uid": 1,
|
"uid": 1,
|
||||||
"password": "%s",
|
"passwordDynamicDigest": "%s",
|
||||||
"serverRandom": "RB-1",
|
"serverRandom": "RB-1",
|
||||||
"issueSignature": "ISSUE-1",
|
"issueSignature": "ISSUE-1",
|
||||||
"loginPayload": "LOGIN-DATA-1",
|
"loginPayload": "LOGIN-DATA-1",
|
||||||
@@ -127,7 +129,7 @@ class AuthControllerTest {
|
|||||||
{
|
{
|
||||||
"pubKey": "PUB-2",
|
"pubKey": "PUB-2",
|
||||||
"uid": 2,
|
"uid": 2,
|
||||||
"password": "%s",
|
"passwordDynamicDigest": "%s",
|
||||||
"serverRandom": "RB-2",
|
"serverRandom": "RB-2",
|
||||||
"issueSignature": "ISSUE-2",
|
"issueSignature": "ISSUE-2",
|
||||||
"loginPayload": "LOGIN-DATA-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(status().isOk())
|
||||||
.andExpect(content().string(containsString("\"token\":\"token-ukey-001\"")))
|
.andExpect(content().string(containsString("\"token\":\"token-ukey-001\"")))
|
||||||
.andExpect(content().string(containsString("\"authLevel\":\"FULL\"")));
|
.andExpect(content().string(containsString("\"authLevel\":\"FULL\"")));
|
||||||
@@ -220,14 +222,15 @@ class AuthControllerTest {
|
|||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content("""
|
.content("""
|
||||||
{
|
{
|
||||||
"oldPassword": "%s",
|
"changeToken": "change-token-001",
|
||||||
"newPassword": "%s"
|
"oldPasswordDynamicDigest": "%s",
|
||||||
|
"newPasswordDigest": "%s"
|
||||||
}
|
}
|
||||||
""".formatted(DIGEST_12345678, DIGEST_ABC1234)))
|
""".formatted(DIGEST_12345678, DIGEST_ABC1234)))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(containsString("\"success\":true")));
|
.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
|
@Test
|
||||||
@@ -246,8 +249,8 @@ class AuthControllerTest {
|
|||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content("""
|
.content("""
|
||||||
{
|
{
|
||||||
"oldPassword": "%s",
|
"oldPasswordDigest": "%s",
|
||||||
"newPassword": "%s"
|
"newPasswordDigest": "%s"
|
||||||
}
|
}
|
||||||
""".formatted(DIGEST_12345678, DIGEST_ABC1234)))
|
""".formatted(DIGEST_12345678, DIGEST_ABC1234)))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
@@ -303,7 +306,7 @@ class AuthControllerTest {
|
|||||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class);
|
||||||
MasterKeyBackupPacket packet = new MasterKeyBackupPacket();
|
MasterKeyBackupPacket packet = new MasterKeyBackupPacket();
|
||||||
packet.setPacketIndex(1);
|
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(
|
Mockito.when(authAdminService.issueUkeyBindingSign(
|
||||||
ArgumentMatchers.isNull(),
|
ArgumentMatchers.isNull(),
|
||||||
ArgumentMatchers.isNull(),
|
ArgumentMatchers.isNull(),
|
||||||
@@ -328,6 +331,7 @@ class AuthControllerTest {
|
|||||||
"""))
|
"""))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(containsString("\"sign\":\"ISSUE-SIGN-001\"")))
|
.andExpect(content().string(containsString("\"sign\":\"ISSUE-SIGN-001\"")))
|
||||||
|
.andExpect(content().string(containsString("\"passwordSalt\":\"SALT-001\"")))
|
||||||
.andExpect(content().string(containsString("\"packetIndex\":1")));
|
.andExpect(content().string(containsString("\"packetIndex\":1")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +339,7 @@ class AuthControllerTest {
|
|||||||
void shouldIssueNormalAdminUkeyBindingSignThroughAdminEndpoint() throws Exception {
|
void shouldIssueNormalAdminUkeyBindingSignThroughAdminEndpoint() throws Exception {
|
||||||
AuthService authService = Mockito.mock(AuthService.class);
|
AuthService authService = Mockito.mock(AuthService.class);
|
||||||
AuthAdminService authAdminService = Mockito.mock(AuthAdminService.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(
|
Mockito.when(authAdminService.issueUkeyBindingSign(
|
||||||
ArgumentMatchers.eq("SUPER_ADMIN"),
|
ArgumentMatchers.eq("SUPER_ADMIN"),
|
||||||
ArgumentMatchers.eq("FULL"),
|
ArgumentMatchers.eq("FULL"),
|
||||||
@@ -360,7 +364,8 @@ class AuthControllerTest {
|
|||||||
}
|
}
|
||||||
"""))
|
"""))
|
||||||
.andExpect(status().isOk())
|
.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
|
@Test
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import org.mockito.Mockito;
|
|||||||
class AuthAdminServiceTest {
|
class AuthAdminServiceTest {
|
||||||
|
|
||||||
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-03-23T02:00:00Z"), ZoneOffset.UTC);
|
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-03-23T02:00:00Z"), ZoneOffset.UTC);
|
||||||
private static final String DEFAULT_PASSWORD_DIGEST = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b";
|
|
||||||
private static final String DIGEST_OLD1234 = "5237f20185252395f6ce351f8e742085d6ab1207ddda94b085b409e41026fc04";
|
private static final String DIGEST_OLD1234 = "5237f20185252395f6ce351f8e742085d6ab1207ddda94b085b409e41026fc04";
|
||||||
private static final String DIGEST_NEW1234 = "6b66c27d356b7fef3e2c2a986043031a410bb564205f22525ba022591671a7e5";
|
private static final String DIGEST_NEW1234 = "6b66c27d356b7fef3e2c2a986043031a410bb564205f22525ba022591671a7e5";
|
||||||
private static final String DIGEST_BAD_PASSWORD = "17cb3cec9c337826293158dd5623c75268a2eee06123ba53b9aef5b790c04e51";
|
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 first = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow();
|
||||||
AuthFullAccountEntity second = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 2).orElseThrow();
|
AuthFullAccountEntity second = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 2).orElseThrow();
|
||||||
Assertions.assertEquals("salt-reset-1", first.getPasswordSalt());
|
Assertions.assertEquals("OLD-SALT-1", first.getPasswordSalt());
|
||||||
Assertions.assertEquals("HASH:" + DEFAULT_PASSWORD_DIGEST + ":salt-reset-1", first.getPasswordHash());
|
Assertions.assertEquals("434685979fe125d142bfa1568d61ca1c6349bb54befb766617a1502d07bdc6c3", first.getPasswordDigest());
|
||||||
Assertions.assertEquals("salt-reset-2", second.getPasswordSalt());
|
Assertions.assertEquals("OLD-SALT-2", second.getPasswordSalt());
|
||||||
Assertions.assertEquals("HASH:" + DEFAULT_PASSWORD_DIGEST + ":salt-reset-2", second.getPasswordHash());
|
Assertions.assertEquals("dd6eae63d8a5776a4ed2b01cfac3d0b31ae15f331dfa507cd833da3ea5b504e4", second.getPasswordDigest());
|
||||||
Assertions.assertTrue(Boolean.TRUE.equals(first.getNeedChangePassword()));
|
Assertions.assertTrue(Boolean.TRUE.equals(first.getNeedChangePassword()));
|
||||||
Assertions.assertTrue(Boolean.TRUE.equals(second.getNeedChangePassword()));
|
Assertions.assertTrue(Boolean.TRUE.equals(second.getNeedChangePassword()));
|
||||||
}
|
}
|
||||||
@@ -65,7 +64,7 @@ class AuthAdminServiceTest {
|
|||||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||||
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
||||||
roles.save(role(RoleCode.KEY_ADMIN, RoleAccountStatus.ACTIVE));
|
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))
|
service(roles, accounts, new InMemoryRoleUkeyBindingRepository(), Mockito.mock(LmkService.class))
|
||||||
.changeAccountPassword(
|
.changeAccountPassword(
|
||||||
@@ -78,8 +77,8 @@ class AuthAdminServiceTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
AuthFullAccountEntity changed = accounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
AuthFullAccountEntity changed = accounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
||||||
Assertions.assertEquals("salt-reset-1", changed.getPasswordSalt());
|
Assertions.assertEquals("SALT-OLD", changed.getPasswordSalt());
|
||||||
Assertions.assertEquals("HASH:" + DIGEST_NEW1234 + ":salt-reset-1", changed.getPasswordHash());
|
Assertions.assertEquals(DIGEST_NEW1234, changed.getPasswordDigest());
|
||||||
Assertions.assertTrue(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
Assertions.assertTrue(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +87,7 @@ class AuthAdminServiceTest {
|
|||||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||||
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
|
||||||
roles.save(role(RoleCode.KEY_ADMIN, RoleAccountStatus.ACTIVE));
|
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,
|
BizException exception = Assertions.assertThrows(BizException.class,
|
||||||
() -> service(roles, accounts, new InMemoryRoleUkeyBindingRepository(), Mockito.mock(LmkService.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);
|
.issueUkeyBindingSign(RoleCode.SUPER_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.SUPER_ADMIN.getCode(), dto);
|
||||||
|
|
||||||
Assertions.assertEquals("ISSUE-SIGN-REBIND", result.getSign());
|
Assertions.assertEquals("ISSUE-SIGN-REBIND", result.getSign());
|
||||||
|
Assertions.assertEquals("SALT-OLD", result.getPasswordSalt());
|
||||||
Assertions.assertNotNull(result.getBackupPacket());
|
Assertions.assertNotNull(result.getBackupPacket());
|
||||||
List<RoleUkeyBindingEntity> activeUidBindings = bindings.findActiveByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1);
|
List<RoleUkeyBindingEntity> activeUidBindings = bindings.findActiveByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1);
|
||||||
Assertions.assertEquals(2, activeUidBindings.size());
|
Assertions.assertEquals(2, activeUidBindings.size());
|
||||||
@@ -184,6 +184,7 @@ class AuthAdminServiceTest {
|
|||||||
.issueUkeyBindingSign(RoleCode.SUPER_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.KEY_ADMIN.getCode(), dto);
|
.issueUkeyBindingSign(RoleCode.SUPER_ADMIN.getCode(), AuthLevel.FULL.name(), RoleCode.KEY_ADMIN.getCode(), dto);
|
||||||
|
|
||||||
Assertions.assertEquals("ISSUE-SIGN-NEW", result.getSign());
|
Assertions.assertEquals("ISSUE-SIGN-NEW", result.getSign());
|
||||||
|
Assertions.assertEquals("SALT-OLD", result.getPasswordSalt());
|
||||||
Assertions.assertNull(result.getBackupPacket());
|
Assertions.assertNull(result.getBackupPacket());
|
||||||
List<RoleUkeyBindingEntity> activeBindings = bindings.findActiveByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1);
|
List<RoleUkeyBindingEntity> activeBindings = bindings.findActiveByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1);
|
||||||
Assertions.assertEquals(1, activeBindings.size());
|
Assertions.assertEquals(1, activeBindings.size());
|
||||||
@@ -394,8 +395,7 @@ class AuthAdminServiceTest {
|
|||||||
new FakePasswordHasher(),
|
new FakePasswordHasher(),
|
||||||
lmkService,
|
lmkService,
|
||||||
new ObjectMapper(),
|
new ObjectMapper(),
|
||||||
FIXED_CLOCK,
|
FIXED_CLOCK
|
||||||
new FixedSaltSupplier("salt-reset-1", "salt-reset-2", "salt-reset-3")
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +416,7 @@ class AuthAdminServiceTest {
|
|||||||
entity.setUid(uid);
|
entity.setUid(uid);
|
||||||
entity.setAccountName(accountName);
|
entity.setAccountName(accountName);
|
||||||
entity.setDisplayName(accountName);
|
entity.setDisplayName(accountName);
|
||||||
entity.setPasswordHash(passwordHash);
|
entity.setPasswordDigest(passwordHash);
|
||||||
entity.setPasswordSalt(passwordSalt);
|
entity.setPasswordSalt(passwordSalt);
|
||||||
entity.setStatus(RoleAccountStatus.ACTIVE.name());
|
entity.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
entity.setNeedChangePassword(Boolean.FALSE);
|
entity.setNeedChangePassword(Boolean.FALSE);
|
||||||
@@ -440,29 +440,12 @@ class AuthAdminServiceTest {
|
|||||||
private static class FakePasswordHasher implements PasswordHasher {
|
private static class FakePasswordHasher implements PasswordHasher {
|
||||||
@Override
|
@Override
|
||||||
public String hash(String password, String salt) {
|
public String hash(String password, String salt) {
|
||||||
return "HASH:" + password + ":" + salt;
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean matches(String rawPassword, String salt, String expectedHash) {
|
public boolean matches(String rawPassword, String salt, String expectedHash) {
|
||||||
return hash(rawPassword, salt).equals(expectedHash);
|
return rawPassword.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++);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import org.mockito.Mockito;
|
|||||||
class AuthSecurityResetServiceTest {
|
class AuthSecurityResetServiceTest {
|
||||||
|
|
||||||
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-05-13T08:00:00Z"), ZoneOffset.UTC);
|
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
|
@Test
|
||||||
void shouldClearBindingsSessionsAndRestoreSeededRolesAndPasswords() {
|
void shouldClearBindingsSessionsAndRestoreSeededRolesAndPasswords() {
|
||||||
@@ -89,7 +90,7 @@ class AuthSecurityResetServiceTest {
|
|||||||
Assertions.assertEquals("super-admin-full-01", firstSuperAdmin.getAccountName());
|
Assertions.assertEquals("super-admin-full-01", firstSuperAdmin.getAccountName());
|
||||||
Assertions.assertEquals("超级管理员UKey席位一", firstSuperAdmin.getDisplayName());
|
Assertions.assertEquals("超级管理员UKey席位一", firstSuperAdmin.getDisplayName());
|
||||||
Assertions.assertEquals("3df85c2988a5de2b9eeaa8109639641a", firstSuperAdmin.getPasswordSalt());
|
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.assertEquals(RoleAccountStatus.ACTIVE.name(), firstSuperAdmin.getStatus());
|
||||||
Assertions.assertTrue(Boolean.TRUE.equals(firstSuperAdmin.getNeedChangePassword()));
|
Assertions.assertTrue(Boolean.TRUE.equals(firstSuperAdmin.getNeedChangePassword()));
|
||||||
Assertions.assertEquals(0, firstSuperAdmin.getFailedCount());
|
Assertions.assertEquals(0, firstSuperAdmin.getFailedCount());
|
||||||
@@ -114,7 +115,7 @@ class AuthSecurityResetServiceTest {
|
|||||||
entity.setUid(uid);
|
entity.setUid(uid);
|
||||||
entity.setAccountName("dirty");
|
entity.setAccountName("dirty");
|
||||||
entity.setDisplayName("dirty");
|
entity.setDisplayName("dirty");
|
||||||
entity.setPasswordHash("dirty");
|
entity.setPasswordDigest("dirty");
|
||||||
entity.setPasswordSalt("dirty");
|
entity.setPasswordSalt("dirty");
|
||||||
entity.setStatus(RoleAccountStatus.LOCKED.name());
|
entity.setStatus(RoleAccountStatus.LOCKED.name());
|
||||||
entity.setNeedChangePassword(Boolean.FALSE);
|
entity.setNeedChangePassword(Boolean.FALSE);
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -99,7 +101,7 @@ class AuthServiceTest {
|
|||||||
|
|
||||||
UkeyLoginRequest ukeyRequest = new UkeyLoginRequest();
|
UkeyLoginRequest ukeyRequest = new UkeyLoginRequest();
|
||||||
ukeyRequest.setRoleCode(RoleCode.KEY_ADMIN.getCode());
|
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);
|
LoginResponse response = service.ukeyLogin(ukeyRequest);
|
||||||
|
|
||||||
Assertions.assertEquals(AuthLevel.FULL.name(), response.getAuthLevel());
|
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
|
@Test
|
||||||
void shouldChangeUnifiedPasswordFromLimitedSessionAndAffectUkeyLogin() {
|
void shouldChangeUnifiedPasswordFromLimitedSessionAndAffectUkeyLogin() {
|
||||||
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
|
||||||
@@ -124,11 +154,18 @@ class AuthServiceTest {
|
|||||||
|
|
||||||
AuthService service = newAuthService(roles, accounts, sessions, bindings, () -> "token-after-change");
|
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();
|
AuthFullAccountEntity changed = accounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
||||||
Assertions.assertEquals("salt-test", changed.getPasswordSalt());
|
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()));
|
Assertions.assertFalse(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,8 +235,8 @@ class AuthServiceTest {
|
|||||||
UkeyLoginRequest request = new UkeyLoginRequest();
|
UkeyLoginRequest request = new UkeyLoginRequest();
|
||||||
request.setRoleCode(RoleCode.SUPER_ADMIN.getCode());
|
request.setRoleCode(RoleCode.SUPER_ADMIN.getCode());
|
||||||
request.setLoginFactors(List.of(
|
request.setLoginFactors(List.of(
|
||||||
proofWithPassword("PUB-1-BACKUP", 1, DIGEST_11111111, "RB-1", "ISSUE-1B", "LOGIN-DATA-1B", "LOGIN-SIGN-1B"),
|
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, DIGEST_22222222, "RB-2", "ISSUE-2B", "LOGIN-DATA-2B", "LOGIN-SIGN-2B")
|
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);
|
LoginResponse response = service.ukeyLogin(request);
|
||||||
@@ -223,7 +260,7 @@ class AuthServiceTest {
|
|||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class,
|
BizException exception = Assertions.assertThrows(BizException.class,
|
||||||
() -> newAuthService(roles, accounts, sessions, new InMemoryRoleUkeyBindingRepository(), () -> "unused")
|
() -> 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(ErrorCode.SESSION_INVALID.getCode(), exception.getCode());
|
||||||
Assertions.assertEquals("会话已过期", exception.getMessage());
|
Assertions.assertEquals("会话已过期", exception.getMessage());
|
||||||
@@ -328,7 +365,7 @@ class AuthServiceTest {
|
|||||||
entity.setAccountName(accountName);
|
entity.setAccountName(accountName);
|
||||||
entity.setDisplayName(accountName);
|
entity.setDisplayName(accountName);
|
||||||
entity.setPasswordSalt(salt);
|
entity.setPasswordSalt(salt);
|
||||||
entity.setPasswordHash("HASH:" + password + ":" + salt);
|
entity.setPasswordDigest(password);
|
||||||
entity.setStatus(RoleAccountStatus.ACTIVE.name());
|
entity.setStatus(RoleAccountStatus.ACTIVE.name());
|
||||||
entity.setNeedChangePassword(needChangePassword);
|
entity.setNeedChangePassword(needChangePassword);
|
||||||
entity.setPasswordChangedAt(LocalDateTime.of(2026, 3, 23, 1, 0));
|
entity.setPasswordChangedAt(LocalDateTime.of(2026, 3, 23, 1, 0));
|
||||||
@@ -387,15 +424,24 @@ class AuthServiceTest {
|
|||||||
return entity;
|
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 {
|
private static class FakePasswordHasher implements PasswordHasher {
|
||||||
@Override
|
@Override
|
||||||
public String hash(String password, String salt) {
|
public String hash(String password, String salt) {
|
||||||
return "HASH:" + password + ":" + salt;
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean matches(String rawPassword, String salt, String expectedHash) {
|
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