3 out of 4 conditions in regex java [duplicate]

余生长醉 提交于 2021-01-28 02:30:57

问题


I am trying to create a java to check strength of a password with regex. The password must pass 3 out of 4 conditions:

  1. lowercase
  2. uppercase
  3. contains digits
  4. has special characters

The code looks like below:

import java.util.*;

public class StringPasswordStrength {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("Enter password: ");
        String password = input.nextLine();
        boolean test = checkStrength(password);
        if (test) {
            System.out.print("OK");
        }
        else {
            System.out.print("Not strong enough!");
        }       
    }
    public static boolean checkStrength(String password) {
        if (password.matches("^(?=.*[a-zA-Z][0-9][!@#$%^&*])(?=.{8,})")){
            return true;
        }
        else {
            return false;
        }
    }
}

However when the password is Passw0rd it doesn't accept. How can I change the conditions in regex that the program would accept Passw0rd because it passes 3 out of 4 conditions: uppercase, lowercase and digit?


回答1:


I would suggest avoiding a potentially cryptic regular expression for this, and instead to provide something easier to read, understand and maintain (albeit more verbose).

Something like this (depending on what your conditions are, of course). For example, a length test should be mandatory:

public boolean isValid(String password) {
    // password must contain 3 out of 4 of lowercase, uppercase, digits,
    // and others (punctuation, symbols, spaces, etc.).
    if (password == null) {
        return false;
    }

    if (password.length() < 8) {
        return false;
    }

    char[] chars = password.toCharArray();
    int lowers = 0;
    int uppers = 0;
    int digits = 0;
    int others = 0;
    for (Character c : chars) {
        if (Character.isLowerCase(c)) {
            lowers = 1;
        } else if (Character.isUpperCase(c)) {
            uppers = 1;
        } else if (Character.isDigit(c)) {
            digits = 1;
        } else {
            others = 1;
        }
    }
    // at least 3 out of 4 tests must pass:
    return (lowers + uppers + digits + others >= 3);
}

I understand this is not a direct answer to your question, and so may not meet your needs.

I am also deliberately avoiding the discussion about char[] vs. String for password handling in Java - for example, see this post.

EDIT: Removed wording relating to password length, and changed related code to reflect the question.




回答2:


You could define a set of rules (regex), count how many a given password comply with and compare with the minimum you require. A possible implementation could be:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 * Patterns to be tested in your passwords. If you want some of them
 * mandatory, you can define them in a "mandatoryPatterns" list and
 * check those ones always.
 */
static List<String> patterns = Arrays.asList(
        ".*[A-Z]+.*",
        ".*[a-z]+.*"
);

/** Number of required patterns. */
static long requiredPatterns = 1;

/** This functions counts the number of patterns that a password matches. */
static long passwordStrength(String password) {
    return patterns.stream().filter(password::matches).count();
}

static boolean checkStrength(String password) {
    return passwordStrength(password) >= requiredPatterns;
}

Stream.of("", "foo", "BAR", "FooBar").forEach(pass -> {
    System.out.println(pass);
    System.out.println(passwordStrength(pass));
    System.out.println(checkStrength(pass));
});



回答3:


Your issue has been pointed out by another user, along with a solution. This is an alternative solution.


Have 4 Pattern objects, one for each requirement

Pattern uppercase = Pattern.compile("[A-Z]");
Pattern number = Pattern.compile("\\d+");
Pattern symbol = Pattern.compile("[+&$%!@]");
Pattern other = ...;

String#matches "compiles" the regex every time it is called, which can be time consuming. By using Pattern objects, you'll be using already-compiled regex patterns.

Add the requirements to a list

List<Pattern> requirements = Arrays.asList(uppercase, number, symbol, other);

Loop over the list of requirements. For each requirement, check if the password matches the requirement. If the element does, increase a counter which tracks how many requirements have already been met.

If the requirements equals 3 (or is greater than 3), return true. If the loop exits gracefully, that means 3 requirements were not met; return false if the loop exits gracefully.

public boolean isStrong(String password) {
    int requirementsMet = 0;
    for(Pattern req : requirements) {
        if(req.matcher(password).matches())
            requirementsMet++;

        if(requirementsMet >= 3)
            return true;
    }

    return false;
}



回答4:


I assume the four requirements, of which at three must be met, are as follows. The string must contain:

  1. a letter
  2. a digit
  3. a character in the string "!@#$%^&*"
  4. at least 8 characters

Is the use of a regular expression the best way to determine if a password meets three of the four requirements? That may be a valid question but it's not the one being asked or the one that I will attempt to answer. The OP may just be curious: can this problem be solved using a regular expression? Moreover, even if there are better ways to address the problem there is educational value in answers to the specific question that's been posed.

I am not familiar with Java, but I can suggest a regular expression that uses Ruby syntax. Readers unfamiliar with Ruby should be able to understand the expression, and its translation to Java should be straightforward. (If a reader can perform that translation, I would be grateful to see an edit to this answer that provides the Java equivalent at the end.)

