Passay库实现强大安全的密码策略

Passay库实现强大安全的密码策略

一般大多数web应用有自己的密码策略————也就是强制用户创建难以破解的复杂密码。我们可以利用Passay库生成或检查密码,创建强大的密码策略。

1. Passay库

使用Passay库需要增加相应依赖:

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

2. 密码有效性验证

密码有效性验证时Passay库的两个主要功能之一,非常简单直接,来吧。

  • PasswordData

要检查密码需要使用PasswordData类,它是容纳待检查信息的容器,包括下列数据:

String password;
String username;
List<PasswordData.Reference> passwordReferences = new ArrayList();
PasswordData.Origin origin;

前面两个是自解释的。Passay库提供HistoricalReference 和 SourceReference类,可以增加它们至密码引用列表。origin 字段表示密码是生成的或由用户定义的(枚举值:User, Generated)。

  • PasswordValidator

检查密码需要PasswordData 和 PasswordValidator对象,前面已经讨论了PasswordData,现在我们创建PasswordValidator对象。
首先定义一组密码有效性规则,创建PasswordValidator对象需要在构造函数中传入相应规则,可以传入一组规则。

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

有两种方式传入密码给PasswordData对象,通过构造函数或set方法:

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

调用passwordValidator.validate()方法进行验证:

RuleResult validate = passwordValidator.validate(passwordData);

结果是RuleResult对象。

  • RuleResult

RuleResult包括检测的结果信息,有validate方法产生。首先通过isValid()方法获得检查结果。

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

3. 生成密码

除了检测密码,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"));

我们需要CharacterData 去创建 CharacterRule。Passay库提供了EnglishCharacterData类,包括几个枚举:

digits
lowercase English alphabet
uppercase English alphabet
combination of lowercase and uppercase sets
special characters

当然,我们还可以定义自己的字符集。只需要实现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!@#"));

4. 正向匹配规则

我们已经学习了生成密码和检验密码。检验密码需要定义一组检验规则,因此需要了解Passay库提供的两种类型检验规则:正向匹配规则和反向匹配规则。我们首先看看什么是正向匹配规则以及如何使用。

正匹配规则接受包含提供字符、正则表达式或符合某些限制的密码。有六种正向匹配规则:

AllowedCharacterRule – 定义密码必须包括的所有字符
AllowedRegexRule – 定义密码必须匹配的正则表达式
CharacterRule – 定义字符集和密码中应该包含的最小字符数
LengthRule – 定义密码的最小长度
CharacterCharacteristicsRule – 检查密码是否满足定义的N个规则。
LengthComplexityRule – 允许我们为不同的密码长度定义不同的规则

4.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));

我们看到每个规则给我们清晰的解释密码是否有效。测试显示密码太短且有两个非法字符,同时也看到密码不匹配提供的正在表达式,更重要的是被告知它包含的小写字母不足。

4.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 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。

5. 反向匹配规则

与正向规则不通,反向匹配规则拒绝包含所提供的字符、正则表达式、字典等的密码。其包括下列规则:

IllegalCharacterRule – 定义密码中不能包含的所有字符
IllegalRegexRule – 定义一个不能匹配的正则表达式
IllegalSequenceRule – 检查密码是否有非法字符序列
NumberRangeRule – 定义密码不能包含的数字范围
WhitespaceRule – 检查密码是否包含空格
DictionaryRule – 检查一个密码是否等于任何字典记录
DictionarySubstringRule – 检查密码是否包含任何历史密码引用
HistoryRule – 检查密码是否包含任何摘录的历史密码引用
DigestHistoryRule – 检查密码是否包含任何摘录的历史密码引用
SourceRule – 检查密码是否包含任何源密码引用
DigestSourceRule – 检查密码是否包含任何摘要源密码引用
UsernameRule – 检查密码是否包含用户名
RepeatCharacterRegexRule – 检查密码是否包含重复的ASCII字符

5.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, 1));
assertEquals("ILLEGAL_WHITESPACE:{whitespaceCharacter= , matchBehavior=contains}", getDetail(validate, 2));
public static String getDetail(RuleResult result, int loc){
    return result.getDetails().get(loc).toString();
}

上面示例展示描述规则如何工作。类似于正向规则,它们提供了有效性的完整反馈。

5.2 Dictionary 规则

如果我们想检查密码是否等于所提供的单词,Passay 提供很好的工具实现。下面看下DictionaryRule 和DictionarySubstringRule:

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

我们看到字典规则提供了禁用的单词。如果我们有最常见或最容易被破解的密码列表,这是很有用的。因此禁止用户使用它们是合理的。在实际应用中我们肯定会从文本文件或数据库加载单词列表。在上面示例中使用单词列表。它有三个重载的方法,它们接受一个读取器数组并创建ArrayWordList。

5.3 HistoryRule 和 SourceRule

此外Passay库为我们提供了历史规则和来源。它们可以根据历史密码或来自各种来源的文本内容检验密码。请看示例:

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中提供的不同。我们可以避免在不同系统或应用程序中使用相同密码的风险。

值得一提的是,还有一些规则如DigestSourceRule和DigestHistoryRule。我们将在下节中讨论它们。

5.4 Digest 规则

Passay有两个digest规则DigestHistoryRule 和 DigestSourceRule。Digest规则用于处理存储为摘要或Hash的密码。因此要定义它们需要提供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,另外指定迭代数量、是否加盐。现在能验证摘要密码:

PasswordData passwordData = new PasswordData("example!");
passwordData.setPasswordReferences(historicalReferences);
 
PasswordValidator passwordValidator = new PasswordValidator(new DigestHistoryRule(encodingHashBean));
RuleResult validate = passwordValidator.validate(passwordData);
 
Assert.assertTrue(validate.isValid());

5.5 RepeatCharacterRegexRule规则

另一个有趣的校验规则是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));

5.6 UsernameRule规则

最后一个规则我们讨论UsernameRule。可以禁用在密码中使用用户名。首先存储username在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));

6. 自定义消息

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

上面示例说明通过装配消息解析器的验证器,可以解析错误码。

7. 总结

本文学习了Passay库。通过几个示例展示如何使用Passay检验密码有效性,并介绍了常用校验规则增强密码安全性。Passay库不能保证密码安全性,安全需要我们学习利用该库实现安全的检验规则。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值