diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/AdminChangePasswordRequest.java b/src/main/java/com/cisd/tms/modules/auth/dto/AdminChangePasswordRequest.java index 676e94e..928cef3 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/AdminChangePasswordRequest.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/AdminChangePasswordRequest.java @@ -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() { diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/ChangePasswordRequest.java b/src/main/java/com/cisd/tms/modules/auth/dto/ChangePasswordRequest.java index 8b1e853..3e3174f 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/ChangePasswordRequest.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/ChangePasswordRequest.java @@ -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() { diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginAccountRequest.java b/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginAccountRequest.java index 6f96eb8..8364664 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginAccountRequest.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/PasswordLoginAccountRequest.java @@ -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() { diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginProof.java b/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginProof.java index a0c05b4..053fd44 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginProof.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/UkeyLoginProof.java @@ -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() { diff --git a/src/main/java/com/cisd/tms/modules/auth/service/PasswordDigestValidator.java b/src/main/java/com/cisd/tms/modules/auth/service/PasswordDigestValidator.java new file mode 100644 index 0000000..fb9a98a --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/auth/service/PasswordDigestValidator.java @@ -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); + } + } +} diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java index dce0697..54586f3 100644 --- a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java @@ -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()) { diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthSecurityResetServiceImpl.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthSecurityResetServiceImpl.java index 7d94a66..7af402b 100644 --- a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthSecurityResetServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthSecurityResetServiceImpl.java @@ -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" ) ); diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java index 6d38137..16618e6 100644 --- a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java @@ -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); } diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/PasswordDigestSupport.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/PasswordDigestSupport.java new file mode 100644 index 0000000..a202c7b --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/PasswordDigestSupport.java @@ -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); + } +} diff --git a/src/main/resources/db/migration/V1__tms_schema_full.sql b/src/main/resources/db/migration/V1__tms_schema_full.sql index 0decc94..d19aa04 100644 --- a/src/main/resources/db/migration/V1__tms_schema_full.sql +++ b/src/main/resources/db/migration/V1__tms_schema_full.sql @@ -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白名单表 diff --git a/src/test/java/com/cisd/tms/modules/auth/service/AuthAdminServiceTest.java b/src/test/java/com/cisd/tms/modules/auth/service/AuthAdminServiceTest.java index 1005188..e841dba 100644 --- a/src/test/java/com/cisd/tms/modules/auth/service/AuthAdminServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/auth/service/AuthAdminServiceTest.java @@ -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()); diff --git a/src/test/java/com/cisd/tms/modules/auth/service/AuthSecurityResetServiceTest.java b/src/test/java/com/cisd/tms/modules/auth/service/AuthSecurityResetServiceTest.java index 71216dd..1a77e2d 100644 --- a/src/test/java/com/cisd/tms/modules/auth/service/AuthSecurityResetServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/auth/service/AuthSecurityResetServiceTest.java @@ -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()); diff --git a/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java b/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java index b9a5a72..45979de 100644 --- a/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java @@ -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()); diff --git a/src/test/java/com/cisd/tms/modules/auth/service/PasswordDigestValidatorTest.java b/src/test/java/com/cisd/tms/modules/auth/service/PasswordDigestValidatorTest.java new file mode 100644 index 0000000..6717d94 --- /dev/null +++ b/src/test/java/com/cisd/tms/modules/auth/service/PasswordDigestValidatorTest.java @@ -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()); + } +}