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