1. 序章

現在、ほとんどのWebアプリケーションにはパスワードポリシーがあります。これは、簡単に言えば、ユーザーにパスワードを破りにくいパスワードを作成させるために作成されたものです。

このようなパスワードを生成または検証するには、Passayライブラリを利用できます。

2. Mavenの依存関係

プロジェクトでPassayライブラリを使用する場合は、pom.xmlに次の依存関係を追加する必要があります。

<dependency>
    <groupId>org.passay</groupId>
    <artifactId>passay</artifactId>
    <version>1.3.1</version>
</dependency>

ここで見つけることができます。

3. パスワードの検証

パスワード検証は、Passayライブラリが提供する2つの主要な機能の1つです。 それは楽で直感的です。 それを発見しましょう。

3.1. PasswordData

パスワードを検証するには、次を使用する必要があります PasswordData。 これは、検証に必要な情報のコンテナです。 次のようなデータを保存できます。

  • パスワード
  • ユーザー名
  • パスワード参照のリスト

パスワードとユーザー名のプロパティはそれ自体を説明しています。 Passayライブラリは、パスワード参照のリストに追加できるHistoricalReferenceSourceReferenceを提供します。

originフィールドを使用して、パスワードがユーザーによって生成されたか定義されたかに関する情報を保持できます。

3.2. PasswordValidator

パスワードの検証を開始するには、PasswordDataオブジェクトとPasswordValidatorオブジェクトが必要であることを知っておく必要があります。 すでに議論しました PasswordDataPasswordValidatorを作成しましょう。

まず、パスワード検証の一連のルールを定義する必要があります。 PasswordValidator オブジェクトを作成するときに、それらをコンストラクターに渡す必要があります。

PasswordValidator passwordValidator = new PasswordValidator(new LengthRule(5));

パスワードをPasswordDataオブジェクトに渡す方法は2つあります。 これをコンストラクターまたはセッターメソッドのいずれかに渡します。

PasswordData passwordData = new PasswordData("1234");

PasswordData passwordData2 = new PasswordData();
passwordData.setPassword("1234");

PasswordValidatorvalidate()メソッドを呼び出すことにより、パスワードを検証できます。

RuleResult validate = passwordValidator.validate(passwordData);

その結果、RuleResultオブジェクトを取得します。

3.3. RuleResult

RuleResult は、検証プロセスに関する興味深い情報を保持しています。 これは、 validate()メソッドの結果として提供されます。

まず、パスワードが有効かどうかを確認できます。

Assert.assertEquals(false, validate.isValid());

さらに、パスワードが無効な場合に返されるエラーを知ることができます。エラーコードと検証の説明は RuleResultDetail に保持されます:

RuleResultDetail ruleResultDetail = validate.getDetails().get(0);
Assert.assertEquals("TOO_SHORT", ruleResultDetail.getErrorCode());
Assert.assertEquals(5, ruleResultDetail.getParameters().get("minimumLength"));
Assert.assertEquals(5, ruleResultDetail.getParameters().get("maximumLength"));

最後に、RuleResultMetadataを使用してパスワード検証のメタデータを調べることができます。

Integer lengthCount = validate
  .getMetadata()
  .getCounts()
  .get(RuleResultMetadata.CountCategory.Length);
Assert.assertEquals(Integer.valueOf(4), lengthCount);

4. パスワードの生成

検証に加えて、 Passay ライブラリを使用すると、パスワードを生成できます。 ジェネレーターが使用する必要のあるルールを提供できます。

パスワードを生成するには、PasswordGeneratorオブジェクトが必要です。 取得したら、 generatePassword()メソッドを呼び出し、CharacterRulesのリストを渡します。 サンプルコードは次のとおりです。

CharacterRule digits = new CharacterRule(EnglishCharacterData.Digit);

PasswordGenerator passwordGenerator = new PasswordGenerator();
String password = passwordGenerator.generatePassword(10, digits);

Assert.assertTrue(password.length() == 10);
Assert.assertTrue(containsOnlyCharactersFromSet(password, "0123456789"));

CharacterRule を作成するには、CharacterDataのオブジェクトが必要であることを知っておく必要があります。 もう1つの興味深い事実は、ライブラリがEnglishCharacterDataを提供していることです。これは、5セットの文字の列挙型です。

  • 数字
  • 小文字の英語のアルファベット
  • 大文字の英語のアルファベット
  • 小文字と大文字のセットの組み合わせ
  • 特殊文字

ただし、文字のセットを定義することを妨げるものは何もありません。 CharacterDataインターフェイスを実装するのと同じくらい簡単です。 それをどのように行うことができるか見てみましょう:

CharacterRule specialCharacterRule = new CharacterRule(new CharacterData() {
    @Override
    public String getErrorCode() {
        return "SAMPLE_ERROR_CODE";
    }

    @Override
    public String getCharacters() {
        return "ABCxyz123!@#";
    }
});

