Registration and Login with Spring Boot + Spring Security + Thymeleaf

Hello everyone, I hope you all are well, today we will learn how to create user registration and login using Spring boot, Spring security, Thymeleaf, JPA, and H2DB.GitHub repository link is provided at the end of this tutorial. You can download the source code.

Technologies used:

  • Spring Boot makes developing the web applications and microservices with Spring Framework faster and easier through three core capabilities: 1.  Autoconfiguration 2. An opinionated approach to configuration 3. The ability to create standalone applications
  • Spring Security is a Java/Java EE framework that provides authentication, authorization, and other security features for enterprise applications.
  • Thymeleaf is a modern server-side Java template engine for both web and standalone environments.
  • Maven is a build automation tool used primarily for Java projects. Maven can also be used to build and manage projects written in C#, Ruby, Scala, and other languages.
  • Java 11 is a major feature release of JAVA programming language development.

User Interface

User Registration



User Login




Authentication success page




Project Structure:




Maven[pom.xml]:

A Project Object Model or POM is the fundamental unit of work in Maven. It is an XML file that contains information about the project and configuration details utilized by Maven to build the project. It contains default values for most projects. Some of the configurations that can be designated in the POM is the project dependencies, the plugins or goals that can be executed, the build profiles, and so on. Other information such as the project version, description, developers, mailing lists, and such can withal be designated.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>registration-login-spring-boot-security-thymeleaf</groupId>
<artifactId>registration-login-spring-boot-security-thymeleaf</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>registration-login-spring-boot-security-thymeleaf-rdbms</name>
<description>Demo project for Spring Boot Thymeleaf and RDBMS </description>

<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.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>



Entity class[Role.java]
package com.knf.dev.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "role")
public class Role {

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

public Role() {

}

public Role(String name) {
super();
this.name = name;
}

public Long getId() {
return id;
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
The @Entity annotation specifies that the class is an entity and is mapped to a database table. The @Table annotation specifies the name of the database table to be used for mapping. The @Id annotation specifies the primary key of an entity and the @GeneratedValue provides for the specification of generation strategies for the values of primary keys.



Entity class[User.java]
package com.knf.dev.model;

import java.util.Collection;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.persistence.JoinColumn;

@Entity
@Table(name = "user", uniqueConstraints = @UniqueConstraint
(columnNames = "email"))
public class User {

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

@Column(name = "first_name")
private String firstName;

@Column(name = "last_name")
private String lastName;

private String email;

private String password;

@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name = "users_roles", joinColumns = @JoinColumn
(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id",
referencedColumnName = "id"))

private Collection<Role> roles;

public User() {

}

public User(String firstName, String lastName, String email,
String password, Collection<Role> roles) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.password = password;
this.roles = roles;
}

public Long getId() {
return id;
}

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

public String getFirstName() {
return firstName;
}

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

public String getLastName() {
return lastName;
}

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

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 Collection<Role> getRoles() {
return roles;
}

public void setRoles(Collection<Role> roles) {
this.roles = roles;
}
}
The @Entity annotation specifies that the class is an entity and is mapped to a database table. The @Table annotation specifies the name of the database table to be used for mapping. The @Id annotation specifies the primary key of an entity and the @GeneratedValue provides for the specification of generation strategies for the values of primary keys. The @Column annotation is used to specify the mapped column for a persistent property or field. If no Column annotation is specified, the default value will be applied. We use @ManyToMany annotation to create a many-to-many relationship between two entities.
 


Repository Layer[UserRepository.java]
package com.knf.dev.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.knf.dev.model.User;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);
}
Spring @Repository annotation is used to indicate that the class provides the mechanism for storage, retrieval, search, update and delete operation on objects. JpaRepository is a JPA specific extension of Repository. It contains an API for basic CRUD operations and also API for pagination and sorting.



Service layer[UserService]
package com.knf.dev.service;

import java.util.List;
import org.springframework.security.core.userdetails.UserDetailsService;
import com.knf.dev.dto.UserRegistrationDto;
import com.knf.dev.model.User;

