iT邦幫忙

0

應用系統建置前準備工具 - PassayUtils 密碼工具

  • 分享至 

  • xImage
  •  

PassayUtils 密碼工具類別

概述

PassayUtils 是一個密碼驗證和產生的工具類別。此類別基於 Passay 框架,提供密碼強度驗證和安全密碼產生功能,特別適合需要嚴格密碼規則的應用系統。

專案相關程式

  • GlobalConstants

第三方元件(Dependency)

  • org.passay
  • lombok.extern.slf4j.Slf4j;

主要功能

1. 密碼驗證

基本驗證

// 驗證密碼是否符合規則
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. 至少一個小寫字母

單元測試範例

這些測試案例展示了以下功能的正確性:

  • 密碼驗證功能
  • 密碼產生功能
  • 密碼規則檢查
  • 各種邊界條件與無效輸入
  • 隨機性與安全性要求

1. 有效密碼測試

@Test
void testValidPassword() {
    // 測試有效密碼(符合所有規則)
    String validPassword = "Test123!@";
    List<String> result = PassayUtils.passwordIsValid("user", validPassword);
    assertTrue(result.isEmpty(), "有效密碼應該不會有錯誤訊息");
}

2. 無效密碼測試

@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));
}

3. 密碼產生測試

@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));
}

4. 密碼隨機性測試

@Test
void testMultipleGeneratedPasswords() {
    // 測試多次產生的密碼是否都不同
    String password1 = PassayUtils.generatePassword();
    String password2 = PassayUtils.generatePassword();
    String password3 = PassayUtils.generatePassword();
    
    assertNotEquals(password1, password2, "連續產生的密碼應該不同");
    assertNotEquals(password2, password3, "連續產生的密碼應該不同");
    assertNotEquals(password1, password3, "連續產生的密碼應該不同");
}

5. 密碼規則測試

@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),
        "應包含空白字元規則");
}

測試案例說明

  1. 有效密碼測試

    • 驗證符合所有規則的密碼
    • 確認不會產生錯誤訊息
    • 測試基本驗證功能
  2. 無效密碼測試

    • 測試各種不合規則的密碼
    • 驗證長度限制
    • 檢查字元組成要求
    • 測試特殊規則(連續、重複)
  3. 密碼產生測試

    • 驗證產生密碼的格式
    • 確認符合所有規則
    • 檢查密碼長度
  4. 隨機性測試

    • 確保產生密碼的唯一性
    • 測試隨機性
    • 避免重複密碼
  5. 規則設定測試

    • 驗證規則清單完整性
    • 確認必要規則存在
    • 測試規則組合
      // 4. 至少一個數字
      // 5. 至少一個特殊符號

### 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);

安全規則

1. 密碼長度規則

  • 最小長度:8 個字元
  • 最大長度:16 個字元
new LengthRule(8, 16)

2. 字元組成規則

  • 必須包含大寫字母
  • 必須包含小寫字母
  • 必須包含數字
  • 必須包含特殊符號
new CharacterRule(EnglishCharacterData.UpperCase, 1)
new CharacterRule(EnglishCharacterData.LowerCase, 1)
new CharacterRule(EnglishCharacterData.Digit, 1)
new CharacterRule(EnglishCharacterData.Special, 1)

3. 安全限制規則

  • 不可與使用者名稱相同
  • 不可包含空白字元
  • 不可有 5 個以上連續字母(如:abcde)
  • 不可有 5 個以上連續數字(如:12345)
  • 不可重複同一字元 4 次以上(如:aaaa)

注意事項

1. 密碼驗證

  • 確保傳入正確的使用者名稱,避免繞過使用者名稱檢查
  • 錯誤訊息為英文,可能需要額外的訊息轉換機制
  • 密碼驗證失敗時會返回所有違規原因

2. 密碼產生

  • 產生的密碼固定為 8 位元
  • 產生的密碼保證符合所有規則
  • 使用安全的隨機數產生器

3. 安全考量

  • 不要在日誌中記錄明文密碼
  • 建議搭配密碼雜湊機制使用
  • 定期更新密碼規則以符合最新安全標準

4. 效能考量

  • 密碼驗證過程可能需要多次規則檢查
  • 密碼產生過程可能需要多次嘗試
  • 考慮在高併發場景下的效能影響

進階用法

自定義驗證規則

// 建立自定義驗證器
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));

程式碼 PassayUtils.java

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&lt;String&gt; 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;
    }
}

單元測試程式碼 PassayUtilsTest.java

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),
            "應包含重複字元規則");
    }
}


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言