feat:改密
This commit is contained in:
parent
1f7f67a2c7
commit
57ebf819a3
@ -11,7 +11,7 @@ public class AdminChangePasswordRequest {
|
|||||||
private String oldPassword;
|
private String oldPassword;
|
||||||
|
|
||||||
@NotBlank(message = "newPassword is required")
|
@NotBlank(message = "newPassword is required")
|
||||||
@Schema(description = "新口令", example = "中Abc1234")
|
@Schema(description = "新口令", example = "Abc1234!")
|
||||||
private String newPassword;
|
private String newPassword;
|
||||||
|
|
||||||
public String getOldPassword() {
|
public String getOldPassword() {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ public class ChangePasswordRequest {
|
|||||||
private String oldPassword;
|
private String oldPassword;
|
||||||
|
|
||||||
@NotBlank(message = "newPassword is required")
|
@NotBlank(message = "newPassword is required")
|
||||||
@Schema(description = "新口令", example = "中Abc1234")
|
@Schema(description = "新口令", example = "Abc1234!")
|
||||||
private String newPassword;
|
private String newPassword;
|
||||||
|
|
||||||
public String getOldPassword() {
|
public String getOldPassword() {
|
||||||
|
|||||||
@ -5,41 +5,57 @@ import com.cisd.tms.common.exception.BizException;
|
|||||||
|
|
||||||
public final class PasswordComplexityValidator {
|
public final class PasswordComplexityValidator {
|
||||||
|
|
||||||
public static final String MESSAGE = "密码复杂度不符合要求:需包含中文、英文、数字或特殊字符,长度不少于八位";
|
public static final String MESSAGE = "密码复杂度不符合要求:需由8到16位大小写字母、数字、符号组成,且三类均需包含";
|
||||||
|
|
||||||
private PasswordComplexityValidator() {
|
private PasswordComplexityValidator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void validate(String password) {
|
public static void validate(String password) {
|
||||||
String value = password == null ? "" : password;
|
String value = password == null ? "" : password;
|
||||||
if (value.length() < 8 || !containsChinese(value) || !containsEnglish(value) || !containsDigitOrSpecial(value)) {
|
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);
|
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean containsChinese(String value) {
|
private static boolean isAllowedCharacters(String value) {
|
||||||
return value.codePoints().anyMatch(codePoint -> Character.UnicodeScript.of(codePoint) == Character.UnicodeScript.HAN);
|
return value.codePoints().allMatch(codePoint ->
|
||||||
|
isAsciiLetter(codePoint) || isAsciiDigit(codePoint) || isSymbol(codePoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean containsEnglish(String value) {
|
private static boolean containsUppercase(String value) {
|
||||||
return value.codePoints().anyMatch(codePoint ->
|
return value.codePoints().anyMatch(codePoint -> codePoint >= 'A' && codePoint <= 'Z');
|
||||||
(codePoint >= 'A' && codePoint <= 'Z') || (codePoint >= 'a' && codePoint <= 'z'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean containsDigitOrSpecial(String value) {
|
private static boolean containsLowercase(String value) {
|
||||||
return value.codePoints().anyMatch(codePoint ->
|
return value.codePoints().anyMatch(codePoint -> codePoint >= 'a' && codePoint <= 'z');
|
||||||
Character.isDigit(codePoint) || isSpecial(codePoint));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSpecial(int codePoint) {
|
private static boolean containsDigit(String value) {
|
||||||
return !Character.isWhitespace(codePoint)
|
return value.codePoints().anyMatch(PasswordComplexityValidator::isAsciiDigit);
|
||||||
&& !Character.isDigit(codePoint)
|
|
||||||
&& !isChineseOrEnglish(codePoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isChineseOrEnglish(int codePoint) {
|
private static boolean containsSymbol(String value) {
|
||||||
return Character.UnicodeScript.of(codePoint) == Character.UnicodeScript.HAN
|
return value.codePoints().anyMatch(PasswordComplexityValidator::isSymbol);
|
||||||
|| (codePoint >= 'A' && codePoint <= 'Z')
|
}
|
||||||
|| (codePoint >= 'a' && codePoint <= 'z');
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -214,13 +214,13 @@ class AuthControllerTest {
|
|||||||
.content("""
|
.content("""
|
||||||
{
|
{
|
||||||
"oldPassword": "12345678",
|
"oldPassword": "12345678",
|
||||||
"newPassword": "中Abc1234"
|
"newPassword": "Abc1234!"
|
||||||
}
|
}
|
||||||
"""))
|
"""))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(containsString("\"success\":true")));
|
.andExpect(content().string(containsString("\"success\":true")));
|
||||||
|
|
||||||
Mockito.verify(authService).changeFullAccountPassword("token-change-001", 1, "12345678", "中Abc1234");
|
Mockito.verify(authService).changeFullAccountPassword("token-change-001", 1, "12345678", "Abc1234!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -239,13 +239,13 @@ class AuthControllerTest {
|
|||||||
.content("""
|
.content("""
|
||||||
{
|
{
|
||||||
"oldPassword": "12345678",
|
"oldPassword": "12345678",
|
||||||
"newPassword": "中Abc1234"
|
"newPassword": "Abc1234!"
|
||||||
}
|
}
|
||||||
"""))
|
"""))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(containsString("\"success\":true")));
|
.andExpect(content().string(containsString("\"success\":true")));
|
||||||
|
|
||||||
Mockito.verify(authService).changeLimitedAccountPassword("token-change-limited-001", "audit-admin-01", "12345678", "中Abc1234");
|
Mockito.verify(authService).changeLimitedAccountPassword("token-change-limited-001", "audit-admin-01", "12345678", "Abc1234!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -312,13 +312,13 @@ class AuthControllerTest {
|
|||||||
.content("""
|
.content("""
|
||||||
{
|
{
|
||||||
"oldPassword": "12345678",
|
"oldPassword": "12345678",
|
||||||
"newPassword": "中Abc1234"
|
"newPassword": "Abc1234!"
|
||||||
}
|
}
|
||||||
"""))
|
"""))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(containsString("\"success\":true")));
|
.andExpect(content().string(containsString("\"success\":true")));
|
||||||
|
|
||||||
Mockito.verify(authAdminService).changeFullAccountPassword("SUPER_ADMIN", "FULL", "KEY_ADMIN", 1, "12345678", "中Abc1234");
|
Mockito.verify(authAdminService).changeFullAccountPassword("SUPER_ADMIN", "FULL", "KEY_ADMIN", 1, "12345678", "Abc1234!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -338,13 +338,13 @@ class AuthControllerTest {
|
|||||||
.content("""
|
.content("""
|
||||||
{
|
{
|
||||||
"oldPassword": "12345678",
|
"oldPassword": "12345678",
|
||||||
"newPassword": "中Abc1234"
|
"newPassword": "Abc1234!"
|
||||||
}
|
}
|
||||||
"""))
|
"""))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(containsString("\"success\":true")));
|
.andExpect(content().string(containsString("\"success\":true")));
|
||||||
|
|
||||||
Mockito.verify(authAdminService).changeLimitedAccountPassword("SUPER_ADMIN", "FULL", "AUDIT_ADMIN", "audit-admin-01", "12345678", "中Abc1234");
|
Mockito.verify(authAdminService).changeLimitedAccountPassword("SUPER_ADMIN", "FULL", "AUDIT_ADMIN", "audit-admin-01", "12345678", "Abc1234!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -159,12 +159,12 @@ class AuthAdminServiceTest {
|
|||||||
RoleCode.KEY_ADMIN.getCode(),
|
RoleCode.KEY_ADMIN.getCode(),
|
||||||
1,
|
1,
|
||||||
"12345678",
|
"12345678",
|
||||||
"中Abc1234"
|
"Abc1234!"
|
||||||
);
|
);
|
||||||
|
|
||||||
AuthFullAccountEntity changed = fullAccounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
AuthFullAccountEntity changed = fullAccounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
||||||
Assertions.assertEquals("salt-full-admin", changed.getPasswordSalt());
|
Assertions.assertEquals("salt-full-admin", changed.getPasswordSalt());
|
||||||
Assertions.assertEquals("HASH:中Abc1234:salt-full-admin", changed.getPasswordHash());
|
Assertions.assertEquals("HASH:Abc1234!:salt-full-admin", changed.getPasswordHash());
|
||||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), changed.getStatus());
|
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), changed.getStatus());
|
||||||
Assertions.assertTrue(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
Assertions.assertTrue(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), changed.getPasswordChangedAt());
|
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), changed.getPasswordChangedAt());
|
||||||
@ -198,12 +198,12 @@ class AuthAdminServiceTest {
|
|||||||
RoleCode.AUDIT_ADMIN.getCode(),
|
RoleCode.AUDIT_ADMIN.getCode(),
|
||||||
"audit-admin-01",
|
"audit-admin-01",
|
||||||
"12345678",
|
"12345678",
|
||||||
"中Abc1234"
|
"Abc1234!"
|
||||||
);
|
);
|
||||||
|
|
||||||
AuthUserAccountEntity changed = userAccounts.findByUsername("audit-admin-01").orElseThrow();
|
AuthUserAccountEntity changed = userAccounts.findByUsername("audit-admin-01").orElseThrow();
|
||||||
Assertions.assertEquals("salt-limited-admin", changed.getPasswordSalt());
|
Assertions.assertEquals("salt-limited-admin", changed.getPasswordSalt());
|
||||||
Assertions.assertEquals("HASH:中Abc1234:salt-limited-admin", changed.getPasswordHash());
|
Assertions.assertEquals("HASH:Abc1234!:salt-limited-admin", changed.getPasswordHash());
|
||||||
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), changed.getStatus());
|
Assertions.assertEquals(RoleAccountStatus.ACTIVE.name(), changed.getStatus());
|
||||||
Assertions.assertTrue(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
Assertions.assertTrue(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), changed.getPasswordChangedAt());
|
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 3, 0), changed.getPasswordChangedAt());
|
||||||
@ -235,7 +235,7 @@ class AuthAdminServiceTest {
|
|||||||
RoleCode.AUDIT_ADMIN.getCode(),
|
RoleCode.AUDIT_ADMIN.getCode(),
|
||||||
"audit-admin-01",
|
"audit-admin-01",
|
||||||
"bad-password",
|
"bad-password",
|
||||||
"中Abc1234"
|
"Abc1234!"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -273,7 +273,7 @@ class AuthAdminServiceTest {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
Assertions.assertEquals("密码复杂度不符合要求:需包含中文、英文、数字或特殊字符,长度不少于八位", exception.getMessage());
|
Assertions.assertEquals("密码复杂度不符合要求:需由8到16位大小写字母、数字、符号组成,且三类均需包含", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -289,11 +289,11 @@ class AuthServiceTest {
|
|||||||
() -> "unused"
|
() -> "unused"
|
||||||
);
|
);
|
||||||
|
|
||||||
service.changeFullAccountPassword("token-change-001", 1, "12345678", "中Abc1234");
|
service.changeFullAccountPassword("token-change-001", 1, "12345678", "Abc1234!");
|
||||||
|
|
||||||
AuthFullAccountEntity changed = fullAccounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
AuthFullAccountEntity changed = fullAccounts.findByRoleCodeAndUid(RoleCode.KEY_ADMIN.getCode(), 1).orElseThrow();
|
||||||
Assertions.assertEquals("salt-test", changed.getPasswordSalt());
|
Assertions.assertEquals("salt-test", changed.getPasswordSalt());
|
||||||
Assertions.assertEquals("HASH:中Abc1234:salt-test", changed.getPasswordHash());
|
Assertions.assertEquals("HASH:Abc1234!:salt-test", changed.getPasswordHash());
|
||||||
Assertions.assertEquals(0, changed.getFailedCount());
|
Assertions.assertEquals(0, changed.getFailedCount());
|
||||||
Assertions.assertNull(changed.getLockedUntil());
|
Assertions.assertNull(changed.getLockedUntil());
|
||||||
Assertions.assertFalse(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
Assertions.assertFalse(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||||
@ -323,11 +323,11 @@ class AuthServiceTest {
|
|||||||
() -> "unused"
|
() -> "unused"
|
||||||
);
|
);
|
||||||
|
|
||||||
service.changeLimitedAccountPassword("token-change-limited-001", "audit-admin-01", "12345678", "中Abc1234");
|
service.changeLimitedAccountPassword("token-change-limited-001", "audit-admin-01", "12345678", "Abc1234!");
|
||||||
|
|
||||||
AuthUserAccountEntity changed = userAccounts.findByUsername("audit-admin-01").orElseThrow();
|
AuthUserAccountEntity changed = userAccounts.findByUsername("audit-admin-01").orElseThrow();
|
||||||
Assertions.assertEquals("salt-test", changed.getPasswordSalt());
|
Assertions.assertEquals("salt-test", changed.getPasswordSalt());
|
||||||
Assertions.assertEquals("HASH:中Abc1234:salt-test", changed.getPasswordHash());
|
Assertions.assertEquals("HASH:Abc1234!:salt-test", changed.getPasswordHash());
|
||||||
Assertions.assertEquals(0, changed.getFailedCount());
|
Assertions.assertEquals(0, changed.getFailedCount());
|
||||||
Assertions.assertNull(changed.getLockedUntil());
|
Assertions.assertNull(changed.getLockedUntil());
|
||||||
Assertions.assertFalse(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
Assertions.assertFalse(Boolean.TRUE.equals(changed.getNeedChangePassword()));
|
||||||
@ -362,7 +362,7 @@ class AuthServiceTest {
|
|||||||
() -> service.changeLimitedAccountPassword("token-change-limited-001", "audit-admin-01", "12345678", "87654321")
|
() -> service.changeLimitedAccountPassword("token-change-limited-001", "audit-admin-01", "12345678", "87654321")
|
||||||
);
|
);
|
||||||
|
|
||||||
Assertions.assertEquals("密码复杂度不符合要求:需包含中文、英文、数字或特殊字符,长度不少于八位", exception.getMessage());
|
Assertions.assertEquals("密码复杂度不符合要求:需由8到16位大小写字母、数字、符号组成,且三类均需包含", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -388,7 +388,7 @@ class AuthServiceTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class,
|
BizException exception = Assertions.assertThrows(BizException.class,
|
||||||
() -> service.changeFullAccountPassword("token-expired-001", 1, "12345678", "中Abc1234"));
|
() -> service.changeFullAccountPassword("token-expired-001", 1, "12345678", "Abc1234!"));
|
||||||
|
|
||||||
Assertions.assertEquals(ErrorCode.SESSION_INVALID.getCode(), exception.getCode());
|
Assertions.assertEquals(ErrorCode.SESSION_INVALID.getCode(), exception.getCode());
|
||||||
Assertions.assertEquals("session expired", exception.getMessage());
|
Assertions.assertEquals("session expired", exception.getMessage());
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
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("Abc1234!");
|
||||||
|
assertInvalid("Abc 123!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertInvalid(String password) {
|
||||||
|
BizException exception = Assertions.assertThrows(
|
||||||
|
BizException.class,
|
||||||
|
() -> PasswordComplexityValidator.validate(password)
|
||||||
|
);
|
||||||
|
Assertions.assertEquals(PasswordComplexityValidator.MESSAGE, exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user