PasswordGenerator passwordGenerator = new PasswordGenerator();
String password = passwordGenerator.generatePassword(10, specialCharacterRule);

Assert.assertTrue(containsOnlyCharactersFromSet(password, "ABCxyz123!@#"));

5. ポジティブマッチングルール

パスワードを生成して検証する方法については、すでに学習しました。 そのためには、一連のルールを定義する必要があります。 そのため、Passayで使用できるルールにはポジティブマッチングルールとネガティブマッチングルールの2種類があることを知っておく必要があります。

まず、ポジティブルールとは何か、そしてそれらをどのように使用できるかを見てみましょう。

正の一致ルールは、提供された文字、正規表現を含むパスワード、またはいくつかの制限に適合するパスワードを受け入れます。

6つの正の一致ルールがあります。

  • AllowedCharacterRule –パスワードに含める必要のあるすべての文字を定義します
  • AllowedRegexRule –パスワードが一致する必要がある正規表現を定義します
  • CharacterRule –パスワードに含める必要のある文字セットと最小数の文字を定義します
  • LengthRule –パスワードの最小の長さを定義します
  • CharacterCharacteristicsRule –パスワードが定義されたルールのNを満たしているかどうかを確認します。
  • LengthComplexityRule –パスワードの長さごとに異なるルールを定義できます

5.1. 単純なポジティブマッチングルール

ここで、単純な構成を持つすべてのルールについて説明します。 合法的な文字やパターンのセット、または受け入れ可能なパスワードの長さを定義します。

説明したルールの簡単な例を次に示します。

PasswordValidator passwordValidator = new PasswordValidator(
  new AllowedCharacterRule(new char[] { 'a', 'b', 'c' }), 
  new CharacterRule(EnglishCharacterData.LowerCase, 5), 
  new LengthRule(8, 10)
);

RuleResult validate = passwordValidator.validate(new PasswordData("12abc"));

assertFalse(validate.isValid());
assertEquals(
  "ALLOWED_CHAR:{illegalCharacter=1, matchBehavior=contains}", 
  getDetail(validate, 0));
assertEquals(
  "ALLOWED_CHAR:{illegalCharacter=2, matchBehavior=contains}", 
  getDetail(validate, 1));
assertEquals(
  "TOO_SHORT:{minimumLength=8, maximumLength=10}", 
  getDetail(validate, 4));

パスワードが有効でない場合、各ルールで明確な説明が得られることがわかります。パスワードが短すぎて、2つの不正な文字が含まれているという通知があります。 また、パスワードが提供された正規表現と一致していないことにも気付くでしょう。

さらに、小文字が不十分であることが通知されます。

5.2. CharacterCharacterisitcsRule

CharcterCharacterisitcsRule は、前に示したルールよりも複雑です。 CharcterCharacterisitcsRuleオブジェクトを作成するには、CharacterRulesのリストを提供する必要があります。 さらに、パスワードが一致する必要があるものの数を設定する必要があります。 私たちはこのようにそれを行うことができます:

CharacterCharacteristicsRule characterCharacteristicsRule = new CharacterCharacteristicsRule(
  3, 
  new CharacterRule(EnglishCharacterData.LowerCase, 5), 
  new CharacterRule(EnglishCharacterData.UpperCase, 5), 
  new CharacterRule(EnglishCharacterData.Digit),
  new CharacterRule(EnglishCharacterData.Special)
);

提示されたCharacterCharacteristicsRuleには、提供された4つのルールのうち3つを含むパスワードが必要です。

5.3. LengthComplexityRule

一方、 Passay ライブラリは、LengthComplexityRuleを提供します。 どの長さのパスワードにどのルールを適用するかを定義できます。CharacterCharacteristicsRule とは対照的に、 CharacterRuleだけでなく、あらゆる種類のルールを使用できます。

例を分析してみましょう:

LengthComplexityRule lengthComplexityRule = new LengthComplexityRule();
lengthComplexityRule.addRules("[1,5]", new CharacterRule(EnglishCharacterData.LowerCase, 5));
lengthComplexityRule.addRules("[6,10]", 
  new AllowedCharacterRule(new char[] { 'a', 'b', 'c', 'd' }));

1〜5文字のパスワードでわかるように、CharacterRuleを適用します。 ただし、6〜10文字を含むパスワードの場合、パスワードをAllowedCharacterRuleと一致させる必要があります。

6. ネガティブマッチングルール

正の一致ルールとは異なり、負の一致ルールは、提供された文字、正規表現、エントリなどを含むパスワードを拒否します。

