BUILDING A REGISTRATION PAGE & A LOGIN PAGE USING SPRING BOOT, SPRING DATA JPA, SPRING SECURITY, MYSQL, HIBERNATE, THYMELEAF AND BOOTSTRAP

bimsara.bodaragama
DevOps.dev
Published in
14 min readOct 17, 2022

--

This instructional exercise will walk you through the way toward making a basic User Account Registration and a Login Example using Spring Boot, Spring Data JPA, Spring Security, MySQL, Hibernate, Thymeleaf and Bootstrap.

In this tutorial, we will create two principal functionalities:

1. Register User (store data into a MySQL database).

2. Login Authentication — validate user login credentials with database email and password.

EXPECTED OUTCOME

Registration Page

Login Page

TOOLS USED

  • Spring Boot — 2.0.4 RELEASE
  • Spring Framework — 5.0.8 RELEASE
  • JDK — 1.8 or later
  • IDE — Eclipse or Spring Tool Suite (STS)
  • Maven — 3.2+
  • Tomcat — 8.5+
  • Thymeleaf — 3.0.9 RELEASE
  • JQuery — 3.2.1
  • MySQL — 5.1.46
  • Hibernate — 5.2.17 Final
  • Bootstrap — 3.3.7

Now let’s follow the step by step methods to build our project.

Create a Spring Boot project and import in intelliJ IDEA or any IDE you prefer. (I have used intelliJ in this tutorial)

There are numerous approaches to make a Spring Boot application. The easiest path is to use Spring Initializer at http://start.spring.io/, which is an online Spring Boot application generator.

Now customize your application with the following details.

  • Generate: Maven Project
  • Java Version: 1.8 (Default)
  • Spring Boot: 2.4.5 or 2.0.4
  • Group: net.guides.springboot
  • Artifact: registration-login-springboot-security-thymeleaf
  • Name: registration-login-springboot-security-thymeleaf
  • Package Name: net.javaguides.springboot.springsecurity
  • Packaging: jar (This is the default value)
  • Dependencies: Web, JPA, MySQL, Thymeleaf, Security

Now click on the GENERATE button. Now you can extract the downloaded ZIP file and import it into your IDE.

Project Structure

Refer to the below screenshot of the project structure and create your project packaging structure as it has been shown here. Here I have showed how intelliJ IDEA as the IDE.

Maven project dependencies

You can see the Maven project dependencies is prom.xml file.

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>net.guides.springboot</groupId>
<artifactId>registration-login-springboot-security-thymeleaf</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>registration-login-springboot-security-thymeleaf</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath /> <! — lookup parent from repository →
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<! — https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils →
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>


<! — bootstrap and jquery →
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.2.1</version>
</dependency>

<! — mysql connector →
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<! — testing →
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

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


</project>

Spring security configuration

We secure our application using Spring Security Form Authentication using the following configuration. Ensure you permit all access to the /registration page and your static resources.

package net.javaguides.springboot.springsecurity.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.WebSecurityConfigurerAdapter;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.security.web.util.matcher.AntPathRequestMatcher;



import net.javaguides.springboot.springsecurity.service.UserService;



@Configuration

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {



@Autowired

private UserService userService;



@Override

protected void configure(HttpSecurity http) throws Exception {

http

.authorizeRequests()

.antMatchers(

"/registration**",

"/js/**",

"/css/**",

"/img/**",

"/webjars/**").permitAll()

.anyRequest().authenticated()

.and()

.formLogin()

.loginPage("/login")

.permitAll()

.and()

.logout()

.invalidateHttpSession(true)

.clearAuthentication(true)

.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))

.logoutSuccessUrl("/login?logout")

.permitAll();

}



@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());

}



}

Configure MySQL

Make a database with the name “registration_module” in the MySQL database server. We’ll have to arrange MySQL database URL, username, and password so that Spring can build up a connection with the database on startup.

Open application.properties and add following MySQL database configuration:

Ensure that you change the spring.datasource.username and spring.datasource.password properties according to your MySQL establishment.

spring.datasource.url = jdbc:mysql://localhost:3306/registration_module?useSSL=false

spring.datasource.username = root

spring.datasource.password =



## Hibernate Properties

# The SQL dialect makes Hibernate generate better SQL for the chosen database

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect



# Hibernate ddl auto (create, create-drop, validate, update)

spring.jpa.hibernate.ddl-auto = update

The spring.jpa.hibernate.ddl-auto = update property ensures that the database tables and the domain models in your application are in sync. At whatever point you change the domain model, hibernate will automatically update the mapped table in the database when you restart the application.

