我刚刚编写了此密码验证代码功能,该功能检查是否在AD中接受密码。我不确定这是否是最好的方法,但目前效果很好。我很乐意提出一些有关如何使它更好的建议。

public String validateNewPass(String pass1, String pass2){
        StringBuilder retVal = new StringBuilder();

        if(pass1.length() < 1 || pass2.length() < 1 )retVal.append("Empty fields <br>");

        if (pass1 != null && pass2 != null) {

            if (pass1.equals(pass2)) {
                logger.info(pass1 + " = " + pass2);

                pass1 = pass2;
                boolean hasUppercase = !pass1.equals(pass1.toLowerCase());
                boolean hasLowercase = !pass1.equals(pass1.toUpperCase());
                boolean hasNumber = pass1.matches(".*\d.*");
                boolean noSpecialChar = pass1.matches("[a-zA-Z0-9 ]*");

                if (pass1.length() < 11) {
                    logger.info(pass1 + " is length < 11");
                    retVal.append("Password is too short. Needs to have 11 characters <br>");
                }

                if (!hasUppercase) {
                    logger.info(pass1 + " <-- needs uppercase");
                    retVal.append("Password needs an upper case <br>");
                }

                if (!hasLowercase) {
                    logger.info(pass1 + " <-- needs lowercase");
                    retVal.append("Password needs a lowercase <br>");
                }

                if (!hasNumber) {
                    logger.info(pass1 + "<-- needs a number");
                    retVal.append("Password needs a number <br>");
                }

                if(noSpecialChar){
                    logger.info(pass1 + "<-- needs a specail character");
                    retVal.append("Password needs a special character i.e. !,@,#, etc.  <br>");
                }
            }else{
                logger.info(pass1 + " != " + pass2);
                retVal.append("Passwords don't match<br>");
            }
        }else{
            logger.info("Passwords = null");
            retVal.append("Passwords Null <br>");
        }
        if(retVal.length() == 0){
            logger.info("Password validates");
            retVal.append("Success");
        }

        return retVal.toString();

    }


**注意:这些行是为了确保使用不同的情况来处理null vs empty字符串

