www.baeldung.com/registratio…

Eugen Paraschiv

From stackGC

1, an overview of the

In this article, we will implement a basic registration process using Spring Security. This example builds on the previous article.

The goal of this article is to add a complete registration process that can register users, validate, and persist user data.

2. Registration page

First, let’s implement a simple registration page with the following fields:

  • name
  • emal
  • password

The following example shows a simple registration.html page:

Example 2.1

<html>
<body>
<h1 th:text="#{label.form.title}">form</h1>
<form action="/" th:object="${user}" method="POST" enctype="utf8">
    <div>
        <label th:text="#{label.user.firstName}">first</label>
        <input th:field="*{firstName}"/>
        <p th:each="error: ${#fields.errors('firstName')}"
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.lastName}">last</label>
        <input th:field="*{lastName}"/>
        <p th:each="error : ${#fields.errors('lastName')}"
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.email}">email</label>
        <input type="email" th:field="*{email}"/>
        <p th:each="error : ${#fields.errors('email')}"
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.password}">password</label>
        <input type="password" th:field="*{password}"/>
        <p th:each="error : ${#fields.errors('password')}"
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.confirmPass}">confirm</label>
        <input type="password" th:field="*{matchingPassword}"/>
    </div>
    <button type="submit" th:text="#{label.form.submit}">submit</button>
</form>
 
<a th:href="@{/login.html}" th:text="#{label.form.loginLink}">login</a>
</body>
</html>
Copy the code

3. User DTO object

We need a Data Transfer Object (DTO) to encapsulate all registration information and send it to the Spring back end. When creating and populating the User object, the DTO object should have all the information it needs later:

public class UserDto {
    @NotNull
    @NotEmpty
    private String firstName;
     
    @NotNull
    @NotEmpty
    private String lastName;
     
    @NotNull
    @NotEmpty
    private String password;
    private String matchingPassword;
     
    @NotNull
    @NotEmpty
    private String email;
     
    // standard getters and setters
}
Copy the code

Notice that we use the standard Javax.Validation annotations on the fields of the DTO object. Later, we will also implement custom validation annotations to validate E-mail address formats and confirm passwords. (See section 5)

4. Register the controller

The registration link on the login page redirects to the Registration page. The back end of this page is in the registration controller, which maps to /user/registration:

Example 4.1 – showRegistration method

@RequestMapping(value = "/user/registration", method = RequestMethod.GET)
public String showRegistrationForm(WebRequest request, Model model) {
    UserDto userDto = new UserDto();
    model.addAttribute("user", userDto);
    return "registration";
}
Copy the code

When the controller receives the /user/registration request, it creates a new UserDto object, binds it and returns the registration form, simple.

5. Verify registration data

Let’s look at the validation the controller performs when registering a new account:

  1. All required fields have been filled in (no blank fields or null fields)
  2. Valid email address (correct format)
  3. The password confirmation field matches the password field
  4. Account does not exist

5.1. Built-in validation

For simple checks, we use out-of-the-box bean validation annotations on DTO objects – @notnull, @Notempty, and so on.

To trigger the validation process, we simply annotate objects in the controller layer with the @valid annotation:

public ModelAndView registerUserAccount(
  @ModelAttribute("user") @Valid UserDto accountDto, 
  BindingResult result, WebRequest request, Errors errors) {... }Copy the code

5.2. Use custom validation to check email validity

Let’s verify the E-mail address and make sure it’s formatted correctly. We will create a custom validator and a custom validation annotation named @validemail.

Note that we are using custom annotations, not Hibernate’s @email, because Hibernate treats Intranet addresses (such as myaddress@myserver) as valid Email address formats (see Stackoverflow article), That’s not good.

Here are the email validation annotations and custom validators:

Example 5.2.1 – Custom annotations for E-mail validation

@Target({TYPE, FIELD, ANNOTATION_TYPE}) 
@Retention(RUNTIME)
@Constraint(validatedBy = EmailValidator.class)
@Documented
public @interface ValidEmail {   
    String message(a) default "Invalid email"; Class<? >[] groups()default {}; 
    Class<? extends Payload>[] payload() default {};
}
Copy the code

Notice that we define field-level annotations.