I have likewise indicated the log levels for hibernate with the goal that we can debug the SQL queries executed by hibernate.

SQL DDL Script

As we specified spring.jpa.hibernate.ddl-auto = update property will auto-create tables in MySQL database. The following are create articulations of three tables.

CREATE TABLE `role` (

`id` bigint(20) NOT NULL,

`name` varchar(255) DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;



CREATE TABLE `user` (

`id` bigint(20) NOT NULL,

`email` varchar(255) DEFAULT NULL,

`first_name` varchar(255) DEFAULT NULL,

`last_name` varchar(255) DEFAULT NULL,

`password` varchar(255) DEFAULT NULL,

PRIMARY KEY (`id`),

UNIQUE KEY `UKob8kqyqqgmefl0aco34akdtpe` (`email`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;





CREATE TABLE `users_roles` (

`user_id` bigint(20) NOT NULL,

`role_id` bigint(20) NOT NULL,

KEY `FKt4v0rrweyk393bdgt107vdx0x` (`role_id`),

KEY `FKgd3iendaoyh04b95ykqise6qh` (`user_id`),

CONSTRAINT `FKgd3iendaoyh04b95ykqise6qh` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),

CONSTRAINT `FKt4v0rrweyk393bdgt107vdx0x` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Define Spring controller layer

Ø UserRegistrationController

This controller is mapped to “/registration” URI. We use the UserRegistrationDto to process and validate the user registration form and inject it using the @ModelAttribute(“user”) annotation.

At the point when the form is submitted, it’s automatically validated and errors are accessible in the BindingResult. Then, we check if a user doesn’t as of now exist with the submitted email. On the off chance that the structure has any blunders, we get back to the registration page. Otherwise, we redirect and inform the user the registration procedure is sucsessfully completed.

package net.javaguides.springboot.springsecurity.web;



import javax.validation.Valid;



import org.springframework.beans.factory.annotation.Autowired;

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 org.springframework.web.bind.annotation.RequestMapping;



import net.javaguides.springboot.springsecurity.model.User;

import net.javaguides.springboot.springsecurity.service.UserService;

import net.javaguides.springboot.springsecurity.web.dto.UserRegistrationDto;



@Controller

@RequestMapping("/registration")

public class UserRegistrationController {



@Autowired

private UserService userService;



@ModelAttribute("user")

public UserRegistrationDto userRegistrationDto() {

return new UserRegistrationDto();

}



@GetMapping

public String showRegistrationForm(Model model) {

return "registration";

}



@PostMapping

public String registerUserAccount(@ModelAttribute("user") @Valid UserRegistrationDto userDto,

BindingResult result) {



User existing = userService.findByEmail(userDto.getEmail());

if (existing != null) {

result.rejectValue("email", null, "There is already an account registered with that email");

}



if (result.hasErrors()) {

return "registration";

}



userService.save(userDto);

return "redirect:/registration?success";

}

}

Ø MainController

This is a simple controller for managing trivial thymeleaf pages.

package net.javaguides.springboot.springsecurity.web;



import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

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



@Controller

public class MainController {



@GetMapping("/")

public String root() {

return "index";

}



@GetMapping("/login")

public String login(Model model) {

return "login";

}



@GetMapping("/user")

public String userIndex() {

return "user/index";

}



}

Ø UserRegistrationDto

We use the UserRegistrationDto to validate the user registration form. This DTO is annotated using Hibernate-Validation annotations which validate inconsequential fields on empty and our own custom @FieldMatch annotations which validate if the password is equivalent to the confirmed password and the email address field is equivalent to the confirmed email address field.

package net.javaguides.springboot.springsecurity.web.dto;



import javax.validation.constraints.AssertTrue;

import javax.validation.constraints.Email;

import javax.validation.constraints.NotEmpty;



import net.javaguides.springboot.springsecurity.constraint.FieldMatch;



@FieldMatch.List({

@FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),

@FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")

})

public class UserRegistrationDto {



@NotEmpty

private String firstName;



@NotEmpty

private String lastName;



@NotEmpty

private String password;



@NotEmpty

private String confirmPassword;



@Email

@NotEmpty

private String email;



@Email

@NotEmpty

private String confirmEmail;



@AssertTrue

private Boolean terms;



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 getPassword() {

return password;

}



public void setPassword(String password) {

this.password = password;

}



public String getConfirmPassword() {

return confirmPassword;

}



public void setConfirmPassword(String confirmPassword) {

this.confirmPassword = confirmPassword;

}



public String getEmail() {

return email;

}



public void setEmail(String email) {

this.email = email;

}



public String getConfirmEmail() {

return confirmEmail;

}



public void setConfirmEmail(String confirmEmail) {

this.confirmEmail = confirmEmail;

}



public Boolean getTerms() {

return terms;

}



public void setTerms(Boolean terms) {

this.terms = terms;

}

}

Ø Creating Field Matching Validator

We created a special @FieldMatch annotation to support the validation of comparing fields with one another on the off chance that they match. We can input two fields first and second and a discretionary message.

package net.javaguides.springboot.springsecurity.constraint;



import javax.validation.Payload;

import javax.validation.Constraint;

import java.lang.annotation.Documented;

import java.lang.annotation.Retention;

import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;

import static java.lang.annotation.ElementType.TYPE;

import static java.lang.annotation.RetentionPolicy.RUNTIME;



@Target({

TYPE,

ANNOTATION_TYPE

})

@Retention(RUNTIME)

@Constraint(validatedBy = FieldMatchValidator.class)

@Documented

public @interface FieldMatch {

String message() default "{constraints.field-match}";

Class < ? > [] groups() default {};

Class < ? extends Payload > [] payload() default {};

String first();

String second();



@Target({

TYPE,

ANNOTATION_TYPE

})

@Retention(RUNTIME)

@Documented

@interface List {

FieldMatch[] value();

}

}

Then, we create a custom validator by implementing the ConstraintValidator. Here we can validate if the given input fields match. In the event that they do, we return true, and if the fields don’t match with we return false.

package net.javaguides.springboot.springsecurity.constraint;



import javax.validation.ConstraintValidator;

import javax.validation.ConstraintValidatorContext;



import org.apache.commons.beanutils.BeanUtils;



public class FieldMatchValidator implements ConstraintValidator < FieldMatch, Object > {



private String firstFieldName;

private String secondFieldName;



@Override

public void initialize(final FieldMatch constraintAnnotation) {

firstFieldName = constraintAnnotation.first();

secondFieldName = constraintAnnotation.second();

}



@Override

public boolean isValid(final Object value, final ConstraintValidatorContext context) {

try {

final Object firstObj = BeanUtils.getProperty(value, firstFieldName);

final Object secondObj = BeanUtils.getProperty(value, secondFieldName);

return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);

} catch (final Exception ignore) {}

return true;

}

}

Define the service layer

Ø UserService

The UserService extends the UserDetailsService interface.

package net.javaguides.springboot.springsecurity.service;



import org.springframework.security.core.userdetails.UserDetailsService;



import net.javaguides.springboot.springsecurity.model.User;

import net.javaguides.springboot.springsecurity.web.dto.UserRegistrationDto;



public interface UserService extends UserDetailsService {



User findByEmail(String email);



User save(UserRegistrationDto registration);

}

Ø UserServiceImpl

In the UserServiceImpl we implement the methods to look up a user by email and to save the user registration using the UserRegistrationDto. Ensure when you save the user you’ll encode his password using the BCryptPasswordEncoder. Otherwise, a database administrator will be able to see his/her password in plain text.

package net.javaguides.springboot.springsecurity.service;



import java.util.Arrays;

import java.util.Collection;

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 net.javaguides.springboot.springsecurity.model.Role;

import net.javaguides.springboot.springsecurity.model.User;

import net.javaguides.springboot.springsecurity.repository.UserRepository;

import net.javaguides.springboot.springsecurity.web.dto.UserRegistrationDto;



@Service

public class UserServiceImpl implements UserService {



@Autowired

private UserRepository userRepository;



@Autowired

private BCryptPasswordEncoder passwordEncoder;



public User findByEmail(String email) {

return userRepository.findByEmail(email);

}



public User save(UserRegistrationDto registration) {

User user = new User();

user.setFirstName(registration.getFirstName());

user.setLastName(registration.getLastName());

user.setEmail(registration.getEmail());

user.setPassword(passwordEncoder.encode(registration.getPassword()));

user.setRoles(Arrays.asList(new Role("ROLE_USER")));

return userRepository.save(user);

}



@Override

public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

User user = userRepository.findByEmail(email);

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());

}

}

