博文目录
JDK Version: Oracle JDK 1.8.0_202
package com.mrathena.toolkit;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class PasswordKit {
public static void main(String[] args) {
long begin = System.currentTimeMillis();
String password = "eW2qw2ss3S11aa22t151j331";
System.out.println(strength(password));
System.out.println(check(password, category(false, false, true, true)));
System.out.println(System.currentTimeMillis() - begin);
}
private PasswordKit() {
}
/**
* 预定义的密码校验强度
*/
public static final Feature[] WEAK = {length(1, 8)};
public static final Feature[] MEDIUM = {length(8, 16)};
public static final Feature[] STRONG = {length(8, 32), category(3)};
public static final Feature[] COMPLEX = {length(8, 32), category(4), single(3), keyboard(2), logic(2), same(2)};
public static final Feature[] UNPREDICTABLE = {length(16, 32), category(4), single(2), keyboard(2, true), logic(2, true), same(2, true)};
/**
* 预定义的密码校验强度
*/
private enum Strength {
NONE, ILLEGAL, WEAK, MEDIUM, STRONG, COMPLEX, UNPREDICTABLE;
}
private static final Pattern CONTAINS_DIGIT_PATTERN = Pattern.compile(".*[0-9].*");
private static final Pattern CONTAINS_LOWER_PATTERN = Pattern.compile(".*[a-z].*");
private static final Pattern CONTAINS_UPPER_PATTERN = Pattern.compile(".*[A-Z].*");
private static final Pattern CONTAINS_SPECIAL_PATTERN = Pattern.compile(".*[`~!@#$%^&*()\\-_=+\\[\\]{}\\\\|;:'\",<>./?].*");
private static final String SPECIAL_CHARACTER = "`~!@#$%^&*()-_=+[]{}\\|;:'\",<>./?";
private static final Set<Character> SPECIAL_CHARACTER_SET = new HashSet<>();
static {
for (char c : SPECIAL_CHARACTER.toCharArray()) {
SPECIAL_CHARACTER_SET.add(c);
}
}
private static final List<String> KEYBOARD_CONTINUOUS_HORIZONTAL_LIST = Arrays.asList(
"!@#$%^&*()_+", "01234567890", "qwertyuiop", "asdfghjkl", "zxcvbnm", "qwertyuiop".toUpperCase(), "asdfghjkl".toUpperCase(), "zxcvbnm".toUpperCase());
private static final List<String> KEYBOARD_CONTINUOUS_OBLIQUE_LIST = Arrays.asList(
"1qaz", "2wsx", "3edc", "4rfv", "5tgb", "6yhn", "7ujm", "8ik,", "9ol.", "0p;/", "=[;.", "-pl,", "0okm", "9ijn", "8uhb", "7ygv", "6tfc", "5rdx", "4esz",
"!QAZ", "@WSX", "#EDC", "$RFV", "%TGB", "^YHN", "&UJM", "*IK<", "(OL>", ")P:?", "+{:>", "_PL<", ")OKM", "(IJN", "*UHB", "&YGV", "^TFC", "%RDX", "$ESZ");
private static final List<String> KEYBOARD_CONTINUOUS_LIST = Stream.of(KEYBOARD_CONTINUOUS_HORIZONTAL_LIST, KEYBOARD_CONTINUOUS_OBLIQUE_LIST).flatMap(Collection::stream).collect(Collectors.toList());
private static final List<String> LOGIC_CONTINUOUS_LIST = Arrays.asList(
"!@#$%^&*()_+", "01234567890", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz".toUpperCase());
// ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
/**
* 校验密码(暂未考虑空白等特殊字符)
*/
public static Result check(String password, Feature... features) {
// 未传入密码
if (null == password || "".equals(password)) {
return Result.no("密码不应为空");
}
// 非法密码
for (char c : password.toCharArray()) {
if (!Character.isDigit(c) && !Character.isLowerCase(c) && !Character.isUpperCase(c) && !SPECIAL_CHARACTER_SET.contains(c)) {
return Result.no(String.format("密码应只包含[0-9][a-z][A-Z]和[%s]", SPECIAL_CHARACTER));
}
}
// 特征检测
for (Feature feature : features) {
Result result = feature.check(password);
if (result.isNotPassed()) {
return result;
}
}
// 检测通过
return Result.ok();
}
/**
* 校验密码, 默认要求强度为强壮
*/
public static Result check(String password) {
return check(password, STRONG);
}
/**
* 密码强度(在所有验证都开启的情况下,每满足一个小条件加1级)
*/
public static String strength(String password) {
// 未传入密码
if (null == password || "".equals(password)) {
return Strength.NONE.name();
}
// 非法密码
for (char c : password.toCharArray()) {
if (!Character.isDigit(c) && !Character.isLowerCase(c) && !Character.isUpperCase(c) && !SPECIAL_CHARACTER_SET.contains(c)) {
return Strength.ILLEGAL.name();
}
}
// 强度校验
if (check(password, UNPREDICTABLE).passed) {
return Strength.UNPREDICTABLE.name();
} else if (check(password, COMPLEX).passed) {
return Strength.COMPLEX.name();
} else if (check(password, STRONG).passed) {
return Strength.STRONG.name();
} else if (check(password, MEDIUM).passed) {
return Strength.MEDIUM.name();
} else if (check(password, WEAK).passed) {
return Strength.WEAK.name();
}
return Strength.NONE.name();
}
// ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
public static FeatureLength length(int min, int max) {
FeatureLength feature = new FeatureLength();
feature.min = min;
feature.max = max;
return feature;
}
public static FeatureCategory category(int min) {
FeatureCategory feature = new FeatureCategory();
feature.min = min;
return feature;
}
public static FeatureCategory category(boolean digit, boolean lower, boolean upper, boolean special) {
FeatureCategory feature = new FeatureCategory();
feature.digit = digit;
feature.lower = lower;
feature.upper = upper;
feature.special = special;
return feature;
}
public static FeatureCategoryContinuous single(int max) {
FeatureCategoryContinuous feature = new FeatureCategoryContinuous();
feature.max = max;
return feature;
}
public static FeatureKeyboardContinuous keyboard(int max, boolean ignoreCase) {
FeatureKeyboardContinuous feature = new FeatureKeyboardContinuous();
feature.max = max;
feature.ignoreCase = ignoreCase;
return feature;
}
public static FeatureKeyboardContinuous keyboard(int max) {
return keyboard(max, false);
}
public static FeatureLogicContinuous logic(int max, boolean ignoreCase) {
FeatureLogicContinuous feature = new FeatureLogicContinuous();
feature.max = max;
feature.ignoreCase = ignoreCase;
return feature;
}
public static FeatureLogicContinuous logic(int max) {
return logic(max, false);
}
public static FeatureSameContinuous same(int max, boolean ignoreCase) {
FeatureSameContinuous feature = new FeatureSameContinuous();
feature.max = max;
feature.ignoreCase = ignoreCase;
return feature;
}
public static FeatureSameContinuous same(int max) {
return same(max, false);
}
public static FeatureCustomerList custom(Set<String> set) {
FeatureCustomerList feature = new FeatureCustomerList();
feature.set = set;
return feature;
}
// ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
/**
* 校验结果
*/
public static class Result {
private boolean passed; // 是否通过
private String reason; // 未通过原因
public static Result ok() {
Result result = new Result();
result.passed = true;
return result;
}
public static Result no(String reason) {
Result result = new Result();
result.passed = false;
result.reason = reason;
return result;
}
public boolean isPassed() {
return this.passed;
}
public boolean isNotPassed() {
return !this.passed;
}
public String getReason() {
return this.reason;
}
@Override
public String toString() {
return String.format("Result: passed:%s" + (this.passed ? "" : ", reason:%s"), this.passed, this.reason);
}
}
private interface Feature {
Result check(String password);
}
/**
* 密码长度验证
*/
private static class FeatureLength implements Feature {
private int min; // 最小长度
private int max; // 最大长度
@Override
public Result check(String password) {
int length = password.length();
boolean pass = this.min <= length && length <= this.max;
return pass ? Result.ok() : Result.no(String.format("密码长度应在%d位到%d位之间", this.min, this.max));
}
}
/**
* 密码字符种类验证
*/
private static class FeatureCategory implements Feature {
private int min; // 至少包含种类, 有该配置则不校验下列配置(用户传了两个进来的情况除外)
private boolean digit; // 数字
private boolean lower; // 小写字母
private boolean upper; // 大写字母
private boolean special; // 特殊符号
@Override
public Result check(String password) {
// 校验是否包含指定类型
boolean containsDigit = CONTAINS_DIGIT_PATTERN.matcher(password).matches();
boolean containsLower = CONTAINS_LOWER_PATTERN.matcher(password).matches();
boolean containsUpper = CONTAINS_UPPER_PATTERN.matcher(password).matches();
boolean containsSpecial = CONTAINS_SPECIAL_PATTERN.matcher(password).matches();
// 如果是要求至少含有几种类型
if (this.min != 0) {
int min = Math.min(this.min, 4);
boolean pass = count(containsDigit, containsLower, containsUpper, containsSpecial) >= min;
return pass ? Result.ok() : Result.no(String.format("密码种类应包含[0-9][a-z][A-Z]和[%s]中的至少%d种", SPECIAL_CHARACTER, this.min));
}
// 如果是指定包含某些类型
boolean checkDigitPass = !this.digit || containsDigit;
boolean checkLowerPass = !this.lower || containsLower;
boolean checkUpperPass = !this.upper || containsUpper;
boolean checkSpecialPass = !this.special || containsSpecial;
boolean pass = checkDigitPass && checkLowerPass && checkUpperPass && checkSpecialPass;
if (pass) {
return Result.ok();
} else {
if (!checkDigitPass) {
return Result.no("密码应包含[0-9]");
} else if (!checkLowerPass) {
return Result.no("密码应包含[a-z]");
} else if (!checkUpperPass) {
return Result.no("密码应包含[A-Z]");
} else if (!checkSpecialPass) {
return Result.no(String.format("密码应包含[%s]", SPECIAL_CHARACTER));
}
return Result.no("type check");
}
}
}
/**
* 密码类别连续个数验证
*/
private static class FeatureCategoryContinuous implements Feature {
private int max; // 最大允许连续个数
@Override
public Result check(String password) {
for (int i = 0; i + this.max + 1 <= password.length(); i++) {
String original = password.substring(i, i + this.max + 1);
char first = original.charAt(0);
if (Character.isDigit(first)) {
int count = 0;
for (int j = 1; j < original.length(); j++) {
if (Character.isDigit(original.charAt(j))) {
count++;
}
}
if (count == this.max) {
return Result.no(String.format("密码不应包含连续同类字符[%s]", original));
}
}
if (Character.isLowerCase(first)) {
int count = 0;
for (int j = 1; j < original.length(); j++) {
if (Character.isLowerCase(original.charAt(j))) {
count++;
}
}
if (count == this.max) {
return Result.no(String.format("密码不应包含连续同类字符[%s]", original));
}
}
if (Character.isUpperCase(first)) {
int count = 0;
for (int j = 1; j < original.length(); j++) {
if (Character.isUpperCase(original.charAt(j))) {
count++;
}
}
if (count == this.max) {
return Result.no(String.format("密码不应包含连续同类字符[%s]", original));
}
}
if (SPECIAL_CHARACTER_SET.contains(first)) {
int count = 0;
for (int j = 1; j < original.length(); j++) {
if (SPECIAL_CHARACTER_SET.contains(original.charAt(j))) {
count++;
}
}
if (count == this.max) {
return Result.no(String.format("密码不应包含连续同类字符[%s]", original));
}
}
}
return Result.ok();
}
}
/**
* 键盘按键连续个数验证
* qwertyu,1qaz
*/
private static class FeatureKeyboardContinuous implements Feature {
private int max; // 最大允许连续个数
private boolean ignoreCase; // 忽略大小写
@Override
public Result check(String password) {
for (int i = 0; i + this.max + 1 <= password.length(); i++) {
String original = password.substring(i, i + this.max + 1);
String originalLower = original.toLowerCase();
String reverse = new StringBuilder(original).reverse().toString();
String reverseLower = reverse.toLowerCase();
for (String sequence : KEYBOARD_CONTINUOUS_LIST) {
if (sequence.contains(this.ignoreCase ? originalLower : original)) {
return Result.no(String.format("密码不应包含连续字符[%s]", original));
} else if (sequence.contains(this.ignoreCase ? reverseLower : reverse)) {
return Result.no(String.format("密码不应包含连续字符[%s]", original));
}
}
}
return Result.ok();
}
}
/**
* 逻辑字符连续个数验证
* 123456,abcdefg
*/
private static class FeatureLogicContinuous implements Feature {
private int max; // 最大允许连续个数
private boolean ignoreCase; // 忽略大小写
@Override
public Result check(String password) {
for (int i = 0; i + this.max + 1 <= password.length(); i++) {
String original = password.substring(i, i + this.max + 1);
String originalLower = original.toLowerCase();
String reverse = new StringBuilder(original).reverse().toString();
String reverseLower = reverse.toLowerCase();
for (String sequence : LOGIC_CONTINUOUS_LIST) {
if (sequence.contains(this.ignoreCase ? originalLower : original)) {
return Result.no(String.format("密码不应包含连续字符[%s]", original));
} else if (sequence.contains(this.ignoreCase ? reverseLower : reverse)) {
return Result.no(String.format("密码不应包含连续字符[%s]", original));
}
}
}
return Result.ok();
}
}
/**
* 相邻相同连续个数验证
* 11111,sssss
*/
private static class FeatureSameContinuous implements Feature {
private int max; // 最大允许连续个数
private boolean ignoreCase; // 忽略大小写
@Override
public Result check(String password) {
for (int i = 0; i + this.max + 1 <= password.length(); i++) {
String original = password.substring(i, i + this.max + 1);
String originalLower = original.toLowerCase();
int count = 0;
for (int j = 1; j < original.length(); j++) {
String temp = this.ignoreCase ? originalLower : original;
if (temp.charAt(0) == temp.charAt(j)) {
count++;
}
}
if (count == this.max) {
return Result.no(String.format("密码不应包含连续字符[%s]", original));
}
}
return Result.ok();
}
}
/**
* 自定义密码表验证
*/
private static class FeatureCustomerList implements Feature {
private Set<String> set; // 自定义的密码表
@Override
public Result check(String password) {
if (set.contains(password)) {
return Result.no(String.format("密码不应是[%s]", password));
}
return Result.ok();
}
}
// ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
private static int count(boolean... elements) {
int count = 0;
for (boolean element : elements) {
if (element) {
count++;
}
}
return count;
}
}