Example 5.2.2 — Custom EmailValidator:

public class EmailValidator implements ConstraintValidator<ValidEmail, String> { private Pattern pattern; private Matcher matcher; private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-+]+ (.[_A-Za-z0-9-]+)*@" + "[A-Za-z0-9-]+(.[A-Za-z0-9]+)* (.[A-Za-z]{2,})$"; @Override public void initialize(ValidEmail constraintAnnotation) { } @Override public boolean isValid(String email, ConstraintValidatorContext context){ return (validateEmail(email)); } private boolean validateEmail(String email) { pattern = Pattern.compile(EMAIL_PATTERN); matcher = pattern.matcher(email); return matcher.matches(); }}Copy the code

Then use the new annotation on the UserDto implementation:

@ValidEmail
@NotNull
@NotEmpty
private String email;
Copy the code

5.3. Use custom authentication for password confirmation

We also need a custom annotation and validator to ensure that the Password and matchingPassword fields match:

Example 5.3.1 – Custom annotations for validating password confirmation

@Target({TYPE,ANNOTATION_TYPE}) 
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordMatchesValidator.class)
@Documented
public @interface PasswordMatches { 
    String message(a) default "Passwords don't match"; Class<? >[] groups()default {}; 
    Class<? extends Payload>[] payload() default {};
}
Copy the code

Note that the @target annotation specifies that this is a TYPE level annotation. Because we need the entire UserDto object to perform the validation.

The following is a custom validator called for this annotation:

Example 5.3.2 – PasswordMatch Validator User-defined validator

public class PasswordMatchesValidator 
  implements ConstraintValidator<PasswordMatches.Object> { 
     
    @Override
    public void initialize(PasswordMatches constraintAnnotation) {}@Override
    public boolean isValid(Object obj, ConstraintValidatorContext context){   
        UserDto user = (UserDto) obj;
        returnuser.getPassword().equals(user.getMatchingPassword()); }}Copy the code

Apply the @Passwordmatches annotation to the UserDto object:

@PasswordMatches
public class UserDto {... }Copy the code

5.4. Check whether the account exists

The fourth check we perform is to verify that the E-mail account exists in the database.

This is done after form validation and is done with the help of the UserService implementation.

Example 5.4.1 – The createUserAccount method of the controller calls the UserService object

@RequestMapping(value = "/user/registration", method = RequestMethod.POST)
public ModelAndView registerUserAccount
      (@ModelAttribute("user") @Valid UserDto accountDto, 
      BindingResult result, WebRequest request, Errors errors) {    
    User registered = new User();
    if(! result.hasErrors()) { registered = createUserAccount(accountDto, result); }if (registered == null) {
        result.rejectValue("email"."message.regError");
    }
    // rest of the implementation
}
private User createUserAccount(UserDto accountDto, BindingResult result) {
    User registered = null;
    try {
        registered = service.registerNewUserAccount(accountDto);
    } catch (EmailExistsException e) {
        return null;
    }    
    return registered;
}
Copy the code

Example 5.4.2 – UserService checks duplicate E-mail messages

@Service
public class UserService implements IUserService {
    @Autowired
    private UserRepository repository; 
     
    @Transactional
    @Override
    public User registerNewUserAccount(UserDto accountDto) 
      throws EmailExistsException {
         
        if (emailExist(accountDto.getEmail())) {  
            throw new EmailExistsException(
              "There is an account with that email adress: "+ accountDto.getEmail()); }...// The rest of the registration operation logic
    }
    private boolean emailExist(String email) {
        User user = repository.findByEmail(email);
        if(user ! =null) {
            return true;
        }
        return false; }}Copy the code

The UserService uses the UserRepository class to check whether the user with the specified E-mail address already exists in the database.

The actual implementation of UserRepository in the persistence layer is irrelevant to the current article. You can use Spring Data to quickly generate a repository layer.

Persist data and complete form processing

Finally, implement the registration logic at the controller layer:

Example 6.1.1 – The RegisterAccount method in a controller

