PassayUtils 是一個密碼驗證和產生的工具類別。此類別基於 Passay 框架,提供密碼強度驗證和安全密碼產生功能,特別適合需要嚴格密碼規則的應用系統。
// 驗證密碼是否符合規則
String username = "user123";
String password = "MyP@ssw0rd";
List<String> errors = PassayUtils.passwordIsValid(username, password);
// 檢查驗證結果
if (errors.isEmpty()) {
System.out.println("密碼符合規則");
} else {
errors.forEach(System.out::println); // 顯示所有違規訊息
}
// 基本規則設定
List rules = PassayUtils.passwordRules();
// 3. 至少一個小寫字母
這些測試案例展示了以下功能的正確性:
@Test
void testValidPassword() {
// 測試有效密碼(符合所有規則)
String validPassword = "Test123!@";
List<String> result = PassayUtils.passwordIsValid("user", validPassword);
assertTrue(result.isEmpty(), "有效密碼應該不會有錯誤訊息");
}
@ParameterizedTest
@CsvSource({
"short1!, 密碼長度不足", // 長度不足
"toolongpassword123!@, 密碼過長", // 長度過長
"lowercase123!, 缺少大寫字母", // 缺少大寫字母
"UPPERCASE123!, 缺少小寫字母", // 缺少小寫字母
"TestNoDigit!@, 缺少數字", // 缺少數字
"TestNoSymbol123, 缺少特殊字元", // 缺少特殊字元
"Test With Space!, 含有空白字元", // 含空白
"abcdefghijk1!, 含有連續字母", // 連續字母
"12345Test!@, 含有連續數字", // 連續數字
"TestAAAA123!, 重複字元過多" // 重複字元
})
void testInvalidPasswords(String password, String testCase) {
List<String> errors = PassayUtils.passwordIsValid("user", password);
assertFalse(errors.isEmpty(),
String.format("案例:%s - 應該要偵測到密碼不符合規則", testCase));
}
@Test
void testGeneratedPasswordValidity() {
// 測試產生的密碼是否符合所有規則
String generatedPassword = PassayUtils.generatePassword();
assertNotNull(generatedPassword, "產生的密碼不應為 null");
assertEquals(8, generatedPassword.length(), "產生的密碼長度應為 8 個字元");
List<String> errors = PassayUtils.passwordIsValid("user", generatedPassword);
assertTrue(errors.isEmpty(),
String.format("產生的密碼 '%s' 應符合所有規則,但收到錯誤:%s",
generatedPassword, errors));
}
@Test
void testMultipleGeneratedPasswords() {
// 測試多次產生的密碼是否都不同
String password1 = PassayUtils.generatePassword();
String password2 = PassayUtils.generatePassword();
String password3 = PassayUtils.generatePassword();
assertNotEquals(password1, password2, "連續產生的密碼應該不同");
assertNotEquals(password2, password3, "連續產生的密碼應該不同");
assertNotEquals(password1, password3, "連續產生的密碼應該不同");
}
@Test
void testPasswordRules() {
// 測試密碼規則設定
List<Rule> rules = PassayUtils.passwordRules();
assertNotNull(rules, "密碼規則清單不應為 null");
assertFalse(rules.isEmpty(), "密碼規則清單不應為空");
// 檢查是否包含所有必要的規則類型
assertTrue(rules.stream().anyMatch(r -> r instanceof LengthRule),
"應包含長度規則");
assertTrue(rules.stream().anyMatch(r -> r instanceof CharacterRule),
"應包含字元規則");
assertTrue(rules.stream().anyMatch(r -> r instanceof WhitespaceRule),
"應包含空白字元規則");
}
有效密碼測試
無效密碼測試
密碼產生測試
隨機性測試
規則設定測試
### 2. 密碼產生
#### 產生安全密碼
```java
// 產生符合所有規則的 8 位元密碼
String newPassword = PassayUtils.generatePassword();
// 使用密碼產生器
PasswordGenerator generator = new PasswordGenerator();
List<CharacterRule> rules = new ArrayList<>();
rules.add(new CharacterRule(EnglishCharacterData.UpperCase, 1));
rules.add(new CharacterRule(EnglishCharacterData.LowerCase, 1));
String password = generator.generatePassword(8, rules);
new LengthRule(8, 16)
new CharacterRule(EnglishCharacterData.UpperCase, 1)
new CharacterRule(EnglishCharacterData.LowerCase, 1)
new CharacterRule(EnglishCharacterData.Digit, 1)
new CharacterRule(EnglishCharacterData.Special, 1)
// 建立自定義驗證器
List<Rule> customRules = new ArrayList<>();
customRules.add(new LengthRule(10, 20)); // 自定義長度
customRules.add(new WhitespaceRule()); // 禁止空白
customRules.add(new UsernameRule()); // 禁止使用用戶名
PasswordValidator validator = new PasswordValidator(customRules);
// 執行驗證
RuleResult result = validator.validate(new PasswordData(password));
package tw.lewishome.webapp.base.utility.common;
import java.util.ArrayList;
import java.util.List;
import org.passay.CharacterRule;
import org.passay.EnglishCharacterData;
import org.passay.EnglishSequenceData;
import org.passay.IllegalSequenceRule;
import org.passay.LengthRule;
import org.passay.PasswordData;
import org.passay.PasswordGenerator;
import org.passay.PasswordValidator;
import org.passay.RepeatCharactersRule;
import org.passay.Rule;
import org.passay.RuleResult;
import org.passay.UsernameRule;
import org.passay.WhitespaceRule;
/**
* PassayUtils 提供密碼驗證與密碼產生相關的工具方法。
*
* 本類別主要結合 Passay 密碼驗證框架,依據自訂密碼規則進行密碼強度檢查,
*
*
*
* 主要功能:
* <ul>
* <li>依據密碼規則驗證密碼有效性。</li>
* <li>依密碼規則自動產生 8 位元強密碼。</li>
* </ul>
*
*
*
* 使用範例:
*
* <pre>
* List<String> errors = PassayUtils.passwordIsValid("user", "password123");
* if (!errors.isEmpty()) {
* // 密碼不符合規則,errors 內包含所有違規訊息
* }
*
* String newPassword = PassayUtils.generatePassword();
* // newPassword 為符合密碼規則的隨機密碼
* </pre>
*
*
* @author Lewis
*/
public class PassayUtils {
/** Private constructor to prevent instantiation */
private PassayUtils() {
throw new IllegalStateException("This is a utility class and cannot be instantiated");
}
/**
* 依 PassayUtilsRules.passwordRules() 的密碼規則來驗證
*
* @param username username
* @param password password
* @return ArrayList String
*/
public static List<String> passwordIsValid(String username, String password) {
// Setup message source for internationalization
PasswordValidator validator = new PasswordValidator(passwordRules());
PasswordData passwordData = new PasswordData(username, password);
RuleResult result = validator.validate(passwordData);
List<String> validResult = new ArrayList<>();
if (result.isValid()) {
return validResult;
} else {
return new ArrayList<>(validator.getMessages(result));
}
}
/**
* 依 passwordRules() 來產生8個字元的密碼
*
* @return String Generated Password
*/
public static String generatePassword() {
PasswordGenerator passwordGenerator = new PasswordGenerator();
List<CharacterRule> listCharacterRule = new ArrayList<>();
List<Rule> rules = passwordRules();
for (Rule oneRule : rules) {
if (oneRule instanceof CharacterRule) {
listCharacterRule.add((CharacterRule) oneRule);
}
}
return passwordGenerator.generatePassword(8, listCharacterRule);
}
/**
* 密碼規則
* 1. 8-16 個字元
* 2. 至少一個英文大寫
* 3. 至少一個英文小寫
* 4. 至少一個數字
* 5. 至少一個符號
* 6. 不可以與 username 相同
* 7. 不可以有空白字元
* 8. 不可以連續英文 >= 5 如 'abcde' 參數 false 表示z-a 不算連續如 'xyzabc'`
* 9. 不可以連續數字 >= 5 如 '34567' 參數 false 表示z-a 不算連續如 '09123'
* 10. 不可以重複文數字 >= 4 如 '1111' 或 'aaaa'
*
* @return ArrayList Rule
*/
public static List<Rule> passwordRules() {
List<Rule> pwdRules = new ArrayList<Rule>();
// 1. length between 8 and 16 characters
pwdRules.add(new LengthRule(8, 16));
// 2. at least one upper-case character
pwdRules.add(new CharacterRule(EnglishCharacterData.UpperCase, 1));
// 3. at least one lower-case character
pwdRules.add(new CharacterRule(EnglishCharacterData.LowerCase, 1));
// 4. at least one digit character
pwdRules.add(new CharacterRule(EnglishCharacterData.Digit, 1));
// 5. at least one symbol (special character)
pwdRules.add(new CharacterRule(EnglishCharacterData.Special, 1));
// 6. password can not same as username
pwdRules.add(new UsernameRule());
// 7. no whitespace
pwdRules.add(new WhitespaceRule());
// define some illegal sequences that will fail when >= 5 chars long
// alphabetical is of the form 'abcde', numerical is '34567'
// the false parameter indicates that wrapped sequences are allowed; e.g.
// 'xyzabc'
// 8. define some illegal sequences that will fail when >= 5 chars long
pwdRules.add(new IllegalSequenceRule(EnglishSequenceData.Alphabetical, 5, false));
// 9. define some illegal sequences that will fail when >= 5 chars long
pwdRules.add(new IllegalSequenceRule(EnglishSequenceData.Numerical, 5, false));
// 10 can not repeat long >= 4 char
pwdRules.add(new RepeatCharactersRule(4));
// pwdRules.add(new AllowedCharacterRule(new char[] { 'a', 'b', 'c' }));
return pwdRules;
}
}
package tw.lewishome.webapp.base.utility.common;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
/**
* PassayUtils 的單元測試類別
*/
class PassayTest {
@Test
void testValidPassword() {
// 測試有效密碼(符合所有規則)
String validPassword = "Test123!@";
List<String> result = PassayUtils.passwordIsValid("user", validPassword);
assertTrue(result.isEmpty(), "有效密碼應該不會有錯誤訊息");
}
@ParameterizedTest
@CsvSource({
"short1!, 密碼長度不足", // 長度不足
"toolongpassword123!@, 密碼過長", // 長度過長
"lowercase123!, 缺少大寫字母", // 缺少大寫字母
"UPPERCASE123!, 缺少小寫字母", // 缺少小寫字母
"TestNoDigit!@, 缺少數字", // 缺少數字
"TestNoSymbol123, 缺少特殊字元", // 缺少特殊字元
"Test With Space!, 含有空白字元", // 含空白
"abcdefghijk1!, 含有連續字母", // 連續字母
"12345Test!@, 含有連續數字", // 連續數字
"TestAAAA123!, 重複字元過多" // 重複字元
})
void testInvalidPasswords(String password, String testCase) {
List<String> errors = PassayUtils.passwordIsValid("user", password);
assertFalse(errors.isEmpty(),
String.format("案例:%s - 應該要偵測到密碼不符合規則", testCase));
}
@Test
void testPasswordMatchesUsername() {
String username = "TestUser123!";
List<String> errors = PassayUtils.passwordIsValid(username, username);
assertFalse(errors.isEmpty(), "密碼不應該與使用者名稱相同");
}
@Test
void testGeneratedPasswordValidity() {
// 測試產生的密碼是否符合所有規則
String generatedPassword = PassayUtils.generatePassword();
assertNotNull(generatedPassword, "產生的密碼不應為 null");
assertEquals(8, generatedPassword.length(), "產生的密碼長度應為 8 個字元");
List<String> errors = PassayUtils.passwordIsValid("user", generatedPassword);
assertTrue(errors.isEmpty(),
String.format("產生的密碼 '%s' 應符合所有規則,但收到錯誤:%s",
generatedPassword, errors));
}
@Test
void testMultipleGeneratedPasswords() {
// 測試多次產生的密碼是否都不同
String password1 = PassayUtils.generatePassword();
String password2 = PassayUtils.generatePassword();
String password3 = PassayUtils.generatePassword();
assertNotEquals(password1, password2, "連續產生的密碼應該不同");
assertNotEquals(password2, password3, "連續產生的密碼應該不同");
assertNotEquals(password1, password3, "連續產生的密碼應該不同");
}
@Test
void testPasswordRules() {
// 測試密碼規則設定
List<org.passay.Rule> rules = PassayUtils.passwordRules();
assertNotNull(rules, "密碼規則清單不應為 null");
assertFalse(rules.isEmpty(), "密碼規則清單不應為空");
// 檢查是否包含所有必要的規則類型
assertTrue(rules.stream().anyMatch(r -> r instanceof org.passay.LengthRule),
"應包含長度規則");
assertTrue(rules.stream().anyMatch(r -> r instanceof org.passay.CharacterRule),
"應包含字元規則");
assertTrue(rules.stream().anyMatch(r -> r instanceof org.passay.WhitespaceRule),
"應包含空白字元規則");
assertTrue(rules.stream().anyMatch(r -> r instanceof org.passay.IllegalSequenceRule),
"應包含非法序列規則");
assertTrue(rules.stream().anyMatch(r -> r instanceof org.passay.RepeatCharactersRule),
"應包含重複字元規則");
}
}