Define Spring data JPA repositories

Ø UserRepository

We create the UserRepository by extending the JpaRepository interface. This is a Spring Data interface and gives us all the CRUD operations automatically.

package net.javaguides.springboot.springsecurity.repository;



import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.stereotype.Repository;



import net.javaguides.springboot.springsecurity.model.User;



@Repository

public interface UserRepository extends JpaRepository < User, Long > {

User findByEmail(String email);

}

Define JPA Entities

We have annotated our User and Role objects with Java Persistence API annotations. These annotations are used to map our POJOs to the database.

Ø JPA entity — User

package net.javaguides.springboot.springsecurity.model;



import javax.persistence.*;

import java.util.Collection;



@Entity

@Table(uniqueConstraints = @UniqueConstraint(columnNames = "email"))

public class User {



@Id

@GeneratedValue(strategy = GenerationType.AUTO)

private Long id;



private String firstName;

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) {

this.firstName = firstName;

this.lastName = lastName;

this.email = email;

this.password = password;

}



public User(String firstName, String lastName, String email, String password, Collection < Role > roles) {

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;

}



@Override

public String toString() {

return "User{" +

"id=" + id +

", firstName='" + firstName + '\'' +

", lastName='" + lastName + '\'' +

", email='" + email + '\'' +

", password='" + "*********" + '\'' +

", roles=" + roles +

'}';

}

}

