Spring Security + Spring Data JPA+ Thymeleaf — MySql User Details Configuration (Spring Security — 2)

Amila Iroshan
The Fresh Writes
Published in
7 min readFeb 7, 2023

Overview

This is a extension of spring security part 1 post.This article based on my previouse one.
In this sample application express how to handle authentication and authorization using spring security with JPA and hibernate.

Lets start the implementation

Create spring boot application.
— Navigate to https://start.spring.io.

— Choose either Gradle or Maven as build tool. In here I’m using mavean,Java 18 and .jar as packaging.

Click Dependencies and select Spring starter Web, Spring security, Spring data jpa, mysql-connector and Thymeleaf. Here I’m using MySql DB and my pom.xml file as below.

<?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>dev.mwhyte</groupId>
<artifactId>spring-security-basic</artifactId>
<version>0.0.1-SNAPSHOT</version>

<name>spring-security-basic</name>
<description>An introduction to spring security</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>

<!-- DB Connection-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

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

Configure data source properties

spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/security_basics?allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=amila

spring.sql.init.mode=always
spring.datasource.initialization-mode=always

Note that the first line tells Hibernate create tables upon startup. Update URL, username and password according to your MySQL database.I have added data.sql and schema.sql files to project class path. When bootstrapping the application, create the table and insert data using “spring.datasource.initialization-mode=always”.

CREATE TABLE IF NOT EXISTS users (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(65) NOT NULL,
`password` varchar(255) NOT NULL,
`role` varchar(45) NOT NULL,
`enabled` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`user_id`)
);
INSERT INTO users(username,password,role,enabled) VALUES ('user','$2a$12$PzQbi84RG7rz.HX8PLQhs.m1N.RwKwSfsWZgBaP9xc2NHre.NpjRu','ROLE_USER', 1);

INSERT INTO users(username,password,role,enabled) VALUES ('admin','$2a$12$U1BiFINvLOorRYhm7MuT/.aenz6K.1Iayrd194/2T1mUzj.XjvLLG','ROLE_ADMIN', 1);

Here the password related to ‘user’ is ‘userpass’ and password related to ‘admin’ is ‘adminpass’.I have used bcrypt password hashing technique to encode the passwords.

Project Structure

Project Structure

User class model

The model class that maps with the users table in the database. So create the User class with the following code:

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

@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String role;
private boolean enabled;

public User() {
super();
// TODO Auto-generated constructor stub
}

public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

UserRepository class

@Repository
public interface UserRepository extends CrudRepository<User, Long> {

@Query("SELECT u FROM User u WHERE u.username = :username")
public User getUserByUsername(@Param("username") String username);
}

Implement UserDetails

This class wraps an instance of User class, which is injected via constructor. And we override methods defined by the UserDetails interface, to be used by Spring Security in authentication process.

public class UserDetailsServices implements UserDetails {

private User user;

public UserDetailsServices(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(user.getRole());
return Arrays.asList(authority);
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

Implement UserDetailsService

This class makes use of an implementation of UserRepository, which will be created and injected by Spring Data JPA. And we override the loadUserByUsername() method to authentication the users.

public class UserDetailsServiceImplement implements UserDetailsService {

@Autowired
private UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userRepository.getUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("Could not find user");
}
return new UserDetailsServices(user);
}
}

Configure Authentication provider

To use Spring security with Spring Data JPA and Hibernate, we need to supply a DaoAuthenticationProvider which requires UserDetailsService and PasswordEncoder. In the configure(HttpSecurity) method, we specify that all requests must be authenticated (users must login), and use the default login and logout configuration provided by Spring Security.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

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

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

return authProvider;
}

private AuthenticationSuccessHandler authenticationSuccessHandler;

@Autowired
public WebSecurityConfig(AuthenticationSuccessHandler authenticationSuccessHandler) {
this.authenticationSuccessHandler = authenticationSuccessHandler;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers( "/css/**", "/images/**", "/favicon.ico").permitAll()
.antMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.successHandler(authenticationSuccessHandler)
.permitAll()
.and()
.logout()
.permitAll()
.and().csrf().disable();
}

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