public interface UserService extends UserDetailsService {
User save(UserRegistrationDto registrationDto);
List<User> getAll();
}
The UserDetailsService interface is used to retrieve user-related data. It has one method named loadUserByUsername().



Implementation[UserServiceImpl.java]
package com.knf.dev.service;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import com.knf.dev.dto.UserRegistrationDto;
import com.knf.dev.model.Role;
import com.knf.dev.model.User;
import com.knf.dev.repository.UserRepository;

@Service
public class UserServiceImpl implements UserService {

private UserRepository userRepository;

@Autowired
private BCryptPasswordEncoder passwordEncoder;

public UserServiceImpl(UserRepository userRepository) {
super();
this.userRepository = userRepository;
}

@Override
public User save(UserRegistrationDto registrationDto) {
User user = new User(registrationDto.getFirstName(),
registrationDto.getLastName(), registrationDto.getEmail(),
passwordEncoder.encode(registrationDto.getPassword()),
Arrays.asList(new Role("ROLE_USER")));

return userRepository.save(user);
}

@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {

User user = userRepository.findByEmail(username);
if (user == null) {
throw new UsernameNotFoundException
("Invalid username or password.");
}
return new org.springframework.security.core.userdetails.
User(user.getEmail(), user.getPassword(),
mapRolesToAuthorities(user.getRoles()));
}

private Collection<? extends GrantedAuthority> mapRolesToAuthorities
(Collection<Role> roles) {
return roles.stream().map(role -> new SimpleGrantedAuthority
(role.getName())).collect(Collectors.toList());
}

@Override
public List<User> getAll() {
return userRepository.findAll();
}
}



Data Transfer Object[UserRegistrationDto.java]
package com.knf.dev.dto;

public class UserRegistrationDto {
private String firstName;
private String lastName;
private String email;
private String password;

public UserRegistrationDto() {

}

public UserRegistrationDto(String firstName,
String lastName, String email, String password) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.password = password;
}

public String getFirstName() {
return firstName;
}

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

public String getLastName() {
return lastName;
}

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

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;
}
}



Security Configuration[SecurityConfiguration.java]
package com.knf.dev.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.
DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.
AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.
EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.
WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.knf.dev.service.UserService;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
private UserService userService;

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

