My own custom annotation for validating password inputs does not work fine. I tested it and the registration does work when the passwords matches but when they are not matching I want to display them in my register.html as all other annotations that does work. Why my matching password problem don`t display in register.html page?
register.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<link rel="stylesheet" href="assets/css/form-style.css">
<title>Register</title>
</head>
<body>
<div th:replace="~{header :: header}">...</div>
<h1>Register</h1>
<form th:action="@{/createUser}" th:object="${MyUser}" method="post">
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="errors"></div>
<input type="text" th:field="*{email}" placeholder="email"><br>
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="errors"></div>
<input type="password" th:field="*{password}" placeholder="password"><br>
<div th:if="${#fields.hasErrors('confirmPassword')}" th:errors="*{confirmPassword}" class="errors"></div>
<input type="password" th:field="*{confirmPassword}" placeholder="confirm password"><br>
<div th:if="${#fields.hasErrors('age')}" th:errors="*{age}" class="errors"></div>
<input type="number" th:field="*{age}" placeholder="age"><br>
<input type="submit" value="Register"> <br>
</form>
<div th:replace="~{footer :: footer}">...</div>
</body>
</html>
MyUser.java
package com.loginthyme.security.model;
import com.loginthyme.security.annotation.FieldsValueMatch;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Set;
@Document(collection = "users")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@FieldsValueMatch.List({@FieldsValueMatch(field = "password", fieldMatch = "confirmPassword", message = "The password fields must match")}
)
public class MyUser {
@Id
private ObjectId id;
@NotEmpty(message = "Email can not be empty")
@Email(message = "Invalid email format")
private String email;
@NotEmpty(message = "Password can not be empty")
private String password;
// @Transient
private String confirmPassword;
@Min(value = 18, message = "Age must be at least 18")
@NotNull(message = "Age can not be empty")
private Integer age;
private Set<Role> role;
}
FieldsValueMatchValidator.java
package com.loginthyme.security.validations;
import com.loginthyme.security.annotation.FieldsValueMatch;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.beans.BeanWrapperImpl;
public class FieldsValueMatchValidator implements ConstraintValidator<FieldsValueMatch, Object> {
private String field;
private String fieldMatch;
@Override
public void initialize(FieldsValueMatch constraintAnnotation) {
this.field = constraintAnnotation.field();
this.fieldMatch = constraintAnnotation.fieldMatch();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object fieldValue = new BeanWrapperImpl(value).getPropertyValue(field);
Object fieldMatchValue = new BeanWrapperImpl(value).getPropertyValue(fieldMatch);
if (fieldValue != null) {
return fieldValue.equals(fieldMatchValue);
} else {
return fieldMatchValue == null;
}
}
}
FieldsValueMatch.java
package com.loginthyme.security.annotation;
import com.loginthyme.security.validations.FieldsValueMatchValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Constraint(validatedBy = FieldsValueMatchValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldsValueMatch {
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String message() default "Fields value don`t match";
String field();
String fieldMatch();
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface List {
FieldsValueMatch[] value();
}
}
RegisterController.java
package com.loginthyme.security.controller;
import com.loginthyme.security.model.MyUser;
import com.loginthyme.security.model.Role;
import com.loginthyme.security.repository.UserRepository;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.Collections;
@Controller
@Slf4j
public class RegisterController {
UserRepository userRepository;
PasswordEncoder passwordEncoder;
@Autowired
public RegisterController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@PostMapping("/createUser")
public String createUser(@Valid @ModelAttribute("MyUser") MyUser myUser, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "register";
}
if(!userRepository.existsByEmail(myUser.getEmail())) {
myUser.setPassword(passwordEncoder.encode(myUser.getPassword()));
myUser.setRole(Collections.singleton(Role.ROLE_USER));
userRepository.save(myUser);
log.info("User succesfully registered");
} else {
log.info("Such a user already exists");
}
return "redirect:/register";
}
@GetMapping("/register")
public String displayRegisterPage(Model model) {
model.addAttribute("MyUser", new MyUser());
return "register";
}
}