javaregexspringvalidationconstructor-injection

@Qualifier field versus constructor parameters injection problems in Spring


I have two beans: CheckPassword and CheckEmail

@Service("CheckPassword")
@Primary
public class CheckPassword implements CheckStringInterface {

    private static final String regex_password = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[@#$%^&+=!])(?=\\S+$).{8,20}$";

    @Override
    public boolean isStringValid(String rawPassword) {
        Pattern pattern = Pattern.compile(regex_password);
        Matcher matcher = pattern.matcher(rawPassword);
        return matcher.matches();
    }
}
@Service("CheckEmail")
public class CheckEmail implements CheckStringInterface {

    private static final String regex_password = "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\\\.[A-Z]{2,6}$";

    @Override
    public boolean isStringValid(String rawPassword) {
        Pattern pattern = Pattern.compile(regex_password);
        Matcher matcher = pattern.matcher(rawPassword);
        return matcher.matches();
    }
}

I use @Qualifier to select the bean I need, but why is it that only password returns true while email always returns false?

@Qualifier("CheckPassword")
private final CheckStringInterface checkPasswordFormat;

@Qualifier("CheckEmail")
private final CheckStringInterface checkEmailFormat;

@Qualifier("EmailVerifyCode")
private final VerificationCodeManager verificationCodeManager;

@Value("${email_root}")
private String email_root;

private final SendMailTemplateService sendMailTemplateService;

private static final String template_verify_code_register = "templateVerifyCodeRegister";
private static String message_notification = "Use the code to verify this email: ";

@Transactional 
public Integer register(RegisterDTO registerDTO, HttpServletRequest request){
    if (userService.findByEmail(registerDTO.email()) == null) {
        if (checkPasswordFormat.isStringValid(registerDTO.password()) == false
        || checkEmailFormat.isStringValid(registerDTO.email()) == false) {
            log.info("Wrong Format {}", checkPasswordFormat.isStringValid(registerDTO.password()));
            log.info("Wrong Format {}", checkEmailFormat.isStringValid(registerDTO.email()));
           return 0; // Wrong format email or password
        }

        User user = new User();
        user.setEmail(registerDTO.email());
        user.setFullName(registerDTO.fullName());
        user.setPassword(registerDTO.password());
        user.setCreatedAt(Time.getTheTimeRightNow());
        user.setUserVerifyCode(verificationCodeManager.generateCode());
        user.setUserVerifyCodeExpirationTime(verificationCodeManager.codeExpiration());
        log.info("------> RegisterService | register: register with email {}", user.getEmail());
        createUser.create(user);
        sendMail(user, user.getUserVerifyCode());

        request.getSession().setAttribute("email", registerDTO.email());
        return 1; // Create successful
    } else {
        return 2; // Email already exists
    }
}

This is the data I use to test the API on Swagger:

{
  "email": "bonbon147852369@gmail.com",
  "fullName": "abcdABCD",
  "password": "Password@1234"
}

I tried to change to another regex but it didn't help. When I log it I see password always return true because I input the right format but email will return false with both right and wrong format.


Solution

  • The code posted in the question doesn't work for two main reasons:

    1. The regex validation for the email is wrong
    2. The injection of beans CheckPassword and CheckEmail is made in a wrong way

    REGEX PROBLEM:

    I see two main problems in your regex email validation:

    1. You only included uppercase letters in your regex for the email (which will cause all strings containing undercase letters to fail)
    2. You used \\\\ before the . character, which will make the regex expect a \ (because to write the backslash in java you need \\ so another two \\ will actually escape it) and will cause the dot to be interpreted as special character meaning "any character"

    With your current setup,

    To fix your code, you need to use only two backslashes (instead of 4) before the dot, so that you will escape the dot character and you also need to make your regex expect also undercase letters:

    @Service("CheckEmail")
    public class CheckEmail implements CheckStringInterface {
        private static final String regex_email = "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$";
        @Override
        public boolean isStringValid(String rawEmail) {
            // alternative to Pattern.CASE_INSENSITIVE is to include a-z interval in your regex
            Pattern pattern = Pattern.compile(regex_email, Pattern.CASE_INSENSITIVE);
            Matcher matcher = pattern.matcher(rawEmail);
            return matcher.matches();
        }
    }
    

    BEAN INJECTION PROBLEM:

    The OP used constructor injection, but put @Qualifier on fields declarations. This caused the annotation to be ignored and hence only the bean with @Primary annotation was picked (the CheckPassword one). To solve the problem, you can either