📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
✅ Some premium posts are free to read — no account needed. Follow me on Medium to stay updated and support my writing.
🎓 Top 10 Udemy Courses (Huge Discount): Explore My Udemy Courses — Learn through real-time, project-based development.
▶️ Subscribe to My YouTube Channel (172K+ subscribers): Java Guides on YouTube
In the previous article, we have learned Spring Boot + Spring MVC + Role Based Spring Security + JPA + Thymeleaf + MySQL Tutorial
What we’ll build?
We will develop two main functionalities:
1. Register user (stored data into MySQL database).
2. Login Authentication - validate user login credentials with database email and password.
Tools and Technologies Used
- Spring Boot - 2.0.4.RELEASE
- JDK - 1.8 or later
- Spring Framework - 5.0.8 RELEASE
- Hibernate - 5.2.17. Final
- Maven - 3.2+
- IDE - Eclipse or Spring Tool Suite (STS)
- Tomcat - 8.5+
- Thymeleaf - 3.0.9 RELEASE
- Bootstrap - 3.3.7
- JQuery - 3.2.1
- MySQL - 5.1.46
Creating and Importing a Project
- Generate: Maven Project
- Java Version: 1.8 (Default)
- Spring Boot: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
Project Structure
The pom.xml File - Maven Dependencies
<?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
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 and View Resolver
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:mysql://localhost:3306/registration_module?useSSL=false
spring.datasource.username = root
spring.datasource.password = root
## 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
Database Create SQL Scripts
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;
Spring Rest Controller - UserRegistrationController.java
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";
}
}
Spring Rest Controller - MainController.java
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";
}
}
Data Transfer Object - UserRegistrationDto.java
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
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();
}
}
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;
}
}
Service Layer - UserService.java
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);
}
Service Layer - UserServiceImpl.java
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());
}
}
Spring Data Repository - UserRepository.java
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);
}
Create JPA Entities
PA entity - User.java
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.java
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 + '\'' +
'}';
}
}
Spring Boot Application Running
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);
}
}
Thyemeleaf Templates
User Registration 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">
<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
<!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
Login page demo
Reference
Learn complete Spring boot on Spring Boot Tutorial
The source code of this article available on my GitHub repository registration-login-springboot-security-thymeleaf
UserDetailsService.class is missing :-(
ReplyDeleteUserDetailsService is a Spring security built-in interface, which provides loadUserByUserName() method to load user and roles from database or in-memory. Note that the UserService extends UserDetailsService. So UserDetailsService is not a class, it is a Spring security built-in interface.
Delete