Spring Boot + Spring MVC + Role Based Spring Security + JPA + Thymeleaf + MySQL Tutorial

In this tutorial, we will learn how to use the Spring Boot Security Starter to secure SpringMVC-based web applications. We will develop step by step Message Storing Spring MVC web application(securing with spring security) using spring boot, spring MVC, role-based spring security, JPA, thymeleaf, and MySQL.
Security is an important aspect of software application design. It ensures that only those who have authority to access the secured resources can do so. When it comes to securing an application, two primary things we’ll need to take care of are authentication and authorization.
  • Authentication refers to the process of verifying the user, which is typically done by asking for credentials.
  • Authorization refers to the process of verifying whether or not the user is allowed to do a certain activity.
Spring Security is a framework for securing Java-based applications at various layers with great flexibility and customizability. Spring Security provides authentication and authorization support against database authentication, LDAP, Java Authentication and Authorization Service (JAAS), and many more. Spring Security provides support for dealing with common attacks like CSRF, XSS, and session fixation protection, with minimal configuration.
In this tutorial, we will use Java-based configuration support for security. Using Spring Security in Spring Boot application became easier with its autoconfiguration features.

Table of Contents

  1. Spring Security with its autoconfiguration features
  2. What we’ll build
  3. Tools and Technologies Used
  4. Database Design for Role Based Spring Security
  5. Creating and Importing a Project
  6. Packaging Structure
  7. The pom.xml File
  8. Create the JPA entities called Users, Roles and Message
  9. Spring Data JPA Repository Interface - UserRepository.java
  10. Spring Data JPA Repository Interface - MessageRepository.java
  11. UserDetailsService Implementation
  12. Customized Spring Security Configuration Extending WebSecurityConfigurerAdapter
  13. Spring WebMVC Configuration
  14. MySQL Configuration - application.properties
  15. Implementing the Remember-Me Feature
  16. Persistent Tokens
  17. Thymeleaf layout View
  18. Thymeleaf Login View
  19. Thymeleaf Userhome View
  20. Thymeleaf Adminhome View
  21. Index View
  22. Sample data for Users and Roles - src/main/resources/data.sql
  23. Running Application
  24. Demo

1. Spring Security with its autoconfiguration features