ネガティブマッチングルールとは何かを調べてみましょう。

  • IllegalCharacterRule –パスワードに含めることのできないすべての文字を定義します
  • IllegalRegexRule –一致してはならない正規表現を定義します
  • IllegalSequenceRule –パスワードに不正な文字シーケンスが含まれていないかどうかを確認します
  • NumberRangeRule –パスワードに含めてはならない数値の範囲を定義します
  • WhitespaceRule –パスワードに空白が含まれているかどうかを確認します
  • DictionaryRule –パスワードが任意の辞書レコードと等しいかどうかをチェックします
  • DictionarySubstringRule –パスワードに辞書レコードが含まれているかどうかを確認します
  • HistoryRule –パスワードに過去のパスワード参照が含まれているかどうかを確認します
  • DigestHistoryRule –パスワードにダイジェストされた過去のパスワード参照が含まれているかどうかを確認します
  • SourceRule –パスワードにソースパスワード参照が含まれているかどうかを確認します
  • DigestSourceRule –パスワードにダイジェストソースのパスワード参照が含まれているかどうかを確認します
  • UsersnameRule –パスワードにユーザー名が含まれているかどうかを確認します
  • RepeatCharacterRegexRule –パスワードに繰り返しASCII文字が含まれているかどうかを確認します

6.1. 単純なネガティブマッチングルール

まず、 IllegalCharacterRule IllegalRegexRuleなどの単純なルールを使用する方法を確認します。 簡単な例を次に示します。

PasswordValidator passwordValidator = new PasswordValidator(
  new IllegalCharacterRule(new char[] { 'a' }), 
  new NumberRangeRule(1, 10), 
  new WhitespaceRule()
);

RuleResult validate = passwordValidator.validate(new PasswordData("abcd22 "));

assertFalse(validate.isValid());
assertEquals(
  "ILLEGAL_CHAR:{illegalCharacter=a, matchBehavior=contains}", 
  getDetail(validate, 0));
assertEquals(
  "ILLEGAL_NUMBER_RANGE:{number=2, matchBehavior=contains}", 
  getDetail(validate, 4));
assertEquals(
  "ILLEGAL_WHITESPACE:{whitespaceCharacter= , matchBehavior=contains}", 
  getDetail(validate, 5));

この例は、説明されているルールがどのように機能するかを示しています。 ポジティブマッチングルールと同様に、検証に関する完全なフィードバックを提供します。

6.2. 辞書のルール

パスワードが提供された単語と等しくないかどうかを確認したい場合はどうなりますか。

そのため、 Passay ライブラリは、そのための優れたツールを提供します。 DictionaryRuleDictionarySubstringRuleを見つけましょう。

WordListDictionary wordListDictionary = new WordListDictionary(
  new ArrayWordList(new String[] { "bar", "foobar" }));

DictionaryRule dictionaryRule = new DictionaryRule(wordListDictionary);
DictionarySubstringRule dictionarySubstringRule = new DictionarySubstringRule(wordListDictionary);

辞書のルールにより、禁止されている単語のリストを提供できることがわかります。最も一般的または最も簡単に解読できるパスワードのリストがあると便利です。 したがって、ユーザーがそれらを使用することを禁止することは合理的です。

実際には、テキストファイルまたはデータベースから単語のリストをロードします。 その場合、WordListsを使用できます。 Reader の配列を受け取り、ArrayWordListを作成する3つのオーバーロードされたメソッドがあります。

6.3. HistoryRuleおよびSourceRule

さらに、 Passay ライブラリは、HistoryRuleおよびSourceRuleを提供します。 さまざまなソースからの履歴パスワードまたはテキストコンテンツに対してパスワードを検証できます。

例を見てみましょう:

SourceRule sourceRule = new SourceRule();
HistoryRule historyRule = new HistoryRule();

PasswordData passwordData = new PasswordData("123");
passwordData.setPasswordReferences(
  new PasswordData.SourceReference("source", "password"), 
  new PasswordData.HistoricalReference("12345")
);

PasswordValidator passwordValidator = new PasswordValidator(
  historyRule, sourceRule);

HistoryRules は、パスワードが以前に使用されたことがあるかどうかを確認するのに役立ちます。 このような方法は安全ではないため、ユーザーが古いパスワードを使用することは望ましくありません。

一方、 SourceRule を使用すると、パスワードがSourceReferencesで提供されているものと異なるかどうかを確認できます。 異なるシステムまたはアプリケーションで同じパスワードを使用するリスクを回避できます。

DigestSourceRuleDigestHistoryRuleなどのルールがあることは言及する価値があります。次の段落でそれらについて説明します。

6.4. ダイジェストルール

Passay ライブラリには、DigestHistoryRuleDigestSourceRuleの2つのダイジェストルールがあります。 ダイジェストルールは、ダイジェストまたはハッシュとして保存されたパスワードを処理することを目的としています。したがって、それらを定義するには、EncodingHashBeanオブジェクトを提供する必要があります。