@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}

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

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/registration**", "/js/**",
"/css/**", "/img/**").permitAll().anyRequest()
.authenticated().and().formLogin().loginPage("/login").
permitAll().and().logout()
.invalidateHttpSession(true).clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).
logoutSuccessUrl("/login?logout")
.permitAll();

}
}



Controller[HomeController.java]
package com.knf.dev.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

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

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



Controller[RegistrationController.java]
package com.knf.dev.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.knf.dev.dto.UserRegistrationDto;
import com.knf.dev.service.UserService;

@Controller
@RequestMapping("/registration")
public class RegistrationController {

private UserService userService;

public RegistrationController(UserService userService) {
super();
this.userService = userService;
}

@ModelAttribute("user")
public UserRegistrationDto userRegistrationDto() {
return new UserRegistrationDto();
}

@GetMapping
public String showRegistrationForm() {
return "registration";
}

@PostMapping
public String registerUserAccount(@ModelAttribute("user")
UserRegistrationDto registrationDto) {
userService.save(registrationDto);
return "redirect:/registration?success";
}
}



Spring Boot Main Class
package com.knf.dev;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RegistrationLoginSpringBootSecurityThymeleafApplication {

public static void main(String[] args) {
SpringApplication.
run(RegistrationLoginSpringBootSecurityThymeleafApplication.class, args);
}
}



View Layer

index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.
org/thymeleaf-extras-springsecurity3">

<head>
<meta charset="ISO-8859-1">
<title>Spring Security Demo</title>

<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

</head>
<style>
.navbar {
background-color: green;
}
</style>

<body>
<!-- create navigation bar ( header) -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed"
data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar">
</span>
</button>
<a class="navbar-brand" href="#" th:href="@{/}">
Registration and
Login</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li sec:authorize="isAuthenticated()">
<a th:href="@{/logout}">Logout</a>
</li>
</ul>
</div>
</div>
</nav>

<br><br><br><br>
<div class="container">
<div class="alert alert-success">
<strong>Success!</strong> Login Successful.
</div>
</div>
<input type="hidden" th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
</body>
</html>



login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Spring Security Demo</title>

<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

</head>
<style>
.navbar {
background-color: green;
}
</style>

<body>

<!-- create navigation bar ( header) -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed"
data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span> <span
class="icon-bar"></span> <span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#" th:href="@{/}">
Registration and
Login</a>
</div>
</div>
</nav>

<br><br><br><br><br><br><br>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">

<h1>User Login Page</h1>
<form th:action="@{/login}" method="post">

<!-- error message -->
<div th:if="${param.error}">
<div class="alert alert-danger">Invalid username or
password.</div>
</div>

<!-- logout message -->
<div th:if="${param.logout}">
<div class="alert alert-info">
You have been logged out.</div>
</div>

<div class="form-group">
<label for="username"> Username </label> :
<input type="text" class="form-control" id="username"
name="username" placeholder="Enter Email ID"
autofocus="autofocus">
</div>

<div class="form-group">
<label for="password">Password</label>:
<input type="password" id="password" name="password"
class="form-control" placeholder="Enter Password" />
</div>

<div class="form-group">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<input type="submit" name="login-submit"
id="login-submit"
class="form-control btn btn-primary"
value="Log In" />
</div>
</div>
</div>
</form>
<div class="form-group">
<span>New user? <a href="/" th:href="@{/registration}">
Register
here</a></span>
</div>
</div>
</div>
</div>
</body>
</html>



registration.html
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Spring Security Demo</title>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

</head>
<style>
.navbar {
background-color: green;
}
</style>

<body>

<!-- create navigation bar ( header) -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed"
data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span> <span
class="icon-bar"></span> <span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#" th:href="@{/}">
Registration and
Login </a>
</div>
</div>
</nav>

<br><br><br><br><br><br><br>
<!-- Create HTML registration form -->
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">

<!-- success message -->
<div th:if="${param.success}">
<div class="alert alert-info">You've successfully registered
to our awesome app!</div>
</div>

<h1>Registration</h1>

<form th:action="@{/registration}" method="post"
th:object="${user}">
<div class="form-group">
<label class="control-label" for="firstName">
First Name </label>
<input id="firstName" class="form-control"
th:field="*{firstName}" required
autofocus="autofocus" />
</div>

<div class="form-group">
<label class="control-label" for="lastName">
Last Name </label> <input id="lastName"
class="form-control" th:field="*{lastName}"
required autofocus="autofocus" />
</div>

<div class="form-group">
<label class="control-label" for="email"> Email
</label> <input id="email" class="form-control"
th:field="*{email}" required autofocus="autofocus"
/>
</div>

<div class="form-group">
<label class="control-label" for="password">
Password </label> <input id="password"
class="form-control" type="password"
th:field="*{password}" required
autofocus="autofocus" />
</div>

<div class="form-group">
<button type="submit" class="btn btn-primary">
Register</button>
<span>Already registered? <a href="/"
th:href="@{/login}">Login
here</a></span>
</div>
</form>
</div>
</div>
</div>
</body>
</html>


Github repository download link is provided at the end of this tutorial

Local Setup:

Step 1: Download or clone the source code to a local machine.

Spring Boot 

Step 2mvn clean install

Step 3: Run the Spring Boot application
mvn spring-boot:run



Popular posts from this blog

Spring boot video streaming example-HTML5

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Spring Boot + Mockito simple application with 100% code coverage

Spring Boot + OpenCSV Export Data to CSV Example

Custom Exception Handling in Quarkus REST API

DataTable-Pagination example with Spring boot, jQuery and ajax

Node JS mini projects with source code - free download

Spring boot web project free download:User Registration System

Java - Blowfish Encryption and decryption Example