Before moving to secure actual projects, let's discuss spring boot provided autoconfiguration of spring security for a quick start.
Adding the Spring Security Starter (spring-boot-starter-security) to a Spring Boot application will:
  • Enable HTTP basic security
  • Register the AuthenticationManager bean with an in-memory store and a single user
  • Ignore paths for commonly used static resource locations (such as /css/, /js/, /images/**, etc.)
  • Enable common low-level features such as XSS, CSRF, caching, etc.
Add below dependencies to pom.xml file
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</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-security</artifactId>
</dependency>
Now if you run the application and access http://localhost:8080, you will be prompted to enter the user credentials. The default user is user and the password is auto-generated. You can find it in the console log.
Using default security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35
You can change the default user credentials in application.properties as follows:
security.user.name=admin
security.user.password=secret
security.user.role=USER,ADMIN
Okay, this is nice for a quick demo. But in our actual projects, we may want to implement role-based access control using a persistence data store such as a database. Also, you might want to fine-tune the access to resources (URLs, service layer methods, etc.) based on roles.
Now it's time to see how to customize the default Spring Security autoconfiguration and develop step by step Spring MVC web application.

2. What we’ll build

We will develop step by step message storing Spring MVC web application(securing with spring security) using spring boot, spring MVC, role-based spring security, JPA, thymeleaf, and MySQL.

3. 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+
  • Spring Data JPA - 2.0.10 RELEASE
  • IDE - Eclipse or Spring Tool Suite (STS)
  • MYSQL - 5.1.47
  • Spring Security - 5.0.7 RELEASE
  • Thymeleaf-Spring5 - 3.0.9 RELEASE

4. Database Design for Role-Based Spring Security

First, we’ll create the database tables as below to store users and roles.

5. Creating and Importing a Project

There are many ways to create a Spring Boot application. The simplest way is to use Spring Initializr at http://start.spring.io/, which is an online Spring Boot application generator.
Look at the above diagram, we have specified following details:
  • Generate: Maven Project
  • Java Version: 1.8 (Default)
  • Spring Boot:2.0.4
  • Group: net.javaguides.springbootsecurity
  • Artifact: springboot-thymeleaf-security-demo
  • Name: springboot-thymeleaf-security-demo
  • Description: springboot-thymeleaf-security-demo
  • Package Name : net.javaguides.springbootsecurity
  • Packaging: jar (This is the default value)
  • Dependencies: Web, JPA, MySQL, DevTools,Security
Once, all the details are entered, click on Generate Project button will generate a spring boot project and downloads it. Next, Unzip the downloaded zip file and import it into your favorite IDE.

6. Packaging Structure

Following is the packing structure for your reference -

7. The pom.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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.apress</groupId>
    <artifactId>springboot-thymeleaf-security-demo</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>

    <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-thymeleaf</artifactId>
        </dependency>
        <dependency>
             <groupId>nz.net.ultraq.thymeleaf</groupId>
             <artifactId>thymeleaf-layout-dialect</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
         <dependency>
             <groupId>org.thymeleaf.extras</groupId>
             <artifactId>thymeleaf-extras-springsecurity4</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-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Note that we have used Spring Data JPA starter to talk to MySQL database.

8. Create the JPA entities called User, Role, and Message

User JPA Entity

package net.javaguides.springbootsecurity.entities;

import java.util.List;


import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;

/**
 * @author Ramesh Fadatare
 *
 */
@Entity
@Table(name="users")
public class User
{
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    private Integer id;
    @Column(nullable=false)
    @NotEmpty()
    private String name;
    @Column(nullable=false, unique=true)
    @NotEmpty
    @Email(message="{errors.invalid_email}")
    private String email;
    @Column(nullable=false)
    @NotEmpty
    @Size(min=4)
    private String password;
 
    @ManyToMany(cascade=CascadeType.MERGE)
    @JoinTable(
       name="user_role",
       joinColumns={@JoinColumn(name="USER_ID", referencedColumnName="ID")},
       inverseJoinColumns={@JoinColumn(name="ROLE_ID", referencedColumnName="ID")})
    private List<Role> roles;
 
    public Integer getId()
    {
        return id;
    }
    public void setId(Integer id)
    {
        this.id = id;
    }
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    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 List<Role> getRoles()
    {
        return roles;
    }
    public void setRoles(List<Role> roles)
    {
        this.roles = roles;
    }
}

Role JPA Entity

package net.javaguides.springbootsecurity.entities;

import java.util.List;


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

import org.hibernate.validator.constraints.NotEmpty;

/**
 * @author Ramesh Fadatare
 *
 */
@Entity
@Table(name = "roles")
public class Role {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    @Column(nullable = false, unique = true)
    @NotEmpty
    private String name;

    @ManyToMany(mappedBy = "roles")
    private List < User > users;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List < User > getUsers() {
        return users;
    }

    public void setUsers(List < User > users) {
        this.users = users;
    }
}

Message JPA Entity

package net.javaguides.springbootsecurity.entities;

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

/**
 * @author Ramesh Fadatare
 *
 */
@Entity
@Table(name = "messages")
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(nullable = false)
    private String content;

    public Integer getId() {
        return id;
    }

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

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
Next, create the Spring Data JPA repository for the user entity.

9. Spring Data JPA Repository Interface - UserRepository.java

package net.javaguides.springbootsecurity.repositories;

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import net.javaguides.springbootsecurity.entities.User;

/**
 * @author Ramesh Fadatare
 *
 */
public interface UserRepository extends JpaRepository<User, Integer>
{
    Optional<User> findByEmail(String email);
}
Next, create the Spring Data JPA repository for the Message entity.

10. Spring Data JPA Repository Interface - MessageRepository.java

package net.javaguides.springbootsecurity.repositories;

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

import net.javaguides.springbootsecurity.entities.Message;

/**
 * @author Ramesh Fadatare
 *
 */
public interface MessageRepository extends JpaRepository<Message, Integer>{

}
Spring Security uses the UserDetailsService interface, which contains the loadUserByUsername(String username) method to look up UserDetails for a given username. The UserDetails interface represents an authenticated user object and Spring Security provides an out-of-the box implementation of org.springframework.security.core.userdetails.User. Now we implement a UserDetailsService to get UserDetails from database.

11. UserDetailsService Implementation

package net.javaguides.springbootsecurity.security;

import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import net.javaguides.springbootsecurity.entities.User;
import net.javaguides.springbootsecurity.repositories.UserRepository;

/**
 * @author Ramesh Fadatare
 *
 */
@Service
@Transactional
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(userName)
       .orElseThrow(() -> new UsernameNotFoundException("Email " + userName + " not found"));
         return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(),
         getAuthorities(user));
    }

    private static Collection<? extends GrantedAuthority> getAuthorities(User user) {
        String[] userRoles = user.getRoles().stream().map((role) -> role.getName()).toArray(String[]::new);
        Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(userRoles);
        return authorities;
    }
}
Spring Boot implemented the default Spring Security autoconfiguration in SecurityAutoConfiguration. To switch the default web application security configuration and provide our own customized security configuration, we can create a configuration class that extends WebSecurityConfigurerAdapter and is annotated with @EnableWebSecurity.
Now we’ll create a configuration class that extends WebSecurityConfigurerAdapter to customize the default Spring Security configuration.