View Pages with thymeleaf templates.

login.html

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

<head>
<title>Spring Security Login Form</title>
<meta charset="UTF-8">
<title>Login</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<body class="align">
<div class="grid">
<form th:action="@{/login}" method="post" class="form login">
<br/>
<div class="form__image">
<img src="images/company-logo.png" alt="mwhyte.dev" style='width:250px;height:250px'>
</div>

<div class="form__field">
<label for="login__username"><svg class="icon"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#user"></use></svg><span class="hidden">Username</span></label>
<input id="login__username" type="text" name="username" class="form__input" placeholder="Username" required>
</div>

<div class="form__field">
<label for="login__password"><svg class="icon"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#lock"></use></svg><span class="hidden">Password</span></label>
<input id="login__password" type="password" name="password" class="form__input" placeholder="Password" required>
</div>

<div class="text--center" th:if="${param.error}">
Invalid username and password.
</div>
<div class="text--center" th:if="${param.logout}">
You have been logged out.
</div>

<div class="form__field">
<input type="submit" value="Sign In">
</div>
</form>
</div>
<svg xmlns="http://www.w3.org/2000/svg" class="icons"><symbol id="arrow-right" viewBox="0 0 1792 1792">
<path d="M1600 960q0 54-37 91l-651 651q-39 37-91 37-51 0-90-37l-75-75q-38-38-38-91t38-91l293-293H245q-52 0-84.5-37.5T128 1024V896q0-53 32.5-90.5T245 768h704L656 474q-38-36-38-90t38-90l75-75q38-38 90-38 53 0 91 38l651 651q37 35 37 90z"/></symbol><symbol id="lock" viewBox="0 0 1792 1792"><path d="M640 768h512V576q0-106-75-181t-181-75-181 75-75 181v192zm832 96v576q0 40-28 68t-68 28H416q-40 0-68-28t-28-68V864q0-40 28-68t68-28h32V576q0-184 132-316t316-132 316 132 132 316v192h32q40 0 68 28t28 68z"/></symbol><symbol id="user" viewBox="0 0 1792 1792"><path d="M1600 1405q0 120-73 189.5t-194 69.5H459q-121 0-194-69.5T192 1405q0-53 3.5-103.5t14-109T236 1084t43-97.5 62-81 85.5-53.5T538 832q9 0 42 21.5t74.5 48 108 48T896 971t133.5-21.5 108-48 74.5-48 42-21.5q61 0 111.5 20t85.5 53.5 62 81 43 97.5 26.5 108.5 14 109 3.5 103.5zm-320-893q0 159-112.5 271.5T896 896 624.5 783.5 512 512t112.5-271.5T896 128t271.5 112.5T1280 512z"/></symbol></svg>

</body>
</body>
</html>

user.html

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

<head>
<title>mwhyte.dev - Welcome!</title>
<meta charset="UTF-8">
<title>Home</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet">
<!-- <link rel="stylesheet" href="css/style.css">-->
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>

<div sec:authorize="hasRole('ADMIN')">
This content is only shown to administrators.
</div>

<div sec:authorize="hasRole('USER')">
This content is only shown to users.
</div>

<br/>

<div>
User: <span sec:authentication="name">NOT FOUND</span>
Spring Roles: <span sec:authentication="principal.authorities">NOT FOUND</span>
</div>

<br/>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</body>
</html>

admin.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">

<head>
<title>codenerve.com - Welcome!</title>
<meta charset="UTF-8">
<title>Admin</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet">
<!--<link rel="stylesheet" href="css/style.css">-->
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>

<div>
Custom administrator page.
</div>


<br/>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</body>
</html>

Let’s run our application. Once the application is up and running, login to system by visiting http://localhost:8080/.

Here are some screen shots of running system.

Login page
User page
Admin page

You can find the complete code for this example on GitHub

Happy Coding !! Thank you !!

Do support our publication by following it

Also refer to the following articles

--

--

Amila Iroshan
The Fresh Writes

Software Engineer | Open Source Contributor | Tech Enthusiast