if(pass1.length() < 1 || pass2.length() < 1 )retVal.append("Empty fields <br>");

        if (pass1 != null && pass2 != null) {


评论

这引出了一个问题:为什么您必须限制密码的字符集?这使他们更加不安全。无论如何,您都不应该以纯文本形式存储它们,并且对于任何输入长度,哈希函数都将产生相等长度的哈希。

#1 楼

错误行为

首先您要检查密码的长度,然后检查它们是否为空:


if(pass1.length() < 1 || pass2.length() < 1 )retVal.append("Empty fields <br>");

if (pass1 != null && pass2 != null) {



这不能很好地工作:如果任何密码为null,则在检查长度时会得到NullPointerException。

此外,检查字符串是否为空的更好方法是使用pass1.isEmpty()

这也是没有意义的,并且可能造成混淆:


pass1 = pass2;



简化验证逻辑

创建经过编译的正则表达式并可以重复使用多次的private final Pattern成员会更好,更高效:

private final Pattern hasUppercase = Pattern.compile("[A-Z]");
private final Pattern hasLowercase = Pattern.compile("[a-z]");
private final Pattern hasNumber = Pattern.compile("\d");
private final Pattern hasSpecialChar = Pattern.compile("[^a-zA-Z0-9 ]");


例如如果pass1包含大写字符,则返回true:

hasUppercase.matcher(pass1).find()


注意hasSpecialChar的模式:匹配非字母,非数字,非空格。

建议的实现

基于上述提示,您可以像这样简化实现:

private final Pattern hasUppercase = Pattern.compile("[A-Z]");
private final Pattern hasLowercase = Pattern.compile("[a-z]");
private final Pattern hasNumber = Pattern.compile("\d");
private final Pattern hasSpecialChar = Pattern.compile("[^a-zA-Z0-9 ]");

public String validateNewPass(String pass1, String pass2) {
    if (pass1 == null || pass2 == null) {
        logger.info("Passwords = null");
        return "One or both passwords are null";
    }

    StringBuilder retVal = new StringBuilder();

    if (pass1.isEmpty() || pass2.isEmpty()) {
        retVal.append("Empty fields <br>");
    }

    if (pass1.equals(pass2)) {
        logger.info(pass1 + " = " + pass2);

        if (pass1.length() < 11) {
            logger.info(pass1 + " is length < 11");
            retVal.append("Password is too short. Needs to have 11 characters <br>");
        }

        if (!hasUppercase.matcher(pass1).find()) {
            logger.info(pass1 + " <-- needs uppercase");
            retVal.append("Password needs an upper case <br>");
        }

        if (!hasLowercase.matcher(pass1).find()) {
            logger.info(pass1 + " <-- needs lowercase");
            retVal.append("Password needs a lowercase <br>");
        }

        if (!hasNumber.matcher(pass1).find()) {
            logger.info(pass1 + "<-- needs a number");
            retVal.append("Password needs a number <br>");
        }

        if (!hasSpecialChar.matcher(pass1).find()) {
            logger.info(pass1 + "<-- needs a specail character");
            retVal.append("Password needs a special character i.e. !,@,#, etc.  <br>");
        }
    } else {
        logger.info(pass1 + " != " + pass2);
        retVal.append("Passwords don't match<br>");
    }
    if (retVal.length() == 0) {
        logger.info("Password validates");
        retVal.append("Success");
    }

    return retVal.toString();
}


对unicode的支持

如果要允许密码中使用unicode字符,请使用特殊的java字符类定义模式,如javadoc中所述: />
private final Pattern hasUppercase = Pattern.compile("\p{javaUpperCase}");
private final Pattern hasLowercase = Pattern.compile("\p{javaLowerCase}");
private final Pattern hasNumber = Pattern.compile("\p{javaDigit}");
private final Pattern hasSpecialChar = Pattern.compile("[^\p{javaLetterOrDigit} ]");


评论


\ $ \ begingroup \ $
在某些安全情况下,您可能希望使用char []来存储密码而不是字符串,因为可以对字符串进行内联,子串化等等,并且可以对其进行永久引用。另一方面,char []不会。例如,请参见。 JPasswordField#getPassword。
\ $ \ endgroup \ $
– wchargin
2014年9月19日下午1:25

\ $ \ begingroup \ $
@WChargin子字符串不再引用相同的数组(除非您使用的是旧的JRE)。字符串不能擦除,数组可以擦除。
\ $ \ endgroup \ $
– maaartinus
2014年9月19日,下午1:34

\ $ \ begingroup \ $
@maaartinus对;忘了他们改变了。感谢您的更正。当然,原理仍然成立。
\ $ \ endgroup \ $
– wchargin
2014年9月19日,下午3:45

\ $ \ begingroup \ $
如果密码不匹配,我会立即采取行动,而不是将整个函数的主要逻辑填充到缩进的if块中。
\ $ \ endgroup \ $
– AK荷兰
2014年9月19日19:45在

#2 楼

您的代码恳求使用“责任链”模式(或更确切地说,是它的一种变体)。

但是首先要注意以下几点:



请先取消引用密码,然后再检查它们是否为null:


public String validateNewPass(String pass1, String pass2){
        if(pass1.length() < 1 || pass2.length() < 1 )retVal.append("Empty fields <br>");
        if (pass1 != null && pass2 != null) {



如果其中一个密码为null,则将收到NullPointerException在检查它们是否为空之前。

使用String.isEmpty()而不是length()调用。


替代算法

为了使密码合适,必须满足一系列要求。在Java8中,这可以通过Functions完成,但是现在让我们使用一个接口:

interface PasswordRule {
    boolean passRule(String password);
    String failMessage();
}


为了方便起见,我将引入一个抽象类,它将看起来像:

abstract static class BaseRule implements PasswordRule {
    private final String message;
    BaseRule(String message) {
        this.message = message;
    }

    public String failMessage() {
        return message;
    }
}


确定,所以现在您需要为每个密码规则创建一个实例:

private static final PasswordRule[] RULES = {
        new BaseRule("Password is too short. Needs to have 11 characters") {

            @Override
            public boolean passRule(String password) {
                return password.length() >= 11;
            }
        },

        new BaseRule("Password needs an upper case") {

            private final Pattern ucletter = Pattern.compile(".*[\p{Lu}].*");
            @Override
            public boolean passRule(String password) {
                return ucletter.matcher(password).matches();
            }
        },

        /// .... more rules.
};


现在,您的方法变为:

public static String validateNewPass(String pass1, String pass2){
    if(pass1 == null || pass2 == null) {
        //logger.info("Passwords = null");
        return "Passwords Null <br>";
    }

    if (pass1.isEmpty() || pass2.isEmpty()) {
        return "Empty fields <br>";
    }

    if (!pass1.equals(pass2)) {
        //logger.info(pass1 + " != " + pass2);
        return "Passwords don't match<br>";        
    }

    StringBuilder retVal = new StringBuilder();

    boolean pass = true;
    for (PasswordRule rule : RULES) {
        if (!rule.passRule(pass1)) {
            // logger.info(pass + "<--- " + rule.failMessage());
            retVal.append(rule.failMessage()).append(" <br>");
            pass = false;
        }
    }

    return pass ? "success" : retVal.toString();
}


添加或优化密码规则时,您要做的就是修改RULES数组。

#3 楼

有人提到我不认为的东西:

不要返回这样的字符串。逻辑应与表示形式分开。像这样

public static enum PasswordValidationResult {
    SUCCESS, IS_EMPTY, DO_NOT_MATCH, TOO_SHORT, MISSING_UPPERCASE, MISSING_LOWERCASE, MISSING_NUMBER, MISSING_SPECIAL_CHARACTER
}

public PasswordValidationResult validatePassword( String password, String repeatedPassword ) {
    // ...
}


其实,我个人会寻求一种更复杂的方法,该方法还可以分离结果的类型,从而使对逻辑的更改更加容易。但是,这可能在工程上过度了。你明白了。

#4 楼

一般来说,还不错。

logger.info(pass1 + " = " + pass2);


登录密码可能是安全问题。

boolean hasUppercase = !pass1.equals(pass1.toLowerCase());


如果想知道是否有黑天鹅,是否将所有天鹅都涂成白色,看看是否有所变化?

boolean hasNumber = pass1.matches(".*\d.*");
boolean noSpecialChar = pass1.matches("[a-zA-Z0-9 ]*");


每条线都可以,但是在一起。 3种处理同一件事的不同方法。一切都有正则表达式(好吧,不是真的):

boolean hasUppercase = pass1.matches(".*[A-Z].*");


(假设ASCII,否则也有一个unicode组)

boolean hasSpecialChar = pass1.matches(".*[^0-9A-Za-z].*");


我否定了您的条件(因此就像其他条件一样),而^否定了字符组。

 logger.info(pass1 + " is length < 11");


正确的记录器允许写入

 logger.info("{} is length < 11", pass1);


如果关闭了日志记录(大多数情况下大多数日志记录都处于关闭状态),它的可读性和速度会更快一些。

代码非常重复。您不能返回值而不是返回非常类似的消息吗?

logger.info("Passwords = null");


大多数情况下,最好的null处理是

Preconditions.checkNotNull(pass1);


如果通过null,则抛出NPE。大多数方法都不应允许null,并且尽快抛出是最好的-您可以立即解决问题,而不必稍后查找它。使用

Strings.nullToEmpty(s);


删除不能抛出的空字符串。

评论


\ $ \ begingroup \ $
前提条件和字符串看起来像番石榴,我在任何地方都没有提到。
\ $ \ endgroup \ $
– DannyMo
2014-09-18 22:31

\ $ \ begingroup \ $
@DannyMo对。但是这两个(没有格式化参数)是如此明显,以至于几乎不值得链接。
\ $ \ endgroup \ $
– maaartinus
2014年9月19日,下午1:28

#5 楼

我接受了您的代码,并在其中添加了一些return。即使整个字段都丢失,您也要遍历所有if语句。

如果我击中该语句,我还添加了一个return "success"(如果一个表达式不正确,现在可能要检查所有表达式两次)它成功转储并节省了一些处理时间。

这就是我想出的。

public String validateNewPass(String pass1, String pass2){
    StringBuilder retVal = new StringBuilder();

    if(pass1.length() < 1 || pass2.length() < 1 ){
        return "Empty fields <br>";
    }

    if (pass1 != null && pass2 != null) {

        if (pass1.equals(pass2)) {
            logger.info(pass1 + " = " + pass2);

            pass1 = pass2;
            boolean hasUppercase = !pass1.equals(pass1.toLowerCase());
            boolean hasLowercase = !pass1.equals(pass1.toUpperCase());
            boolean hasNumber = pass1.matches(".*\d.*");
            boolean noSpecialChar = pass1.matches("[a-zA-Z0-9 ]*");

            if (hasUppercase && hasLowercase && hasNumber && !noSpecialChar && pass1.length) {
                logger.info("Password validates");
                return "success";
            }

            if (pass1.length() < 11) {
                logger.info(pass1 + " is length < 11");
                retVal.append("Password is too short. Needs to have 11 characters <br>");
            }

            if (!hasUppercase) {
                logger.info(pass1 + " <-- needs uppercase");
                retVal.append("Password needs an upper case <br>");
            }

            if (!hasLowercase) {
                logger.info(pass1 + " <-- needs lowercase");
                retVal.append("Password needs a lowercase <br>");
            }

            if (!hasNumber) {
                logger.info(pass1 + "<-- needs a number");
                retVal.append("Password needs a number <br>");
            }

            if(noSpecialChar){
                logger.info(pass1 + "<-- needs a specail character");
                retVal.append("Password needs a special character i.e. !,@,#, etc.  <br>");
            }
        }else{
            logger.info(pass1 + " != " + pass2);
            retVal.append("Passwords don't match<br>");
        }
    }else{
        logger.info("Passwords = null");
        return "Passwords Null <br>";
    }

    if(retVal.length() == 0){
        logger.info("Password validates");
        retVal.append("Success");
    }

    return retVal.toString();
}



您可能会在每张支票中退回。就像没有大写字母一样,但是如果一次不通过检查可能会变得单调。

所以,相反,我只在检查后返回,这些检查会完全终止其余的检查,这是


密码字符串之一为空
密码字符串之一为空
字符串不匹配
,其余所有检查均成功

最后一个真的很不确定但这不是多余的,因为如果其中一项检查失败,则会对所有这些检查两次。但是检查实际上只执行一次,然后将值存储在布尔变量中,因此我们现在仅检查变量的值,这意味着从技术上讲,检查本身仅执行一次。

所有这一切,我都扭转了一些if语句,并尽早返回而又不丢失功能或造成单调。我还移动了null检查,以便首先完成它,否则将出现一个NullPointerException

这样做,我们最终得到这个

public String validateNewPass(String pass1, String pass2){
    StringBuilder retVal = new StringBuilder();
    if (pass1 == null || pass2 == null) {
        logger.info("Passwords = null");
        return "Passwords Null <br>";
    }

    if(pass1.length() < 1 || pass2.length() < 1 ){
        return "Empty fields <br>";
    }

    if (!pass1.equals(pass2)) {
        logger.info(pass1 + " != " + pass2);
        return "Passwords don't match<br>";
    }

    logger.info(pass1 + " = " + pass2);

    pass1 = pass2;
    boolean hasUppercase = !pass1.equals(pass1.toLowerCase());
    boolean hasLowercase = !pass1.equals(pass1.toUpperCase());
    boolean hasNumber = pass1.matches(".*\d.*");
    boolean noSpecialChar = pass1.matches("[a-zA-Z0-9 ]*");

    if (hasUppercase && hasLowercase && hasNumber && !noSpecialChar && pass1.length < 11) {
        logger.info("Password validates");
        return "success";
    }

    if (pass1.length() < 11) {
        logger.info(pass1 + " is length < 11");
        retVal.append("Password is too short. Needs to have 11 characters <br>");
    }

    if (!hasUppercase) {
        logger.info(pass1 + " <-- needs uppercase");
        retVal.append("Password needs an upper case <br>");
    }

    if (!hasLowercase) {
        logger.info(pass1 + " <-- needs lowercase");
        retVal.append("Password needs a lowercase <br>");
    }

    if (!hasNumber) {
        logger.info(pass1 + "<-- needs a number");
        retVal.append("Password needs a number <br>");
    }

    if(noSpecialChar){
        logger.info(pass1 + "<-- needs a specail character");
        retVal.append("Password needs a special character i.e. !,@,#, etc.  <br>");
    }

    if(retVal.length() == 0){
        logger.info("Password validates");
        return "Success";
    }

    return retVal.toString();
}



我意识到,最后的if语句永远都不应被评估为true,因为所有内容都已被检查过,因此这是多余的检查,我们也可以摆脱这种情况。 >

评论


\ $ \ begingroup \ $
每次检查后返回都将带来可怕的用户体验:假设您输入的密码不满足任何约束条件,而必须一次修复一个约束,然后在两次之间重新提交。最好一次列出所有内容。
\ $ \ endgroup \ $
– 11684
2014-09-19 17:10

\ $ \ begingroup \ $
@ 11684,我同意这就是为什么我只在需要纠正的主要检查之后才返回,而其他任何检查都可能发生:要么为空,要么为空,一个与另一个不匹配,然后返回成功时(即使它已经执行了所有检查,它也是可读且清晰的。而且我们实际上可以摆脱最后的if语句,因为它绝不应该评估为true,因为已经覆盖了该语句)。
\ $ \ endgroup \ $
–马拉奇♦
2014-09-19 17:29

\ $ \ begingroup \ $
否定函数调用时不需要多余的括号。 if(!foo.equals(baz))可以正常工作。
\ $ \ endgroup \ $
– IngoBürk
2014-09-20 23:41

\ $ \ begingroup \ $
谢谢@IngoBürk,我一直对编译器如何解释事物感到不安,因此我使用括号来确保我知道事物将以什么顺序发生。我在其他地方这样做了代码,不知道为什么我没有在那个特定实例中...我将对其进行编辑。
\ $ \ endgroup \ $
–马拉奇♦
2014年9月22日下午13:25

#6 楼

关于您的代码,几乎所有内容都可以说。但是,至少还有最后一句话要说,这很重要,maaartinus在他的回答中提到:不要记录密码。曾经

本质上,密码应该是秘密的,并且只有帐户持有人才能知道。我希望当您存储密码时,您将尽一切努力来保护它(哈希,盐等)。问题是,如果您将其记录下来,则无论您在存储时如何保护它,都将永远以明文形式记录它。日志通常是可访问的,并且可能不应该看到用户密码的人们可以阅读日志。这是一个安全问题,并且可能是一个严重的安全问题。

如果您需要一些输出,则可以使用logger.debug,该产品大多数时候没有在生产中激活,但是请记住,您不应该信任在安全性方面记录配置。最好的选择是从日志中删除密码。

评论


\ $ \ begingroup \ $
只是出于离线目的而进行调试
\ $ \ endgroup \ $
–pt18cher
2014年9月22日14:09

\ $ \ begingroup \ $
很高兴听到这一消息。我会将答案作为“警告”留给以后的读者!
\ $ \ endgroup \ $
–马克·安德烈(Marc-Andre)
2014-09-22 14:11

#7 楼

除了所有其他有用的答案,我建议从HTML生成中分离密码验证。您可以为错误和成功的不同类型定义一个枚举值,然后返回其中之一。然后,您可以轻松编写一些单元测试来验证此方法的正确行为。最后,一个单独的方法将调用验证例程,然后生成适当的HTML。

评论


\ $ \ begingroup \ $
这正是我在回答中写的。
\ $ \ endgroup \ $
– IngoBürk
2014-09-20 23:42