12. Customized Spring Security Configuration Extending WebSecurityConfigurerAdapter

package net.javaguides.springbootsecurity.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private UserDetailsService customUserDetailsService;
 
    @Autowired
    private DataSource dataSource;
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
 
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
         .userDetailsService(customUserDetailsService)
         .passwordEncoder(passwordEncoder());
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http         
         .headers()
          .frameOptions().sameOrigin()
          .and()
            .authorizeRequests()
             .antMatchers("/resources/**", "/webjars/**","/assets/**").permitAll()
                .antMatchers("/").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/home")
                .failureUrl("/login?error")
                .permitAll()
                .and()
            .logout()
             .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
             .logoutSuccessUrl("/login?logout")
             .deleteCookies("my-remember-me-cookie")
                .permitAll()
                .and()
             .rememberMe()
              //.key("my-secure-key")
              .rememberMeCookieName("my-remember-me-cookie")
              .tokenRepository(persistentTokenRepository())
              .tokenValiditySeconds(24 * 60 * 60)
              .and()
            .exceptionHandling()
              ;
    }
    
    PersistentTokenRepository persistentTokenRepository(){
     JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
     tokenRepositoryImpl.setDataSource(dataSource);
     return tokenRepositoryImpl;
    }
}
This example configures CustomUserDetailsService and BCryptPasswordEncoder to be used by AuthenticationManager instead of the default in-memory database with a single-user with a plaintext password.
The configure(HttpSecurity http) method is configured to:
  • Ignore the static resource paths "/resources/", "/webjars/", and "/assets/**"
  • Allow everyone to have access to the root URL "/"
  • Restrict access to URLs that start with /admin/ to only users with the ADMIN role
  • All other URLs should be accessible to authenticated users only
We are also configuring custom form-based login parameters and making them accessible to everyone. The example also configures the URL to redirect the users to the /accessDenied URL if they try to access a resource they don’t have access to. We are going to use Thymeleaf view templates for rendering views. The thymeleaf-extrasspringsecurity4 module provides Thymeleaf Spring Security dialect attributes (sec:authentication, sec:authorize, etc.) to conditionally render parts of the view based on authentication status, logged-in user roles, etc.
Add the following dependency to use the Thymeleaf Spring Security dialect.
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
Now we need to create a configuration class for providing MVC configuration.

13. Spring WebMVC Configuration