Ø JPA entity — Role

package net.javaguides.springboot.springsecurity.model;



import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;



@Entity

public class Role {



@Id

@GeneratedValue(strategy = GenerationType.AUTO)

private Long id;

private String name;



public Role() {}



public Role(String name) {

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;

}



@Override

public String toString() {

return "Role{" +

"id=" + id +

", name='" + name + '\'' +

'}';

}

}

Run Spring boot application

Let’s run the main Application class to run this standalone Spring boot application with embedded tomcat server:

package net.javaguides.springboot.springsecurity;



import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;



@SpringBootApplication

public class Application {



public static void main(String[] args) {

SpringApplication.run(Application.class, args);

}

}

Thymeleaf Templates

The thymeleaf pages are built with bootstrap and jquery. All templates are located in the src/main/resources/templates folder. Note that we have used webjars to manage client-side dependencies.

Ø User Registration Page

In the user registration page, we have multiple input fields. For each input field, we have a corresponding error message. On top of the form, we also have some global error messages.

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org">



<head>

<meta charset="utf-8" />

<meta http-equiv="X-UA-Compatible" content="IE=edge" />

<meta name="viewport" content="width=device-width, initial-scale=1" />



<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.min.css}" />



<title>Registration</title>

</head>



<body>

<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 Module</a>

</div>

</div>

</nav>



<br>

<br>



<div class="container">

<div class="row">

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



<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}" th:object="${user}" method="post">



<p class="error-message" th:if="${#fields.hasGlobalErrors()}" th:each="error : ${#fields.errors('global')}" th:text="${error}">Validation error

</p>



<div class="form-group" th:classappend="${#fields.hasErrors('firstName')}? 'has-error':''">

<label for="firstName" class="control-label">First name</label> <input id="firstName" class="form-control" th:field="*{firstName}" />

<p class="error-message" th:each="error: ${#fields.errors('firstName')}" th:text="${error}">Validation error</p>

</div>

<div class="form-group" th:classappend="${#fields.hasErrors('lastName')}? 'has-error':''">

<label for="lastName" class="control-label">Last name</label> <input id="lastName" class="form-control" th:field="*{lastName}" />

<p class="error-message" th:each="error : ${#fields.errors('lastName')}" th:text="${error}">Validation error</p>

</div>

<div class="form-group" th:classappend="${#fields.hasErrors('email')}? 'has-error':''">

<label for="email" class="control-label">E-mail</label> <input id="email" class="form-control" th:field="*{email}" />

<p class="error-message" th:each="error : ${#fields.errors('email')}" th:text="${error}">Validation error

</p>

</div>

<div class="form-group" th:classappend="${#fields.hasErrors('confirmEmail')}? 'has-error':''">

<label for="confirmEmail" class="control-label">Confirm

e-mail</label> <input id="confirmEmail" class="form-control" th:field="*{confirmEmail}" />

<p class="error-message" th:each="error : ${#fields.errors('confirmEmail')}" th:text="${error}">Validation error</p>

</div>

<div class="form-group" th:classappend="${#fields.hasErrors('password')}? 'has-error':''">

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

<p class="error-message" th:each="error : ${#fields.errors('password')}" th:text="${error}">Validation error</p>

</div>

<div class="form-group" th:classappend="${#fields.hasErrors('confirmPassword')}? 'has-error':''">

<label for="confirmPassword" class="control-label">Confirm

password</label> <input id="confirmPassword" class="form-control" type="password" th:field="*{confirmPassword}" />

<p class="error-message" th:each="error : ${#fields.errors('confirmPassword')}" th:text="${error}">Validation error</p>

</div>

<div class="form-group" th:classappend="${#fields.hasErrors('terms')}? 'has-error':''">