それがどのように行われるか見てみましょう:

List<PasswordData.Reference> historicalReferences = Arrays.asList(
  new PasswordData.HistoricalReference(
    "SHA256",
    "2e4551de804e27aacf20f9df5be3e8cd384ed64488b21ab079fb58e8c90068ab"
));

EncodingHashBean encodingHashBean = new EncodingHashBean(
  new CodecSpec("Base64"), 
  new DigestSpec("SHA256"), 
  1, 
  false
);

今回は、ラベルとコンストラクターへのエンコードされたパスワードによってHistoricalReferenceを作成します。 その後、適切なコーデックとダイジェストアルゴリズムを使用してEncodingHashBeanをインスタンス化しました。

さらに、反復回数とアルゴリズムがソルトされるかどうかを指定できます。

エンコーディングBeanを取得したら、ダイジェストパスワードを検証できます。

PasswordData passwordData = new PasswordData("example!");
passwordData.setPasswordReferences(historicalReferences);

PasswordValidator passwordValidator = new PasswordValidator(new DigestHistoryRule(encodingHashBean));

RuleResult validate = passwordValidator.validate(passwordData);

Assert.assertTrue(validate.isValid());

EncodingHashinBean の詳細については、CryptacularライブラリのWebページを参照してください。

6.5. RepeatCharacterRegexRule

もう1つの興味深い検証ルールは、RepeatCharacterRegexRuleです。 パスワードに繰り返しASCII文字が含まれているかどうかを確認するために使用できます。

サンプルコードは次のとおりです。

PasswordValidator passwordValidator = new PasswordValidator(new RepeatCharacterRegexRule(3));

RuleResult validate = passwordValidator.validate(new PasswordData("aaabbb"));

assertFalse(validate.isValid());
assertEquals("ILLEGAL_MATCH:{match=aaa, pattern=([^\\x00-\\x1F])\\1{2}}", getDetail(validate, 0));

6.6. UsernameRule

この章で説明する最後のルールは、UsersnameRuleです。 これにより、パスワードにユーザーの名前を使用することを禁止できます。 

以前に学習したように、ユーザー名をPasswordDataに保存する必要があります。

PasswordValidator passwordValidator = new PasswordValidator(new UsernameRule());

PasswordData passwordData = new PasswordData("testuser1234");
passwordData.setUsername("testuser");

RuleResult validate = passwordValidator.validate(passwordData);

assertFalse(validate.isValid());
assertEquals("ILLEGAL_USERNAME:{username=testuser, matchBehavior=contains}", getDetail(validate, 0));

7. カスタマイズされたメッセージ

Passay ライブラリを使用すると、検証ルールによって返されるメッセージをカスタマイズできます。 まず、メッセージを定義してエラーコードに割り当てる必要があります。

それらを単純なファイルに入れることができます。 それがいかに簡単か見てみましょう:

TOO_LONG=Password must not have more characters than %2$s.
TOO_SHORT=Password must not contain less characters than %2$s.

メッセージを受け取ったら、そのファイルをロードする必要があります。 最後に、それをPasswordValidatorオブジェクトに渡すことができます。

サンプルコードは次のとおりです。

URL resource = this.getClass().getClassLoader().getResource("messages.properties");
Properties props = new Properties();
props.load(new FileInputStream(resource.getPath()));

MessageResolver resolver = new PropertiesMessageResolver(props);

ご覧のとおり、 message.properties ファイルをロードし、Propertiesオブジェクトに渡しました。 次に、 Properties オブジェクトを使用して、PropertiesMessageResolverを作成できます。

メッセージリゾルバの使用方法の例を見てみましょう。

PasswordValidator validator = new PasswordValidator(
  resolver, 
  new LengthRule(8, 16), 
  new WhitespaceRule()
);

RuleResult tooShort = validator.validate(new PasswordData("XXXX"));
RuleResult tooLong = validator.validate(new PasswordData("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"));

Assert.assertEquals(
  "Password must not contain less characters than 16.", 
  validator.getMessages(tooShort).get(0));
Assert.assertEquals(
  "Password must not have more characters than 16.", 
  validator.getMessages(tooLong).get(0));

この例は、メッセージリゾルバーを備えたバリデーターを使用してすべてのエラーコードを変換できることを明確に示しています。

8. 結論

このチュートリアルでは、Passayライブラリの使用方法を学習しました。 ライブラリをパスワード検証に簡単に使用する方法のいくつかの例を分析しました。 提供されるルールは、パスワードが安全であることを保証する一般的な方法のほとんどをカバーしています。

ただし、Passayライブラリ自体はパスワードを安全にしないことを覚えておく必要があります。 まず、一般的なルールとは何かを学び、次にライブラリを使用してそれらを実装する必要があります。

すべての例は、いつものように、GitHubにあります。