Spring Boot - Custom Password Validator Using Passay Library
Join the DZone community and get the full member experience.
Join For FreeMany online web platforms require users to enter a strong password during registration. This strategy helps reduce the vulnerability of user data to any hacking.
In this article, we'll create a simple form with a registration page. Before continuing with this tutorial, you should have a basic understanding of Java with the Spring framework.
What Is Passay ?
Passay is a Java-based password generation and validation library. It builds on the success of vt-password
and provides a comprehensive and extensible feature set.
Technology Stack
- Node.js.
- Angular 9.
- Spring Boot 2.
- Maven 3.6.1.
- JAVA 8.
- Git.
Maven Dependency
Use Spring Initializr to generate the spring boot 2 project with the dependencies: web, lombok, spring-boot-starter-validation.
Then add the Passay dependency to manage validation policies.
xxxxxxxxxx
<dependency>
<groupId>org.passay</groupId>
<artifactId>passay</artifactId>
<version>1.6.0</version>
</dependency>
You can find all versions here.
Use the UserData
class containing the information to verify.
xxxxxxxxxx
List({ .
(
field = "password",
fieldMatch = "confirmPassword",
message = "Passwords do not match!"
)
})
public class UserData {
message = "username is mandatory") (
private String username;
private String email;
message = "New password is mandatory") (
private String password;
message = "Confirm Password is mandatory") (
private String confirmPassword;
}
Two important annotations:
@PasswordValueMatch
: Check if the password and confirmation password match.@ValidPassword
: Contains the password validation policy.
Password Validation
Password validation involves creating a PasswordValidator
from a rule set, which is simply a list of Rule
objects . The @ValidPassword
annotation is an annotation validated by the PasswordConstraintValidator.class
xxxxxxxxxx
validatedBy = PasswordConstraintValidator.class) (
TYPE, FIELD, ANNOTATION_TYPE }) ({
RUNTIME) (
public @interface ValidPassword {
String message() default "Invalid Password";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Consider the following simple password policy:
- Length of password should be in between 8 to 16 characters.
- A password should not contain any whitespace.
- A password must contain at least 1 upper-case character.
- A password must contain at least 1 lower-case character.
- A password must contain at least 1 digit character.
- A password must contain at least 1 symbol (special character).
- Rejects passwords that contain a sequence of >= 5 characters alphabetical (e.g. abcdef).
- Rejects passwords that contain a sequence of >= 5 characters numerical (e.g. 12345).
The PasswordConstraintValidator
class contains all previously defined password rules without having to implement them manually.
xxxxxxxxxx
public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, String> {
public void initialize(final ValidPassword arg0) {
}
public boolean isValid(String password, ConstraintValidatorContext context) {
//customizing validation messages
Properties props = new Properties();
InputStream inputStream = getClass()
.getClassLoader().getResourceAsStream("passay.properties");
props.load(inputStream);
MessageResolver resolver = new PropertiesMessageResolver(props);
PasswordValidator validator = new PasswordValidator(resolver, Arrays.asList(
// length between 8 and 16 characters
new LengthRule(8, 16),
// at least one upper-case character
new CharacterRule(EnglishCharacterData.UpperCase, 1),
// at least one lower-case character
new CharacterRule(EnglishCharacterData.LowerCase, 1),
// at least one digit character
new CharacterRule(EnglishCharacterData.Digit, 1),
// at least one symbol (special character)
new CharacterRule(EnglishCharacterData.Special, 1),
// no whitespace
new WhitespaceRule(),
// rejects passwords that contain a sequence of >= 5 characters alphabetical (e.g. abcdef)
new IllegalSequenceRule(EnglishSequenceData.Alphabetical, 5, false),
// rejects passwords that contain a sequence of >= 5 characters numerical (e.g. 12345)
new IllegalSequenceRule(EnglishSequenceData.Numerical, 5, false)
));
RuleResult result = validator.validate(new PasswordData(password));
if (result.isValid()) {
return true;
}
List<String> messages = validator.getMessages(result);
String messageTemplate = String.join(",", messages);
context.buildConstraintViolationWithTemplate(messageTemplate)
.addConstraintViolation()
.disableDefaultConstraintViolation();
return false;
}
}
passay has a list of several rules to help validate passwords. The full list of rules that can be written using Passay can be found on the official website.
In addition to password validation, passay allows you to generate a password using a given policy.
Create BaseExceptionHandler.class
to catch all exceptions that will be thrown for data validation.
xxxxxxxxxx
public class BaseExceptionHandler {
HttpStatus.BAD_REQUEST) (
MethodArgumentNotValidException.class) (
public ApiResponse handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
if (errors.containsKey(error.getField())) {
errors.put(error.getField(), String.format("%s, %s", errors.get(error.getField()), error.getDefaultMessage()));
} else {
errors.put(error.getField(), error.getDefaultMessage());
}
}
);
return new ApiResponse(errors, "VALIDATION_FAILED");
}
}
Launch the Backend project. http://localhost:8080/
For this article, I created a signup form with Angular for the front-end.
Now, Add the Angular project under the src/main
folder using the command line:
ng new webapp
After adding the content of the registration form code. run frontend npm start
Open your browser on http://localhost:4200/
and enter an invalid password to verify that validation is working.
The complete source code can be found in my GitHub repository.
Opinions expressed by DZone contributors are their own.
Comments