<input id="terms" type="checkbox" th:field="*{terms}" /> <label class="control-label" for="terms"> I agree with the <a

href="#">terms and conditions</a> for Registration.

</label>

<p class="error-message" th:each="error : ${#fields.errors('terms')}" th:text="${error}">Validation error

</p>

</div>

<div class="form-group">

<button type="submit" class="btn btn-success">Register</button>

<span>Already registered? <a href="/" th:href="@{/login}">Login

here</a></span>

</div>



</form>

</div>

</div>

</div>



<script type="text/javascript" th:src="@{/webjars/jquery/3.2.1/jquery.min.js/}"></script>

<script type="text/javascript" th:src="@{/webjars/bootstrap/3.3.7/js/bootstrap.min.js}"></script>

</body>

</html>

Ø Index Page

The index page can be accessed via http://localhost:8080/ after a successful login.

<!DOCTYPE html>

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



<head>

<meta charset="utf-8" />

<meta http-equiv="X-UA-Compatible" content="IE=edge" />

<meta name="viewport" content="width=device-width, initial-scale=1" />



<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.min.css}" />



<title>Registration</title>

</head>



<body>

<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 Module</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>

<div class="container">

<h2>User Registration and Login Module using Spring Boot, Spring MVC, Spring Security, JPA/Hibernate and Thymeleaf</h2>



<p>

Welcome <span sec:authentication="principal.username">User</span>

</p>

</div>



<script type="text/javascript" th:src="@{/webjars/jquery/3.2.1/jquery.min.js/}"></script>

<script type="text/javascript" th:src="@{/webjars/bootstrap/3.3.7/js/bootstrap.min.js}"></script>



</body>



</html>

Ø Login Page

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org">



<head>

<meta charset="utf-8" />

<meta http-equiv="X-UA-Compatible" content="IE=edge" />

<meta name="viewport" content="width=device-width, initial-scale=1" />



<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.min.css}" />



<title>Registration</title>

</head>

<body>

<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 Module</a>

</div>

</div>

</nav>

<br>

<br>

<div class="container">



<div class="row">

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

<h1>Login page</h1>

<form th:action="@{/login}" method="post">

<div th:if="${param.error}">

<div class="alert alert-danger">Invalid username or password.

</div>

</div>

<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" id="username" name="username" class="form-control" autofocus="autofocus" placeholder="Username" />

</div>

<div class="form-group">

<label for="password">Password</label>: <input type="password" id="password" name="password" class="form-control" placeholder="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>

<div class="form-group">

<span>New user? <a href="/" th:href="@{/registration}">Register

here</a></span>

</div>

</form>

</div>

</div>





</div>



<script type="text/javascript" th:src="@{/webjars/jquery/3.2.1/jquery.min.js/}"></script>

<script type="text/javascript" th:src="@{/webjars/bootstrap/3.3.7/js/bootstrap.min.js}"></script>

</body>

</html>

Demo

Ø Registration page Demo

Access http://localhost:8080/registration and fill in some valid and invalid fields and try the demo.

After successful registration, the user is redirected to http://localhost:8080/registration?success

Ø Login page Demo

You can go to the login page at http://localhost:8080/login and log in with the registered user. In addition, try out some invalid logins and check what happens.

After successfully, login will navigate below the page.

Then try logging out. You will see the below outcome.

Try the above demos with many invalid values valid values and see what happens.

Ø MySQL Database Server Demo

Now you can see the database has been created in MySQL. Here I have used phpMyAdmin.

Source Code on GitHub

The source code of this tutorial is available on my GitHub Repository.

· https://github.com/BimsaraBodaragama/user-registration-login-springboot-security-thymeleaf

References

· https://www.javaguides.net/2019/08/registration-login-example-using-springboot-spring-data-jpa-hibernate-mysql-thymeleaf.html

· https://www.javaguides.net/2018/09/spring-boot2-mvc-web-application-thymeleaf-jpa-mysql-example.html

· https://www.javaguides.net/2018/10/user-registration-module-using-springboot-springmvc-springsecurity-hibernate5-thymeleaf-mysql.html

· https://www.javaguides.net/p/spring-boot-tutorial.html

— — — — — — — — — — — — — — — — — — — — — — — — — — — — —

D. B. Bodaragama

Undergraduate

Department of Computer Science & Engineering

University of Moratuwa

Sri Lanka

dinukab.19@cse.mrt.ac.lk

— — — — — — — — — — — — — — — — — — — — — — — — — — — — —

--

--

Undergraduate in University of Moratuwa Sri Lanka specialized in Computer Science and Engineering. Special interest in Physics.