package net.javaguides.springbootsecurity.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect;

/**
 * @author Ramesh Fadatare
 *
 */
@Configuration
public class WebConfig implements WebMvcConfigurer
{   
 
    @Autowired
    private MessageSource messageSource;

    @Override
    public void addViewControllers(ViewControllerRegistry registry)
    {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/login").setViewName("login");
        //registry.addViewController("/home").setViewName("userhome");
        registry.addViewController("/admin/home").setViewName("adminhome");
        //registry.addViewController("/403").setViewName("403");   
    }
 
    @Override
    public Validator getValidator() {
        LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
        factory.setValidationMessageSource(messageSource);
        return factory;
    }
 
    @Bean
    public SpringSecurityDialect securityDialect() {
         return new SpringSecurityDialect();
    }
}
Note that we have configured view controllers to specify which view to render for which URL. Also, it registers SpringSecurityDialect to enable using the Thymeleaf Spring Security dialect.

14. MySQL Configuration - application.properties


################### DataSource Configuration ##########################
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root

spring.datasource.initialization-mode=always

################### Hibernate Configuration ##########################

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

#security.user.name=admin
#security.user.password=secret
#security.user.role=USER,ADMIN

15. Implementing the Remember-Me Feature

Spring Security provides the Remember-Me feature so that applications can remember the identity of a user between sessions. To use the Remember-Me functionality, you just need to send the HTTP parameter remember-me.
<input type="checkbox" name="remember-me"> Remember Me
Spring Security provides the following two implementations of the Remember-Me feature out-of-the-box:
  • Simple hash-based token as a cookie —This approach creates a token by hashing the user identity information and setting it as a cookie on the client browser.
  • Persistent token —This approach uses a persistent store like a relational database to store the tokens.
In this tutorial, we will use the Persistent token approach.

16. Persistent Tokens

We will implement Spring Security Remember-Me feature, which can be used to store the generated tokens in a persistent storage such as a database. The persistent tokens approach is implemented using org.springframework.security.web.authentication.rememberme.
PersistentTokenBasedRememberMeServices, which internally uses the PersistentTokenRepository interface to store the tokens.
Spring provides the following two implementations of PersistentTokenRepository out-of-the-box.
  • InMemoryTokenRepositoryImpl can be used to store tokens in-memory (not recommended for production use).
  • JdbcTokenRepositoryImpl can be used to store tokens in a database.
The JdbcTokenRepositoryImpl stores the tokens in the persistent_logins table.

persistent_logins table

create table persistent_logins
(
   username varchar(64) not null,
   series varchar(64) primary key,
   token varchar(64) not null,
   last_used timestamp not null
);
Now that we have all the configuration ready, it’s time to create views using Thymeleaf.

17. Thymeleaf layout View

The most important part of this page is layout:fragment="content". This is the heart of the decorator page (layout). This is tutorial uses Standard Thymeleaf Layout System.
Read more about thymeleaf layouts on https://www.thymeleaf.org/doc/articles/layouts.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:th="http://www.thymeleaf.org"
 xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">SpringBoot
 Thymeleaf</title>
<meta
 content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
 name="viewport" />
<link rel="stylesheet"
 th:href="@{/assets/bootstrap/css/bootstrap.min.css}" />
<link rel="stylesheet"
 th:href="@{/assets/font-awesome-4.5.0/css/font-awesome.min.css}" />
<link rel="stylesheet" th:href="@{/assets/css/styles.css}" />
<style>
.footer {
 position: fixed;
 left: 0;
 bottom: 0;
 width: 100%;
 background-color: black;
 color: white;
 height: 100px;
 text-align: center;
}
</style>
</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="@{/}">SpringBoot
     Thymeleaf</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>

 <div class="container">
  <div layout:fragment="content">
   <!-- Your Page Content Here -->
  </div>
 </div>

 <script th:src="@{'/assets/js/jquery-2.1.4.min.js'}"></script>
 <script th:src="@{'/assets/bootstrap/js/bootstrap.min.js'}"></script>

 <div class="footer">
  <h1>
   <a href="http://www.javaguides.net/p/spring-boot-tutorial.html">
    Spring Boot Tutorial</a>
  </h1>
 </div>

