1. 简介
如今,大多数 Web 应用都有自己的密码策略,目的很简单:强制用户创建难以被破解的密码。
为了生成或验证这类密码,我们可以借助 Passay 库来实现。
2. Maven 依赖
如果要在项目中使用 Passay,需要在 pom.xml
文件中添加如下依赖:
<dependency>
<groupId>org.passay</groupId>
<artifactId>passay</artifactId>
<version>1.3.1</version>
</dependency>
你可以在 Maven Central 找到该依赖。
3. 密码校验
密码校验是 Passay 提供的两大核心功能之一,使用起来非常直观和方便。我们一起来看看它是怎么工作的。
3.1. PasswordData
要进行密码校验,我们需要使用 PasswordData
对象。✅ 它是一个容器,用来存储校验所需的各类信息,包括:
- 密码本身
- 用户名
- 密码引用列表(如历史密码、来源密码等)
- 密码来源(是否由用户自定义或系统生成)
Passay 提供了 HistoricalReference
和 SourceReference
来支持密码引用功能。
3.2. PasswordValidator
✅ 要开始校验密码,必须有 PasswordData
和 PasswordValidator
两个对象。我们已经介绍了 PasswordData
,现在来看 PasswordValidator
的创建方式。
首先,我们需要定义一组校验规则,并将其传递给 PasswordValidator
的构造函数:
PasswordValidator passwordValidator = new PasswordValidator(new LengthRule(5));
然后,我们可以将密码传入 PasswordData
,支持两种方式:
PasswordData passwordData = new PasswordData("1234");
PasswordData passwordData2 = new PasswordData();
passwordData.setPassword("1234");
最后,调用 validate()
方法进行校验:
RuleResult validate = passwordValidator.validate(passwordData);
校验结果会返回一个 RuleResult
对象。
3.3. RuleResult
RuleResult
包含了校验过程中的详细信息。
我们可以通过它判断密码是否合法:
Assert.assertEquals(false, validate.isValid());
✅ 同时,我们还可以获取具体的错误信息,包括错误码和参数:
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()
方法并传入一组 CharacterRule
:
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
对象。Passay 提供了 EnglishCharacterData
枚举,包含以下字符集:
- 数字
- 小写字母
- 大写字母
- 大小写混合
- 特殊字符
当然,我们也可以自定义字符集,只需实现 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 中的规则分为两类:正向匹配规则 和 负向匹配规则。
正向匹配规则接受满足特定字符、正则或长度要求的密码。
Passay 提供了以下 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));
✅ 每个规则都会返回清晰的错误信息,方便定位问题。
5.2. CharacterCharacteristicsRule
CharacterCharacteristicsRule
比前面的规则更复杂。✅ 它需要我们提供一组 CharacterRule
,并指定密码需要满足其中的 N 个:
CharacterCharacteristicsRule characterCharacteristicsRule = new CharacterCharacteristicsRule(
3,
new CharacterRule(EnglishCharacterData.LowerCase, 5),
new CharacterRule(EnglishCharacterData.UpperCase, 5),
new CharacterRule(EnglishCharacterData.Digit),
new CharacterRule(EnglishCharacterData.Special)
);
5.3. LengthComplexityRule
LengthComplexityRule
允许我们根据密码长度应用不同的规则:
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' }));
✅ 这样可以灵活应对不同长度的密码需求。
6. 负向匹配规则
与正向规则相反,负向匹配规则用于拒绝包含非法字符、正则、序列等的密码。
Passay 提供了以下负向匹配规则:
IllegalCharacterRule
:禁止包含某些字符IllegalRegexRule
:禁止匹配某些正则IllegalSequenceRule
:禁止包含非法字符序列NumberRangeRule
:禁止包含指定范围内的数字WhitespaceRule
:禁止包含空格DictionaryRule
:禁止等于字典中的单词DictionarySubstringRule
:禁止包含字典中的单词HistoryRule
:禁止使用历史密码DigestHistoryRule
:禁止使用历史摘要密码SourceRule
:禁止使用来源密码DigestSourceRule
:禁止使用来源摘要密码UsernameRule
:禁止包含用户名RepeatCharacterRegexRule
:禁止包含重复字符
6.1. 简单负向匹配规则
以下是一些简单负向规则的使用示例:
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. 字典规则
如果要禁止使用常见密码,可以使用 DictionaryRule
和 DictionarySubstringRule
:
WordListDictionary wordListDictionary = new WordListDictionary(
new ArrayWordList(new String[] { "bar", "foobar" }));
DictionaryRule dictionaryRule = new DictionaryRule(wordListDictionary);
DictionarySubstringRule dictionarySubstringRule = new DictionarySubstringRule(wordListDictionary);
✅ 我们可以加载文件或数据库中的黑名单词库,防止用户使用弱密码。
6.3. 历史和来源规则
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);
✅ 可有效避免密码复用问题。
6.4. 摘要规则
Passay 还支持对摘要密码进行校验,如 DigestHistoryRule
和 DigestSourceRule
。需要配合 EncodingHashBean
使用:
List<PasswordData.Reference> historicalReferences = Arrays.asList(
new PasswordData.HistoricalReference(
"SHA256",
"2e4551de804e27aacf20f9df5be3e8cd384ed64488b21ab079fb58e8c90068ab"
));
EncodingHashBean encodingHashBean = new EncodingHashBean(
new CodecSpec("Base64"),
new DigestSpec("SHA256"),
1,
false
);
✅ 然后就可以校验摘要密码是否重复:
PasswordData passwordData = new PasswordData("example!");
passwordData.setPasswordReferences(historicalReferences);
PasswordValidator passwordValidator = new PasswordValidator(new DigestHistoryRule(encodingHashBean));
RuleResult validate = passwordValidator.validate(passwordData);
Assert.assertTrue(validate.isValid());
更多关于 EncodingHashBean
的信息可以查看 Cryptacular。
6.5. 重复字符规则
使用 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
可以防止密码中包含用户名:
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=密码不能超过 %2$s 个字符。
TOO_SHORT=密码不能少于 %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);
✅ 使用示例:
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(
"密码不能少于 16 个字符。",
validator.getMessages(tooShort).get(0));
Assert.assertEquals(
"密码不能超过 16 个字符。",
validator.getMessages(tooLong).get(0));
8. 总结
本指南介绍了 Passay 库的基本用法,涵盖了密码生成、校验以及丰富的规则体系。✅ Passay 能满足大多数密码策略需求,但记住,它只是一个工具,真正的安全策略还需要我们结合业务场景合理设计。