@RequestMapping(value = "/user/registration", method = RequestMethod.POST)
public ModelAndView registerUserAccount(
  @ModelAttribute("user") @Valid UserDto accountDto, 
  BindingResult result, 
  WebRequest request, 
  Errors errors) {
     
    User registered = new User();
    if(! result.hasErrors()) { registered = createUserAccount(accountDto, result); }if (registered == null) {
        result.rejectValue("email"."message.regError");
    }
    if (result.hasErrors()) {
        return new ModelAndView("registration"."user", accountDto);
    } 
    else {
        return new ModelAndView("successRegister"."user", accountDto); }}private User createUserAccount(UserDto accountDto, BindingResult result) {
    User registered = null;
    try {
        registered = service.registerNewUserAccount(accountDto);
    } catch (EmailExistsException e) {
        return null;
    }
    return registered;
}
Copy the code

Note the following in the code above:

  1. The controller returns a ModelAndView object that passes model data (user) to the view to bind.
  2. If an error occurs during validation, the controller redirects to the registration form.
  3. The createUserAccount method calls UserService to persist data. We will discuss the UserService implementation in the next section

7. UserService – Register operation

Let’s complete the registration operation in UserService:

Example 7.1 – The IUserService interface

public interface IUserService {
    User registerNewUserAccount(UserDto accountDto)     
      throws EmailExistsException;
}
Copy the code

Example 7.2 – The UserService class

@Service
public class UserService implements IUserService {
    @Autowired
    private UserRepository repository;
     
    @Transactional
    @Override
    public User registerNewUserAccount(UserDto accountDto) 
      throws EmailExistsException {
         
        if (emailExist(accountDto.getEmail())) {   
            throw new EmailExistsException(
              "There is an account with that email address:  + accountDto.getEmail());
        }
        User user = new User();    
        user.setFirstName(accountDto.getFirstName());
        user.setLastName(accountDto.getLastName());
        user.setPassword(accountDto.getPassword());
        user.setEmail(accountDto.getEmail());
        user.setRoles(Arrays.asList("ROLE_USER"));
        return repository.save(user);       
    }
    private boolean emailExist(String email) {
        User user = repository.findByEmail(email);
        if (user != null) {
            return true;
        }
        return false;
    }
}
Copy the code

Load User Detail for secure login

In the previous article, hard-coded credentials were used for login. Now let’s make a change and use the newly registered user information and credentials. We will implement a custom UserDetailsService to check the login credentials for the persistence layer.

8.1. Customize UserDetailsService

Start with the custom User Detail service implementation:

@Service
@Transactional
public class MyUserDetailsService implements UserDetailsService {
  
    @Autowired
    private UserRepository userRepository;
    // 
    public UserDetails loadUserByUsername(String email)
      throws UsernameNotFoundException {
  
        User user = userRepository.findByEmail(email);
        if (user == null) {
            throw new UsernameNotFoundException(
              "No user found with username: "+ email);
        }
        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;
        return  new org.springframework.security.core.userdetails.User
          (user.getEmail(), 
          user.getPassword().toLowerCase(), enabled, accountNonExpired, 
          credentialsNonExpired, accountNonLocked, 
          getAuthorities(user.getRoles()));
    }
     
    private static List<GrantedAuthority> getAuthorities (List<String> roles) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        returnauthorities; }}Copy the code

8.2. Enable a new validator provider

To enable the new user service in the Spring Security configuration, we simply add a reference to the UserDetailsService within the authentication-Manager element and add the UserDetailsService bean:

Example 8.2 — Validation Manager and UserDetailsService

<authentication-manager>
    <authentication-provider user-service-ref="userDetailsService" /> 
</authentication-manager>
  
<beans:bean id="userDetailsService"
  class="org.baeldung.security.MyUserDetailsService"/>
Copy the code

Or, via Java configuration:

@Autowired
private MyUserDetailsService userDetailsService;
 
@Override
protected void configure(AuthenticationManagerBuilder auth) 
  throws Exception {
    auth.userDetailsService(userDetailsService);
}
Copy the code

9, conclusion

There you have it — a registration process implemented by Spring Security and Spring MVC that is almost ready for a quasi-production environment. In a subsequent article, we will explore the activation process for a newly registered account by validating the new user’s E-mail.

The source code for implementing the Spring Security REST tutorial is available on the GitHub project, which is an Eclipse project.

Sample code

  • Github.com/eugenp/spri…