</body>
</html>

18. Thymeleaf Login View

This code creates the login form with the username and password fields and renders a login error if there is an error request parameter. The code configures the login form failureUrl to "/login?error", so if the users provide incorrect credentials, they will be redirected to the /login?error URL.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:th="http://www.thymeleaf.org" 
 xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
 layout:decorator="layout">
<head>
<title>Log in</title>
</head>
<body>
 <div layout:fragment="content">
  <div class="panel col-md-5">
   <div class="panel panel-primary">
    <div class="panel-heading">Login Form</div>
    <div class="panel-body">
     <form action="home" th:action="@{/login}" method="post">
      <div class="form-group has-feedback">
       <input type="email" class="form-control" name="username"
        placeholder="Email" /> <span
        class="glyphicon glyphicon-envelope form-control-feedback"></span>
      </div>
      <div class="form-group has-feedback">
       <input type="password" class="form-control" name="password"
        placeholder="Password" /> <span
        class="glyphicon glyphicon-lock form-control-feedback"></span>
      </div>
      <div class="form-group">
       <label>
            <input type="checkbox" name="remember-me"> Remember Me
          </label>
      </div>
      <div class="row">
       <div class="form-group col-xs-offset-8 col-xs-4">
        <button type="submit" class="btn btn-primary btn-block btn-flat"
         th:text="#{label.login}">LogIn</button>
       </div>
      </div>
      <div class="row">
       <div class="col-xs-12">
        <div th:if="${param.error}"
         class="alert alert-danger alert-dismissable">
         <p>
          <i class="icon fa fa-ban"></i> <span
           th:text="#{error.login_failed}">Invalid Email and
           Password.</span>
         </p>
        </div>
        <div th:if="${param.logout}"
         class="alert alert-info alert-dismissable">
         <p>
          <i class="icon fa fa-info"></i> <span
           th:text="#{info.logout_success}">You have been logged
           out.</span>
         </p>
        </div>
        <div th:if="${msg!=null}"
         class="alert alert-warning alert-dismissable">
         <p>
          <i class="icon fa fa-warning"></i> <span th:text="${msg}"></span>
         </p>
        </div>
       </div>
      </div>
     </form>
    </div>
   </div>
  </div>
 </div>
</body>
</html>

19. Thymeleaf Userhome View

The code configures "/home" as defaultSuccessUrl, so after successful authentication, users will be redirected to the /home URL, which will render the userhome.html view.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:th="http://www.thymeleaf.org"
 xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
 layout:decorator="layout">
<head>
   <title>User Home</title>
</head>
<body>
 <div layout:fragment="content">
  <p>
   Welcome <span sec:authentication="principal.username">User</span>
  </p>
  <p>
   <a th:href="@{/logout}">Logout</a>
  </p>
  <div sec:authorize="hasRole('ROLE_ADMIN')">
   <h3>You will see this only if you are ADMIN</h3>
   <p>
    <a th:href="@{/admin/home}">Admin Home</a>
   </p>
  </div>
  <h3>Form with CSRF Token</h3>
  <form th:action="@{/messages}" method="post">
   <textarea name="content" cols="50" rows="5"></textarea>
   <br>
   <input type="submit" value="Submit" />
  </form>
  <div>
  <br>
   <div class="panel panel-default">
    <div class="panel-heading">
     Messages
    </div>
    <p th:each="msg: ${msgs}" th:text="${msg.content}"></p>
   </div>
  </div>
 </div>
