fix:密码摘要

This commit is contained in:
waner 2026-05-21 16:34:17 +08:00
parent 597fa4fe67
commit a5c73c82b0
14 changed files with 174 additions and 49 deletions

View File

@ -7,11 +7,11 @@ import jakarta.validation.constraints.NotBlank;
public class AdminChangePasswordRequest {
@NotBlank(message = "oldPassword不能为空")
@Schema(description = "旧口令", example = "12345678")
@Schema(description = "旧口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
private String oldPassword;
@NotBlank(message = "newPassword不能为空")
@Schema(description = "新口令", example = "Abc1234!")
@Schema(description = "新口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
private String newPassword;
public String getOldPassword() {

View File

@ -9,11 +9,11 @@ public class ChangePasswordRequest {
@NotBlank(message = "oldPassword不能为空")
@JsonAlias("currentPassword")
@Schema(description = "旧口令", example = "12345678")
@Schema(description = "旧口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
private String oldPassword;
@NotBlank(message = "newPassword不能为空")
@Schema(description = "新口令", example = "Abc1234!")
@Schema(description = "新口令SM3摘要", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
private String newPassword;
public String getOldPassword() {

View File

@ -12,7 +12,7 @@ public class PasswordLoginAccountRequest {
private Integer uid;
@NotBlank(message = "password不能为空")
@Schema(description = "账号口令", example = "12345678")
@Schema(description = "账号口令SM3摘要字段名保持password兼容前端", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
private String password;
public Integer getUid() {

View File

@ -28,7 +28,7 @@ public class UkeyLoginProof {
@NotBlank(message = "loginSignature不能为空")
private String loginSignature;
@Schema(description = "当前 UKey 固定席位对应账号的口令,所有角色都必传", example = "12345678")
@Schema(description = "当前 UKey 固定席位对应账号口令的SM3摘要字段名保持password兼容前端", example = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b")
private String password;
public String getPubKey() {

View File

@ -0,0 +1,19 @@
package com.cisd.tms.modules.auth.service;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
public final class PasswordDigestValidator {
public static final String MESSAGE = "密码摘要格式无效password必须为64位SM3十六进制摘要";
private PasswordDigestValidator() {
}
public static void validate(String passwordDigest) {
String value = passwordDigest == null ? "" : passwordDigest.trim();
if (value.length() != 64 || !value.matches("(?i)^[0-9a-f]{64}$")) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), MESSAGE);
}
}
}

View File

@ -14,7 +14,7 @@ import com.cisd.tms.modules.auth.repository.AuthFullAccountRepository;
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
import com.cisd.tms.modules.auth.service.AuthAdminService;
import com.cisd.tms.modules.auth.service.PasswordComplexityValidator;
import com.cisd.tms.modules.auth.service.PasswordDigestValidator;
import com.cisd.tms.modules.auth.service.PasswordHasher;
import com.cisd.tms.modules.auth.service.PasswordSaltGenerator;
import com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket;
@ -43,6 +43,7 @@ import org.springframework.stereotype.Service;
public class AuthAdminServiceImpl implements AuthAdminService {
static final String DEFAULT_PASSWORD = "Sunyard@123";
private static final String DEFAULT_PASSWORD_DIGEST = PasswordDigestSupport.sm3Hex(DEFAULT_PASSWORD);
private final RoleAccountRepository roleAccountRepository;
private final AuthFullAccountRepository authFullAccountRepository;
@ -106,7 +107,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
for (AuthFullAccountEntity account : fullAccounts) {
String newSalt = passwordSaltGenerator.nextSalt();
account.setPasswordSalt(newSalt);
account.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD, newSalt));
account.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD_DIGEST, newSalt));
account.setStatus(RoleAccountStatus.ACTIVE.name());
account.setNeedChangePassword(Boolean.TRUE);
account.setPasswordChangedAt(current);
@ -160,7 +161,8 @@ public class AuthAdminServiceImpl implements AuthAdminService {
String oldPassword,
String newPassword
) {
PasswordComplexityValidator.validate(newPassword);
PasswordDigestValidator.validate(oldPassword);
PasswordDigestValidator.validate(newPassword);
loadRole(targetRoleCode);
RoleCode targetRole = resolveRoleCode(targetRoleCode);
if (uid == null || uid < 1 || uid > targetRole.getRequiredUkeyCount()) {

View File

@ -120,7 +120,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
1,
"super-admin-full-01",
"超级管理员UKey席位一",
"u70ZBltsrr8ncTyFgBkoziqvU2PfgpBYFqeoZUz4k6Y=",
"vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=",
"3df85c2988a5de2b9eeaa8109639641a"
),
new SeedFullAccount(
@ -129,7 +129,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
2,
"super-admin-full-02",
"超级管理员UKey席位二",
"A5GWgv2UmFc+r6O5fkdAZkuWXcF/c7B13mSivSf7vQg=",
"25e83ejpUmjU64cYAKcDV5hhqClEu6aol8YyR3Jey7E=",
"50de03346bb1f6afc74938acf5e88560"
),
new SeedFullAccount(
@ -138,7 +138,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
1,
"key-admin-full-01",
"密钥管理员UKey席位",
"Lbhpr63UUBjRkRuXZKCMZhhinXXOz0LkvjiIJYcnvJs=",
"l65Pr7MxOvqPOe0u6ozGGZcT4raTXUgDYMQfLTINTyE=",
"073cd5fd30701edfcf033e8f501b2b43"
),
new SeedFullAccount(
@ -147,7 +147,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
1,
"audit-admin-full-01",
"审计管理员UKey席位",
"SsqLWKlWRlxmWaxxBaxy6nSXfvu7McBfmXfOYQBXKE0=",
"ZRwdn3TOotyXAytO4zeSpxRm/68Rm8VcB7w7VJmZPYc=",
"57e2616077bd8af5cb90fc9790f69573"
),
new SeedFullAccount(
@ -156,7 +156,7 @@ public class AuthSecurityResetServiceImpl implements AuthSecurityResetService {
1,
"ops-admin-full-01",
"运维管理员UKey席位",
"k0a3X9siC6Nyj72U/BavF8XSSXAAIXGKte+y6JgkCTc=",
"5Wu8Mf5hC3kq4gm/62/T7pO+aZIw8Y7uYoMIztWyT1w=",
"a6bea7601138e90a540b39c73d53df81"
)
);

View File

@ -30,8 +30,8 @@ import com.cisd.tms.modules.auth.service.AuthPolicyService;
import com.cisd.tms.modules.auth.service.AuthService;
import com.cisd.tms.modules.auth.service.CaptchaService;
import com.cisd.tms.modules.auth.service.CompatUkeyVerifier;
import com.cisd.tms.modules.auth.service.PasswordDigestValidator;
import com.cisd.tms.modules.auth.service.PasswordHasher;
import com.cisd.tms.modules.auth.service.PasswordComplexityValidator;
import com.cisd.tms.modules.auth.service.PasswordSaltGenerator;
import com.cisd.tms.modules.auth.service.SessionTokenGenerator;
import com.cisd.tms.modules.auth.service.UkeyLoginRandomService;
@ -206,7 +206,8 @@ AuthServiceImpl implements AuthService {
@Override
public void changeAccountPassword(String sessionToken, Integer uid, String currentPassword, String newPassword) {
PasswordComplexityValidator.validate(newPassword);
PasswordDigestValidator.validate(currentPassword);
PasswordDigestValidator.validate(newPassword);
AuthSessionEntity session = requireActiveSession(sessionToken);
if (uid == null || !containsAuthenticatedUid(session, uid)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "当前会话未完成账号认证");
@ -265,6 +266,7 @@ AuthServiceImpl implements AuthService {
.findByRoleCodeAndUid(roleAccount.getRoleCode(), uid)
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在"));
validateFullAccountStatus(roleAccountSeat);
PasswordDigestValidator.validate(account.getPassword());
if (!passwordHasher.matches(account.getPassword(), roleAccountSeat.getPasswordSalt(), roleAccountSeat.getPasswordHash())) {
onFullPasswordFailed(roleAccountSeat);
}
@ -336,6 +338,7 @@ AuthServiceImpl implements AuthService {
.findByRoleCodeAndUid(roleAccount.getRoleCode(), uid)
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "完整账号不存在"));
validateFullAccountStatus(fullAccount);
PasswordDigestValidator.validate(account.getPassword());
if (!passwordHasher.matches(account.getPassword(), fullAccount.getPasswordSalt(), fullAccount.getPasswordHash())) {
onFullPasswordFailed(fullAccount);
}

View File

@ -0,0 +1,20 @@
package com.cisd.tms.modules.auth.service.impl;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.util.encoders.Hex;
final class PasswordDigestSupport {
private PasswordDigestSupport() {
}
static String sm3Hex(String password) {
byte[] input = (password == null ? "" : password).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);
}
}

View File

@ -388,7 +388,7 @@ VALUES
1,
'super-admin-full-01',
'超级管理员UKey席位一',
'u70ZBltsrr8ncTyFgBkoziqvU2PfgpBYFqeoZUz4k6Y=',
'vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=',
'3df85c2988a5de2b9eeaa8109639641a',
'ACTIVE',
1,
@ -406,7 +406,7 @@ VALUES
2,
'super-admin-full-02',
'超级管理员UKey席位二',
'A5GWgv2UmFc+r6O5fkdAZkuWXcF/c7B13mSivSf7vQg=',
'25e83ejpUmjU64cYAKcDV5hhqClEu6aol8YyR3Jey7E=',
'50de03346bb1f6afc74938acf5e88560',
'ACTIVE',
1,
@ -424,7 +424,7 @@ VALUES
1,
'key-admin-full-01',
'密钥管理员UKey席位',
'Lbhpr63UUBjRkRuXZKCMZhhinXXOz0LkvjiIJYcnvJs=',
'l65Pr7MxOvqPOe0u6ozGGZcT4raTXUgDYMQfLTINTyE=',
'073cd5fd30701edfcf033e8f501b2b43',
'ACTIVE',
1,
@ -442,7 +442,7 @@ VALUES
1,
'audit-admin-full-01',
'审计管理员UKey席位',
'SsqLWKlWRlxmWaxxBaxy6nSXfvu7McBfmXfOYQBXKE0=',
'ZRwdn3TOotyXAytO4zeSpxRm/68Rm8VcB7w7VJmZPYc=',
'57e2616077bd8af5cb90fc9790f69573',
'ACTIVE',
1,
@ -460,7 +460,7 @@ VALUES
1,
'ops-admin-full-01',
'运维管理员UKey席位',
'k0a3X9siC6Nyj72U/BavF8XSSXAAIXGKte+y6JgkCTc=',
'5Wu8Mf5hC3kq4gm/62/T7pO+aZIw8Y7uYoMIztWyT1w=',
'a6bea7601138e90a540b39c73d53df81',
'ACTIVE',
1,
@ -473,6 +473,47 @@ VALUES
CURRENT_TIMESTAMP(3)
);
-- -----------------------------------------------------------------------------
-- 兼容旧库默认口令摘要升级
-- -----------------------------------------------------------------------------
-- 旧版本默认账号保存的是 PBKDF2('Sunyard@123', password_salt)。
-- 当前版本前端提交 password=SM3('Sunyard@123'),因此默认账号统一重置为
-- 固定 salt + PBKDF2(SM3('Sunyard@123'), salt),便于旧库直接切换到摘要口径。
UPDATE tms_auth_full_account
SET password_hash = 'vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=',
password_salt = '3df85c2988a5de2b9eeaa8109639641a',
update_time = CURRENT_TIMESTAMP(3)
WHERE role_code = 'SUPER_ADMIN'
AND uid = 1;
UPDATE tms_auth_full_account
SET password_hash = '25e83ejpUmjU64cYAKcDV5hhqClEu6aol8YyR3Jey7E=',
password_salt = '50de03346bb1f6afc74938acf5e88560',
update_time = CURRENT_TIMESTAMP(3)
WHERE role_code = 'SUPER_ADMIN'
AND uid = 2;
UPDATE tms_auth_full_account
SET password_hash = 'l65Pr7MxOvqPOe0u6ozGGZcT4raTXUgDYMQfLTINTyE=',
password_salt = '073cd5fd30701edfcf033e8f501b2b43',
update_time = CURRENT_TIMESTAMP(3)
WHERE role_code = 'KEY_ADMIN'
AND uid = 1;
UPDATE tms_auth_full_account
SET password_hash = 'ZRwdn3TOotyXAytO4zeSpxRm/68Rm8VcB7w7VJmZPYc=',
password_salt = '57e2616077bd8af5cb90fc9790f69573',
update_time = CURRENT_TIMESTAMP(3)
WHERE role_code = 'AUDIT_ADMIN'
AND uid = 1;
UPDATE tms_auth_full_account
SET password_hash = '5Wu8Mf5hC3kq4gm/62/T7pO+aZIw8Y7uYoMIztWyT1w=',
password_salt = 'a6bea7601138e90a540b39c73d53df81',
update_time = CURRENT_TIMESTAMP(3)
WHERE role_code = 'OPS_ADMIN'
AND uid = 1;
-- -----------------------------------------------------------------------------
-- IP白名单表

View File

@ -34,6 +34,10 @@ import org.mockito.Mockito;
class AuthAdminServiceTest {
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-03-23T02:00:00Z"), ZoneOffset.UTC);
private static final String DEFAULT_PASSWORD_DIGEST = "fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b";
private static final String DIGEST_OLD1234 = "5237f20185252395f6ce351f8e742085d6ab1207ddda94b085b409e41026fc04";
private static final String DIGEST_NEW1234 = "6b66c27d356b7fef3e2c2a986043031a410bb564205f22525ba022591671a7e5";
private static final String DIGEST_BAD_PASSWORD = "17cb3cec9c337826293158dd5623c75268a2eee06123ba53b9aef5b790c04e51";
@Test
void shouldResetOnlyUnifiedSeatPasswords() {
@ -49,9 +53,9 @@ class AuthAdminServiceTest {
AuthFullAccountEntity first = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow();
AuthFullAccountEntity second = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 2).orElseThrow();
Assertions.assertEquals("salt-reset-1", first.getPasswordSalt());
Assertions.assertEquals("HASH:Sunyard@123:salt-reset-1", first.getPasswordHash());
Assertions.assertEquals("HASH:" + DEFAULT_PASSWORD_DIGEST + ":salt-reset-1", first.getPasswordHash());
Assertions.assertEquals("salt-reset-2", second.getPasswordSalt());
Assertions.assertEquals("HASH:Sunyard@123:salt-reset-2", second.getPasswordHash());
Assertions.assertEquals("HASH:" + DEFAULT_PASSWORD_DIGEST + ":salt-reset-2", second.getPasswordHash());
Assertions.assertTrue(Boolean.TRUE.equals(first.getNeedChangePassword()));
Assertions.assertTrue(Boolean.TRUE.equals(second.getNeedChangePassword()));
}
@ -61,7 +65,7 @@ class AuthAdminServiceTest {
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
roles.save(role(RoleCode.KEY_ADMIN, RoleAccountStatus.ACTIVE));
accounts.save(account(RoleCode.KEY_ADMIN, 1, "key-admin-01", "HASH:Old1234!:SALT-OLD", "SALT-OLD"));
accounts.save(account(RoleCode.KEY_ADMIN, 1, "key-admin-01", "HASH:" + DIGEST_OLD1234 + ":SALT-OLD", "SALT-OLD"));
service(roles, accounts, new InMemoryRoleUkeyBindingRepository(), Mockito.mock(LmkService.class))
.changeAccountPassword(
@ -69,13 +73,13 @@ class AuthAdminServiceTest {
AuthLevel.FULL.name(),
RoleCode.KEY_ADMIN.getCode(),
1,
"Old1234!",
"New1234!"
DIGEST_OLD1234,
DIGEST_NEW1234
);
AuthFullAccountEntity changed = accounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
Assertions.assertEquals("salt-reset-1", changed.getPasswordSalt());
Assertions.assertEquals("HASH:New1234!:salt-reset-1", changed.getPasswordHash());
Assertions.assertEquals("HASH:" + DIGEST_NEW1234 + ":salt-reset-1", changed.getPasswordHash());
Assertions.assertTrue(Boolean.TRUE.equals(changed.getNeedChangePassword()));
}
@ -84,7 +88,7 @@ class AuthAdminServiceTest {
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
roles.save(role(RoleCode.KEY_ADMIN, RoleAccountStatus.ACTIVE));
accounts.save(account(RoleCode.KEY_ADMIN, 1, "key-admin-01", "HASH:Old1234!:SALT-OLD", "SALT-OLD"));
accounts.save(account(RoleCode.KEY_ADMIN, 1, "key-admin-01", "HASH:" + DIGEST_OLD1234 + ":SALT-OLD", "SALT-OLD"));
BizException exception = Assertions.assertThrows(BizException.class,
() -> service(roles, accounts, new InMemoryRoleUkeyBindingRepository(), Mockito.mock(LmkService.class))
@ -93,8 +97,8 @@ class AuthAdminServiceTest {
AuthLevel.FULL.name(),
RoleCode.KEY_ADMIN.getCode(),
1,
"bad-password",
"New1234!"
DIGEST_BAD_PASSWORD,
DIGEST_NEW1234
));
Assertions.assertEquals(ErrorCode.UNAUTHORIZED.getCode(), exception.getCode());

View File

@ -22,6 +22,7 @@ import org.mockito.Mockito;
class AuthSecurityResetServiceTest {
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-05-13T08:00:00Z"), ZoneOffset.UTC);
private static final String FIRST_SUPER_ADMIN_PASSWORD_HASH = "vVPlH2UGQcXUo7NblMJMaof32um6UinF8zsckZuxe9E=";
@Test
void shouldClearBindingsSessionsAndRestoreSeededRolesAndPasswords() {
@ -78,7 +79,7 @@ class AuthSecurityResetServiceTest {
Assertions.assertEquals("super-admin-full-01", firstSuperAdmin.getAccountName());
Assertions.assertEquals("超级管理员UKey席位一", firstSuperAdmin.getDisplayName());
Assertions.assertEquals("3df85c2988a5de2b9eeaa8109639641a", firstSuperAdmin.getPasswordSalt());
Assertions.assertEquals("u70ZBltsrr8ncTyFgBkoziqvU2PfgpBYFqeoZUz4k6Y=", firstSuperAdmin.getPasswordHash());
Assertions.assertEquals(FIRST_SUPER_ADMIN_PASSWORD_HASH, firstSuperAdmin.getPasswordHash());
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), firstSuperAdmin.getStatus());
Assertions.assertTrue(Boolean.TRUE.equals(firstSuperAdmin.getNeedChangePassword()));
Assertions.assertEquals(0, firstSuperAdmin.getFailedCount());

View File

@ -42,6 +42,13 @@ import org.junit.jupiter.api.Test;
class AuthServiceTest {
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-03-23T02:00:00Z"), ZoneOffset.UTC);
private static final String DIGEST_12345678 = "0fffff81e971fa3f09107abf77931463fc0710bfb8962efeae3d5654b073bb0c";
private static final String DIGEST_ABC1234 = "3add0ab5dec629efbc3a741c4063f7c82d8919b3de749a3f74b91b704aa2fd9e";
private static final String DIGEST_OLD1234 = "5237f20185252395f6ce351f8e742085d6ab1207ddda94b085b409e41026fc04";
private static final String DIGEST_NEW1234 = "6b66c27d356b7fef3e2c2a986043031a410bb564205f22525ba022591671a7e5";
private static final String DIGEST_11111111 = "f5fb72062ab1d6ddc0bfecb87dc32bec6e773f585e1cbbf93de394081c2e0f37";
private static final String DIGEST_22222222 = "377fc0693a9ed9de44bd8c8aa9d166887896f329f39f028eaf23bca7fd9f3b57";
private static final String DIGEST_BAD_PASSWORD = "17cb3cec9c337826293158dd5623c75268a2eee06123ba53b9aef5b790c04e51";
@Test
void shouldCreateLimitedSessionFromUnifiedRoleSeatPassword() {
@ -49,12 +56,12 @@ class AuthServiceTest {
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
roles.save(activeRole(RoleCode.AUDIT_ADMIN));
accounts.save(activeAccount(RoleCode.AUDIT_ADMIN, 1, "audit-admin-01", "12345678", "SALT-A", true));
accounts.save(activeAccount(RoleCode.AUDIT_ADMIN, 1, "audit-admin-01", DIGEST_12345678, "SALT-A", true));
AuthService service = newAuthService(roles, accounts, sessions, new InMemoryRoleUkeyBindingRepository(), () -> "token-limited-001");
LoginRequest request = new LoginRequest();
request.setRoleCode(RoleCode.AUDIT_ADMIN.getCode());
request.setAccounts(List.of(passwordAccount(1, "12345678")));
request.setAccounts(List.of(passwordAccount(1, DIGEST_12345678)));
LoginResponse response = service.login(request);
@ -74,7 +81,7 @@ class AuthServiceTest {
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
InMemoryRoleUkeyBindingRepository bindings = new InMemoryRoleUkeyBindingRepository();
roles.save(activeRole(RoleCode.KEY_ADMIN));
accounts.save(activeAccount(RoleCode.KEY_ADMIN, 1, "key-admin-01", "Abc1234!", "SALT-K", false));
accounts.save(activeAccount(RoleCode.KEY_ADMIN, 1, "key-admin-01", DIGEST_ABC1234, "SALT-K", false));
bindings.save(activeBinding(RoleCode.KEY_ADMIN, 1, "UK-1", "PUB-1"));
LmkService lmkService = org.mockito.Mockito.mock(LmkService.class);
@ -87,12 +94,12 @@ class AuthServiceTest {
LoginRequest passwordRequest = new LoginRequest();
passwordRequest.setRoleCode(RoleCode.KEY_ADMIN.getCode());
passwordRequest.setAccounts(List.of(passwordAccount(1, "Abc1234!")));
passwordRequest.setAccounts(List.of(passwordAccount(1, DIGEST_ABC1234)));
Assertions.assertEquals(AuthLevel.LIMITED.name(), service.login(passwordRequest).getAuthLevel());
UkeyLoginRequest ukeyRequest = new UkeyLoginRequest();
ukeyRequest.setRoleCode(RoleCode.KEY_ADMIN.getCode());
ukeyRequest.setLoginFactors(List.of(proofWithPassword("PUB-1", 1, "Abc1234!", "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1")));
ukeyRequest.setLoginFactors(List.of(proofWithPassword("PUB-1", 1, DIGEST_ABC1234, "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1")));
LoginResponse response = service.ukeyLogin(ukeyRequest);
Assertions.assertEquals(AuthLevel.FULL.name(), response.getAuthLevel());
@ -109,7 +116,7 @@ class AuthServiceTest {
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
InMemoryRoleUkeyBindingRepository bindings = new InMemoryRoleUkeyBindingRepository();
roles.save(activeRole(RoleCode.KEY_ADMIN));
accounts.save(activeAccount(RoleCode.KEY_ADMIN, 1, "key-admin-01", "Old1234!", "SALT-K", true));
accounts.save(activeAccount(RoleCode.KEY_ADMIN, 1, "key-admin-01", DIGEST_OLD1234, "SALT-K", true));
bindings.save(activeBinding(RoleCode.KEY_ADMIN, 1, "UK-1", "PUB-1"));
AuthSessionEntity session = session("token-change-001", RoleCode.KEY_ADMIN.getCode(), AuthLevel.LIMITED.name());
session.setAuthenticatedPrincipalsJson("[{\"type\":\"LIMITED\",\"uid\":1,\"username\":\"key-admin-01\"}]");
@ -117,11 +124,11 @@ class AuthServiceTest {
AuthService service = newAuthService(roles, accounts, sessions, bindings, () -> "token-after-change");
service.changeAccountPassword("token-change-001", 1, "Old1234!", "New1234!");
service.changeAccountPassword("token-change-001", 1, DIGEST_OLD1234, DIGEST_NEW1234);
AuthFullAccountEntity changed = accounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
Assertions.assertEquals("salt-test", changed.getPasswordSalt());
Assertions.assertEquals("HASH:New1234!:salt-test", changed.getPasswordHash());
Assertions.assertEquals("HASH:" + DIGEST_NEW1234 + ":salt-test", changed.getPasswordHash());
Assertions.assertFalse(Boolean.TRUE.equals(changed.getNeedChangePassword()));
}
@ -130,13 +137,13 @@ class AuthServiceTest {
InMemoryRoleAccountRepository roles = new InMemoryRoleAccountRepository();
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
roles.save(activeRole(RoleCode.SUPER_ADMIN));
accounts.save(activeAccount(RoleCode.SUPER_ADMIN, 1, "super-admin-01", "11111111", "SALT-S1", 4, null));
accounts.save(activeAccount(RoleCode.SUPER_ADMIN, 2, "super-admin-02", "22222222", "SALT-S2", 0, null));
accounts.save(activeAccount(RoleCode.SUPER_ADMIN, 1, "super-admin-01", DIGEST_11111111, "SALT-S1", 4, null));
accounts.save(activeAccount(RoleCode.SUPER_ADMIN, 2, "super-admin-02", DIGEST_22222222, "SALT-S2", 0, null));
AuthService service = newAuthService(roles, accounts, new InMemoryAuthSessionRepository(), new InMemoryRoleUkeyBindingRepository(), () -> "unused");
LoginRequest request = new LoginRequest();
request.setRoleCode(RoleCode.SUPER_ADMIN.getCode());
request.setAccounts(List.of(passwordAccount(1, "bad-password"), passwordAccount(2, "22222222")));
request.setAccounts(List.of(passwordAccount(1, DIGEST_BAD_PASSWORD), passwordAccount(2, DIGEST_22222222)));
BizException exception = Assertions.assertThrows(BizException.class, () -> service.login(request));
@ -154,7 +161,7 @@ class AuthServiceTest {
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
roles.save(activeRole(RoleCode.AUDIT_ADMIN));
AuthFullAccountEntity account = activeAccount(RoleCode.AUDIT_ADMIN, 1, "audit-admin-01", "12345678", "SALT-A", false);
AuthFullAccountEntity account = activeAccount(RoleCode.AUDIT_ADMIN, 1, "audit-admin-01", DIGEST_12345678, "SALT-A", false);
account.setPasswordChangedAt(LocalDateTime.of(2026, 2, 21, 2, 0));
accounts.save(account);
AuthSessionEntity session = session("token-me-001", RoleCode.AUDIT_ADMIN.getCode(), AuthLevel.LIMITED.name());
@ -174,8 +181,8 @@ class AuthServiceTest {
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
InMemoryRoleUkeyBindingRepository bindings = new InMemoryRoleUkeyBindingRepository();
roles.save(activeRole(RoleCode.SUPER_ADMIN));
accounts.save(activeAccount(RoleCode.SUPER_ADMIN, 1, "super-admin-01", "11111111", "SALT-S1", false));
accounts.save(activeAccount(RoleCode.SUPER_ADMIN, 2, "super-admin-02", "22222222", "SALT-S2", false));
accounts.save(activeAccount(RoleCode.SUPER_ADMIN, 1, "super-admin-01", DIGEST_11111111, "SALT-S1", false));
accounts.save(activeAccount(RoleCode.SUPER_ADMIN, 2, "super-admin-02", DIGEST_22222222, "SALT-S2", false));
bindings.save(activeBinding(RoleCode.SUPER_ADMIN, 1, "UK-1", "PUB-1"));
bindings.save(activeBinding(RoleCode.SUPER_ADMIN, 1, "UK-1-BACKUP", "PUB-1-BACKUP"));
bindings.save(activeBinding(RoleCode.SUPER_ADMIN, 2, "UK-2", "PUB-2"));
@ -191,8 +198,8 @@ class AuthServiceTest {
UkeyLoginRequest request = new UkeyLoginRequest();
request.setRoleCode(RoleCode.SUPER_ADMIN.getCode());
request.setLoginFactors(List.of(
proofWithPassword("PUB-1-BACKUP", 1, "11111111", "RB-1", "ISSUE-1B", "LOGIN-DATA-1B", "LOGIN-SIGN-1B"),
proofWithPassword("PUB-2-BACKUP", 2, "22222222", "RB-2", "ISSUE-2B", "LOGIN-DATA-2B", "LOGIN-SIGN-2B")
proofWithPassword("PUB-1-BACKUP", 1, DIGEST_11111111, "RB-1", "ISSUE-1B", "LOGIN-DATA-1B", "LOGIN-SIGN-1B"),
proofWithPassword("PUB-2-BACKUP", 2, DIGEST_22222222, "RB-2", "ISSUE-2B", "LOGIN-DATA-2B", "LOGIN-SIGN-2B")
));
LoginResponse response = service.ukeyLogin(request);
@ -208,7 +215,7 @@ class AuthServiceTest {
InMemoryAuthFullAccountRepository accounts = new InMemoryAuthFullAccountRepository();
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
roles.save(activeRole(RoleCode.KEY_ADMIN));
accounts.save(activeAccount(RoleCode.KEY_ADMIN, 1, "key-admin-01", "12345678", "SALT-K", false));
accounts.save(activeAccount(RoleCode.KEY_ADMIN, 1, "key-admin-01", DIGEST_12345678, "SALT-K", false));
AuthSessionEntity expired = session("token-expired-001", RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name());
expired.setAuthenticatedPrincipalsJson("[{\"type\":\"FULL\",\"uid\":1,\"username\":\"key-admin-01\"}]");
expired.setExpiresAt(LocalDateTime.of(2026, 3, 23, 1, 59));
@ -216,7 +223,7 @@ class AuthServiceTest {
BizException exception = Assertions.assertThrows(BizException.class,
() -> newAuthService(roles, accounts, sessions, new InMemoryRoleUkeyBindingRepository(), () -> "unused")
.changeAccountPassword("token-expired-001", 1, "12345678", "Abc1234!"));
.changeAccountPassword("token-expired-001", 1, DIGEST_12345678, DIGEST_ABC1234));
Assertions.assertEquals(ErrorCode.SESSION_INVALID.getCode(), exception.getCode());
Assertions.assertEquals("会话已过期", exception.getMessage());

View File

@ -0,0 +1,28 @@
package com.cisd.tms.modules.auth.service;
import com.cisd.tms.common.exception.BizException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class PasswordDigestValidatorTest {
@Test
void shouldAcceptSm3HexDigest() {
Assertions.assertDoesNotThrow(() -> PasswordDigestValidator.validate(
"fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b"
));
}
@Test
void shouldRejectNonDigestPasswordInput() {
assertInvalid("Sunyard@123");
assertInvalid("12345678");
assertInvalid("fc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647");
assertInvalid("zc216e5eea029a7b5c267ab13cc2e0927a3810e66a2a09b86f8fdbd8d4aa647b");
}
private void assertInvalid(String value) {
BizException exception = Assertions.assertThrows(BizException.class, () -> PasswordDigestValidator.validate(value));
Assertions.assertEquals(PasswordDigestValidator.MESSAGE, exception.getMessage());
}
}