This tutorial will implement Spring Security using a simple user registration form. This tutorial, which is the first of many under Spring Security, provides a beginner’s guide to learning Spring Boot. As always, our GitHub page has all code for our tutorials.

Setting up our project

The maven repository was used to manage dependencies in this project. Our pom.xml file will house every dependency including the Java version used. In this example, Java 11 was used alongside Spring Boot version 2.6.2 and other dependencies such as Thymeleaf for our front-end display. Below is our pom.xml file.

 <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>

        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
    </dependencies>

User Model

Hibernate was used to manage our database processes using the JPA feature supported by Spring Boot. Furthermore, user information needed were the Surname, Email, Password, Firstname, Other names, and Lastname. Then, handling the persistence of data, hibernate was applied with its configuration as shown below and saved in application.properties

spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/simpleUserDB
spring.datasource.username=root
spring.datasource.password=
spring.jpa.properties.hibernate.format_sql=true

User model code structure

import javax.persistence.*;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 30)
    private String email;

    @Column(nullable = false, length = 80)
    private String password;

    @Column(name = "first_name", nullable = false, length = 40)
    private String firstName;

    @Column(name = "other_names", nullable = true, length = 40)
    private String otherNames;

    @Column(name = "last_name", nullable = false, length = 60)
    private String lastName;

    public User() {
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getOtherNames() {
        return otherNames;
    }

    public void setOtherNames(String otherNames) {
        this.otherNames = otherNames;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

Registration Page

This registration page collects user information such as surname, first name, last name, other names, email, and password.

<div class="container">
  <div>
    <h1>User Registration form</h1>
  </div>
  <form th:action="@{/register_action}" th:object="${user}"
        method="post">
    <div class="m-3">
      <div class="form-group row">
        <label class="col-4 col-form-label">User Email: </label>
        <div class="col-8">
          <input type="email" th:field="*{email}" class="form-control" required />
        </div>
      </div>

      <div class="form-group row">
        <label class="col-4 col-form-label">Password: </label>
        <div class="col-8">
          <input type="password" th:field="*{password}" class="form-control"
                 required minlength="6" maxlength="60"/>
        </div>
      </div>

      <div class="form-group row">
        <label class="col-4 col-form-label">First Name: </label>
        <div class="col-8">
          <input type="text" th:field="*{firstName}" class="form-control"
                 required minlength="4" maxlength="40"/>
        </div>
      </div>
      <div class="form-group row">
        <label class="col-4 col-form-label">Other Name: </label>
        <div class="col-8">
          <input type="text" th:field="*{otherNames}" class="form-control"
                 required minlength="4" maxlength="40"/>
        </div>
      </div>

      <div class="form-group row">
        <label class="col-4 col-form-label">Last Name: </label>
        <div class="col-8">
          <input type="text" th:field="*{lastName}" class="form-control"
                 required minlength="4" maxlength="60" />
        </div>
      </div>

      <div>
        <button type="submit" class="btn btn-primary">Register</button>
      </div>
    </div>
  </form>
</div>

Login Page

The login page we used in this program enables access using our spring security feature. Ensuring that only registered users can access the application homepage, spring security was configured to grant access to registered and authorized users.

User Repository Interface

Spring Data Jpa provides a way to manage the data access layer of an application. Over the years, developing spring applications has been cumbersome. Using this data access manager enhances application performance. It is important to add that a custom access interface can also be written but in our case, we are sticking to the interface provided by Spring Data JPA.

import com.valerio.user_registration.models.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface UserRepo extends JpaRepository<User, Long> {
    @Query("SELECT u FROM User u WHERE u.email = ?1")
    User findByEmail(String username);

}

User Controller

We have to add our controller to process all requests.

import com.valerio.user_registration.dao.UserRepo;
import com.valerio.user_registration.models.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class UserController {


    @Autowired
    private UserRepo userRepo;

    @GetMapping("")
    public String HomePage(){
        return "index";
    }

    @GetMapping("/users")
    public String UserPage(){
        return "users";
    }

    @GetMapping("/register")
    public String RegistrationForm(Model model) {
        model.addAttribute("user", new User());

        return "register";
    }

    @PostMapping("/register_action")
    public String processRegister(User user) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encodedPassword = passwordEncoder.encode(user.getPassword());
        user.setPassword(encodedPassword);

        userRepo.save(user);

        return "success";
    }
}

Implementing Spring Security

To implement spring security in our application, add CustomUserDetailsService class which implements UserDetailsService

public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepo userRepo;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepo.findByEmail(username);
        if(user == null) {
            throw new UsernameNotFoundException("This user is not available");
        }
        return new CustomUserDetails(user);

    }
}

Furthermore, to activate the Spring Security features we need to create the Web security configure adapter. Then, create a class that extends  WebSecurityConfigurerAdapter to enable all methods associated with this step.

import javax.sql.DataSource;


@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DataSource dataSource;

    @Bean
    public UserDetailsService userDetailsService() {
        return new CustomUserDetailsService();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder());

        return authProvider;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/users").authenticated()
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .usernameParameter("email")
                .defaultSuccessUrl("/users")
                .permitAll()
                .and()
                .logout().logoutSuccessUrl("/").permitAll();
    }

}

Lastly, our application provided a surface-level process for Spring Security. Our source code for this tutorial is available on GitHub.

Leave a Reply