</body>
</html>
In the userhome.html view, you are using sec:authentication="principal.username to display the authenticated username. This example also conditionally renders the link to the admin’s home page only if the authenticated user has the role ROLE_ADMIN. This is done by using sec:authorize="hasRole ('ROLE_ADMIN').
The above file is our decorator for content pages we will be creating in the application. The most important thing about the above example is layout:fragment="content. This is the heart of the decorator page (layout). You can also notice, that header and footer are included using Standard Thymeleaf Layout System.
Read more about thymeleaf layouts on https://www.thymeleaf.org/doc/articles/layouts.html

20. Thymeleaf Adminhome View

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org"
   xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout">      
    <head>
        <title>Admin Home</title>
    </head>
    <body>
     <div layout:fragment="content">
         <p>Welcome Administrator(<span sec:authentication="principal.username">Admin</span>)</p>
         <p sec:authorize="isAuthenticated()"><a th:href="@{/logout}">Logout</a></p>
     </div>
     <h1>This is admin home page</h1>
    </body>    
</html>

21. Index View

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="http://www.thymeleaf.org"
   xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
      layout:decorator="layout">
      
      <head>
        <title>Home</title>
    </head>
    <body>
     <div layout:fragment="content">
     
       <p sec:authorize="isAnonymous()"><a th:href="@{/login}">Login</a></p>
          <p><a th:href="@{/home}">User Home</a></p>
       <p><a th:href="@{/admin/home}">Admin Home</a></p>
       <p sec:authorize="isAuthenticated()"><a th:href="@{/logout}">Logout</a></p>
     </div>
    </body>
</html>
Before running this application, we need to initialize the database with some sample data for users and roles.

22. Sample data for Users and Roles - src/main/resources/data.sql

create table if not exists persistent_logins ( 
     username varchar(100) not null, 
     series varchar(64) primary key, 
     token varchar(64) not null, 
     last_used timestamp not null
);

delete from  user_role;
delete from  roles;
delete from  users;

INSERT INTO roles (id, name) VALUES 
(1, 'ROLE_ADMIN'),
(2, 'ROLE_ACTUATOR'),
(3, 'ROLE_USER');

INSERT INTO users (id, email, password, name) VALUES 
(1, 'admin@gmail.com', '$2a$10$hKDVYxLefVHV/vtuPhWD3OigtRyOykRLDdUAp80Z1crSoS1lFqaFS', 'Admin'),
(3, 'user@gmail.com', '$2a$10$ByIUiNaRfBKSV6urZoBBxe4UbJ/sS6u1ZaPORHF9AtNWAuVPVz1by', 'User');

insert into user_role(user_id, role_id) values
(1,1),
(1,2),
(1,3),
(3,2);
The passwords are encrypted using the BCryptPasswordEncoder.encode(plan_tx_password) method.

23. Running Application

Two ways we can start the standalone Spring boot application.
  1. From the root directory of the application and type the following command to run it -
$ mvn spring-boot:run
  1. From your IDE, run the SpringbootThymeleafSecurityDemoApplication.main() method as a standalone Java class that will start the embedded Tomcat server on port 8080 and point the browser to http://localhost:8080/.
package net.javaguides.springbootsecurity;

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

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

24. Demo

After running this application, go to http://localhost:8080. You will be redirected to the index page:

Click on any link will redirect to the login page:

After submitting the valid credentials (such as admin@gmail.com/admin), you will be redirected to the home page. If you have logged in as a user with the ADMIN role, you should be able to see the text You will see this only if you are ADMIN and a link to Admin Home.

If you click on the Admin Home link, you should be able to see the Admin Homepage:

If you have logged in as a normal user (user@gmail.com/user), you will not be able to see You will see this only if you are ADMIN and a link to Admin Home. We create and list messages to and from database.

Click on logout link will redirect to login page with the proper message:
This is the end of the application flow and tutorial.
Note that I haven't used webjars feature and I have manually added css and js in asset folder. Download CSS and js from my GitHub repository.
Do comment if you don't understand any flow or code. My suggestion is to clone the source code of this tutorial and import to your IDE and try to run it.
The source code of this tutorial available on my github Repository at springboot-thymeleaf-security-demo
Complete Spring Boot tutorial available on Spring Boot Tutorial

Comments