r = /
    ((?=.*[a-z]))      # match a lowercase letter in the string in
                       # a positive lookahead in cap grp 1, 0 times
    ((?=.*\d))         # match a digit in the string in a positive
                       # lookahead in cap grp 2, 0 times
    ((?=.*[!@#$%^&*])) # match a special character in in the string
                       # in a positive lookahead in cap grp 3, 0 times
    (.{8,})            # match at least 8 characters in cap grp 4, 0 times
    \g<1>\g<2>\g<3>    # match conditions 1, 2 and 3
    |                  # or
    \g<1>\g<2>\g<4>    # match conditions 1, 2 and 4
    |                  # or
    \g<1>\g<3>\g<4>    # match conditions 1, 3 and 4
    |                  # or
    \g<2>\g<3>\g<4>    # match conditions 2, 3 and 4
    /xi                # case indiff & free-spacing regex def modes 

\g{2}, for example, is replaced by the sub-expression contained in capture group 2 ((?=.*\d)). The first four lines each contain an expression in a capture group, with the capture group repeated zero times. This is just a device to define the subexpressions in the capture groups for retrieval later.

Let's test some strings.

"Passw0rd".match? r  #=> true  (a letter, digit and >= 8)
"ab2c#45d".match? r  #=> true  (all 4 conditions satisfied)
"ab2c#5d".match?  r  #=> true  (a letter, digit and special char)
"ab2c345d".match? r  #=> true  (a letter, digit and >= 8)
"ab#c?def".match? r  #=> true  (a letter, special char and >= 8)
"21#6?512".match? r  #=> true  (a digit, special char and >= 8)

"ab26c4".match?   r  #=> false (only letter and digit)
"a$b#c".match?    r  #=> false (only letter and special char)
"abc ef h".match? r  #=> false (only letter and >= 8)
"12 45 78".match? r  #=> false (only digit and >=8)
"########".match? r  #=> false (only special char and >= 8)
"".match          r  #=> false (no condition matched) 

To use named capture groups, ((?=.*[a-z])) would be replaced with, say,

(?<letter>(?=.*[a-z]))

and \g<1>\g<2>\g<3> would be replaced by something like

\g<letter>\g<digit>\g<spec_char>



回答5:


To answer your question, the sequence in which you have provided

1st: [a-zA-Z] characters

2nd: [0-9] Numbers

3rd: [!@#$%^&*] Sp. Chars

The occurrence of literals in this sequence is must. Abcd1234@ will pass but Abcd1234@A will not pass, as A appears again after [!@#$%^&*]

And a positive lookahead must include this sequence only. If you provied any special char before, it will not be validated, similarly in your case, characters after number is not expected.

use a positive lookahead for each combination, use groups for each

Try this instead(This is my work after several lookups):

^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%&*_])(?!.*[`~^=+/?<>():;-])(?=\S+$).{8,20}$

In this case: any of the provided chars or literals can appear anywhere.

(?=.*[0-9])

(?=.*[a-z])

(?=.*[A-Z])

(?=.*[!@#$%&*_])

(?!.*[`~^=+/?<>():;-])

(?=\S+$)



回答6:


You can simplify it as follows:

public class Main {
    public static void main(String[] args) {
        String password = "Passw0rd";
        String[] patterns = { ".*[A-Z].*", ".*[a-z].*", ".*[0-9].*", ".*[!@#$%^&*].*" };
        int count = 0;
        for (String pattern : patterns) {
            if (password.matches(pattern)) {
                count++;
            }
        }
        if (count >= 3 && password.length() >= 8) {
            System.out.println("The password, '" + password + "' passed the required strength test");
        } else {
            System.out.println("The password, '" + password + "' failed the required strength test");
        }
    }
}

Output:

The password, 'Passw0rd' passed the required strength test

Some more tests:

public class Main {
    public static void main(String[] args) {
        String[] passwords = { "Passw0rd", "Abcd1234", "1234Abcd", "Abcd@1234", "abcdefgh", "ABCDEFGH", "123456789",
                "A@b1", "Ab@%cd12", "!@#$%^&*", "@helloW0rld" };
        String[] patterns = { ".*[A-Z].*", ".*[a-z].*", ".*[0-9].*", ".*[!@#$%^&*].*" };
        int count = 0;
        for (String password : passwords) {
            count = 0;
            for (String pattern : patterns) {
                if (password.matches(pattern)) {
                    count++;
                }
            }
            if (count >= 3 && password.length() >= 8) {
                System.out.println("The password, '" + password + "' passed the required strength test");
            } else {
                System.out.println("The password, '" + password + "' failed the required strength test");
            }
        }
    }
}

Output:

The password, 'Passw0rd' passed the required strength test
The password, 'Abcd1234' passed the required strength test
The password, '1234Abcd' passed the required strength test
The password, 'Abcd@1234' passed the required strength test
The password, 'abcdefgh' failed the required strength test
The password, 'ABCDEFGH' failed the required strength test
The password, '123456789' failed the required strength test
The password, 'A@b1' failed the required strength test
The password, 'Ab@%cd12' passed the required strength test
The password, '!@#$%^&*' failed the required strength test
The password, '@helloW0rld' passed the required strength test


来源:https://stackoverflow.com/questions/60443766/3-out-of-4-conditions-in-regex-java

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!