fix:密码摘要

This commit is contained in:
waner 2026-05-21 16:56:57 +08:00
parent a5c73c82b0
commit 06a76d5f39
4 changed files with 22 additions and 113 deletions

View File

@ -1,61 +0,0 @@
package com.cisd.tms.modules.auth.service;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
public final class PasswordComplexityValidator {
public static final String MESSAGE = "密码复杂度不符合要求需由8到16位大小写字母、数字、符号组成且三类均需包含";
private PasswordComplexityValidator() {
}
public static void validate(String password) {
String value = password == null ? "" : password;
if (value.isBlank()
|| value.length() < 8
|| value.length() > 16
|| !isAllowedCharacters(value)
|| !containsUppercase(value)
|| !containsLowercase(value)
|| !containsDigit(value)
|| !containsSymbol(value)) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), MESSAGE);
}
}
private static boolean isAllowedCharacters(String value) {
return value.codePoints().allMatch(codePoint ->
isAsciiLetter(codePoint) || isAsciiDigit(codePoint) || isSymbol(codePoint));
}
private static boolean containsUppercase(String value) {
return value.codePoints().anyMatch(codePoint -> codePoint >= 'A' && codePoint <= 'Z');
}
private static boolean containsLowercase(String value) {
return value.codePoints().anyMatch(codePoint -> codePoint >= 'a' && codePoint <= 'z');
}
private static boolean containsDigit(String value) {
return value.codePoints().anyMatch(PasswordComplexityValidator::isAsciiDigit);
}
private static boolean containsSymbol(String value) {
return value.codePoints().anyMatch(PasswordComplexityValidator::isSymbol);
}
private static boolean isAsciiLetter(int codePoint) {
return (codePoint >= 'A' && codePoint <= 'Z') || (codePoint >= 'a' && codePoint <= 'z');
}
private static boolean isAsciiDigit(int codePoint) {
return codePoint >= '0' && codePoint <= '9';
}
private static boolean isSymbol(int codePoint) {
return codePoint >= 33 && codePoint <= 126
&& !isAsciiDigit(codePoint)
&& !isAsciiLetter(codePoint);
}
}

View File

@ -19,6 +19,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@AutoConfigureMockMvc @AutoConfigureMockMvc
class TmsApplicationTests { class TmsApplicationTests {
private static final String DEFAULT_PASSWORD_DIGEST =
"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})'",
Pattern.DOTALL Pattern.DOTALL
@ -115,7 +118,7 @@ class TmsApplicationTests {
String hash = matcher.group(1); String hash = 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("Sunyard@123", salt, hash), "hash mismatch for 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(5, rows); org.junit.jupiter.api.Assertions.assertEquals(5, rows);
} }

View File

@ -28,6 +28,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
class AuthControllerTest { class AuthControllerTest {
private static final String DIGEST_12345678 =
"0fffff81e971fa3f09107abf77931463fc0710bfb8962efeae3d5654b073bb0c";
private static final String DIGEST_ABC1234 =
"6b66c27d356b7fef3e2c2a986043031a410bb564205f22525ba022591671a7e5";
@Test @Test
void shouldLoginWithPasswordPayload() throws Exception { void shouldLoginWithPasswordPayload() throws Exception {
AuthService authService = Mockito.mock(AuthService.class); AuthService authService = Mockito.mock(AuthService.class);
@ -51,13 +56,13 @@ class AuthControllerTest {
"accounts": [ "accounts": [
{ {
"uid": 1, "uid": 1,
"password": "12345678" "password": "%s"
} }
], ],
"captchaCode": "ABCD", "captchaCode": "ABCD",
"captchaId": "captcha-001" "captchaId": "captcha-001"
} }
""")) """.formatted(DIGEST_12345678)))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().string(containsString("\"token\":\"token-password-001\""))) .andExpect(content().string(containsString("\"token\":\"token-password-001\"")))
.andExpect(content().string(containsString("\"authLevel\":\"LIMITED\""))); .andExpect(content().string(containsString("\"authLevel\":\"LIMITED\"")));
@ -113,7 +118,7 @@ class AuthControllerTest {
{ {
"pubKey": "PUB-1", "pubKey": "PUB-1",
"uid": 1, "uid": 1,
"password": "11111111", "password": "%s",
"serverRandom": "RB-1", "serverRandom": "RB-1",
"issueSignature": "ISSUE-1", "issueSignature": "ISSUE-1",
"loginPayload": "LOGIN-DATA-1", "loginPayload": "LOGIN-DATA-1",
@ -122,7 +127,7 @@ class AuthControllerTest {
{ {
"pubKey": "PUB-2", "pubKey": "PUB-2",
"uid": 2, "uid": 2,
"password": "22222222", "password": "%s",
"serverRandom": "RB-2", "serverRandom": "RB-2",
"issueSignature": "ISSUE-2", "issueSignature": "ISSUE-2",
"loginPayload": "LOGIN-DATA-2", "loginPayload": "LOGIN-DATA-2",
@ -130,7 +135,7 @@ class AuthControllerTest {
} }
] ]
} }
""")) """.formatted(DIGEST_12345678, DIGEST_ABC1234)))
.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\"")));
@ -215,14 +220,14 @@ class AuthControllerTest {
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(""" .content("""
{ {
"oldPassword": "12345678", "oldPassword": "%s",
"newPassword": "Abc1234!" "newPassword": "%s"
} }
""")) """.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, "12345678", "Abc1234!"); Mockito.verify(authService).changeAccountPassword("token-change-001", 1, DIGEST_12345678, DIGEST_ABC1234);
} }
@Test @Test
@ -241,14 +246,14 @@ class AuthControllerTest {
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(""" .content("""
{ {
"oldPassword": "12345678", "oldPassword": "%s",
"newPassword": "Abc1234!" "newPassword": "%s"
} }
""")) """.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(authAdminService).changeAccountPassword("SUPER_ADMIN", "FULL", "KEY_ADMIN", 1, "12345678", "Abc1234!"); Mockito.verify(authAdminService).changeAccountPassword("SUPER_ADMIN", "FULL", "KEY_ADMIN", 1, DIGEST_12345678, DIGEST_ABC1234);
} }
@Test @Test

View File

@ -1,38 +0,0 @@
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 PasswordComplexityValidatorTest {
@Test
void shouldAcceptPasswordWithLettersDigitsAndSymbolInAllowedLength() {
Assertions.assertDoesNotThrow(() -> PasswordComplexityValidator.validate("Abc1234!"));
Assertions.assertDoesNotThrow(() -> PasswordComplexityValidator.validate("Aa123456789012!"));
}
@Test
void shouldRejectPasswordOutsideNewComplexityPolicy() {
assertInvalid(null);
assertInvalid("");
assertInvalid(" ");
assertInvalid("Abc123!");
assertInvalid("Abc1234567890123!");
assertInvalid("abc1234!");
assertInvalid("ABC1234!");
assertInvalid("Abcdefg!");
assertInvalid("Abc12345");
assertInvalid("中Abc1234!");
assertInvalid("Abc34!");
assertInvalid("Abc 123!");
}
private void assertInvalid(String password) {
BizException exception = Assertions.assertThrows(
BizException.class,
() -> PasswordComplexityValidator.validate(password)
);
Assertions.assertEquals(PasswordComplexityValidator.MESSAGE, exception.getMessage());
}
}