Spring Boot Security Database Authentication using UserDetailsService Example

In spring boot security, we see the database authentication and authorization using UserDetailsService in this example. The UserDetailsService is configured in spring boot security configuration class WebSecurityConfigurerAdapter. The mysql database, spring boot MVC, spring boot security, tomcat web server and jsp files are used to authenticate and authorize the user using the spring boot security module.

The step by step procedure is explained how to configure the spring boot security with mysql database for the user authentication and autherization.



Preparing Database Tables

There are three tables required for authenticating spring boot security. The three tables are USERS, ROLE and USER_ROLE. The Users table contains user related details such as username, password etc. The role table contains the roles supported in the spring boot security. The users and role table has many to many relationship using user_role table. The mysql database is used in this example. The sql is as below.

In the users table only username and password are important columns, rest of the columns are optional. These columns can be removed if it is not required for your application.

drop table users_role;
drop table role;
drop table users;

create table users(
user_id int primary key AUTO_INCREMENT,
username varchar(50) unique not null,
password varchar(50) not null,
disabled boolean default false,
account_expired boolean default false,
account_locked boolean default false,
credentials_expired boolean default false
);

create table role (
role_id integer primary key AUTO_INCREMENT,
role_name varchar(50)
);

create table user_role (
user_role_id integer primary key AUTO_INCREMENT,
user_id integer references users(user_id),
role_id integer references role(role_id)
);


Preparing Database Table Data

The below sql will provide the sample data in the database table. This data is used to test the spring boot security in this example. The password is not encrypted in this example. The encryption code is modified to insert the encrypted password.

insert into role (role_name) values('ADMIN');
insert into role (role_name) values('USER');

insert into users (username, password) values('admin', 'password');
insert into users (username, password) values('user', 'password');

insert into user_role (user_id, role_id) values(1,1);
insert into user_role (user_id, role_id) values(2,2);


Create project and maven dependency

In the spring boot, create a spring boot security project. Add the maven dependency spring-boot-starter-web and tomcat-embed-jasper for the spring boot web application with MVC module. Add the maven dependency spring-boot-starter-security to enable the spring boot security module. Add the maven dependency spring-boot-starter-data-jpa and mysql-connector-java for database connection using JPA and mysql database.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.4.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.yawintutor</groupId>
	<artifactId>Spring-Application</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SpringBootSecuritySimple</name>
	<description>Spring Boot Project</description>

	<properties>
		<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-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
		</dependency>

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


Spring Boot Main Class

The spring boot main class is created while creating the project. The default spring boot main class is shown as below.

package com.yawintutor;

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

@SpringBootApplication
public class SpringBootSecuritySimpleApplication {

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


Spring Boot Controller Layer

Create a controller class for the spring boot security application. The controller class will receive the http request from the browser, process the request and send the response back to the browser.

Create a class of controllers as shown below. Add two methods, the default method that shows a page after the user has been authenticated. The second page of the page is a login page. If the user is not logged in, the request will be forwarded to this login page.

package com.yawintutor;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping
public class TestController {

	@RequestMapping("/")
	public ModelAndView defaultHome() {
		return new ModelAndView("home");
	}

	@RequestMapping("/login")
	public ModelAndView login() {
		return new ModelAndView("login");
	}
}


Spring Boot JSP Layer

Two jsp files are configured in this application, home.jsp and login.jsp. The login page contains the login username and password textbox and login button. On click of the login button, the jsp page sends request to the controller and process it. After successful login, the page is redirected too home.jsp. The home.jsp contains the login success message.

src/main/webapp/WEB-INF/jsp/home.jsp

<center>
<h1>Welcome to Spring Boot Simple Security Example</h1>
<a href="/logout">logout</a>
</center>

src/main/webapp/WEB-INF/jsp/login.jsp

<center>
<h1>Welcome to Spring Boot Security</h1>

<h2>Login Page</h2>

<form method="POST" action="/login">
	User Name : <input type="text" name="username" value="user"/><br><br>
	Password  : <input type="password" name="password" value="password"/><br><br>
	<input type="submit" name="submit"/>
</form>
</center>


Spring Boot Application.properties Configurations

The application.properties file contains two set of properties. The first set is the configuration for the jsp files. The second set contains the mysql database connection configurations.

application.properties

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

spring.datasource.url=jdbc:mysql://localhost/testdb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update


Spring Boot Security Configurations

Create a Java class to configure security settings. The spring boot security configuration class must be extended to the WebSecurityConfigurerAdapter class. This class allows the user details to be configured. This example uses the mysql database to configure user details. User authorization and authentication details are provided in this custom class. The user will be authenticated on the basis of this security configuration.

SpringBootSecurityConfiguration.java

package com.yawintutor;

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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class SpringBootSecurityConfiguration extends WebSecurityConfigurerAdapter {
	
	@Autowired
	BCryptPasswordEncoder bCryptPasswordEncoder;
	
	@Autowired
	UserDetailsService userDetailsService;
	
	@Override
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
	}

	@Override
	public void configure(HttpSecurity http) throws Exception {
		http 
			.csrf() 
			.disable()
			.authorizeRequests()
			.antMatchers("/**").hasAnyRole("USER")
			.and()
			.formLogin().loginPage("/login").permitAll();

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


Spring Boot Service Layer

In the spring boot service layer, add a @Service class. The UserDetailsServiceImpl class is created by implementing spring boot interface UserDetailsService. The annotation @Service is used to make this class as service.

UserDetailsServiceImpl.java

package com.yawintutor;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UsersRepository usersRepository;
	
    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;
	
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        Optional<Users> optionalUser = usersRepository.findByUsername(userName);
        if(optionalUser.isPresent()) {
        	Users users = optionalUser.get();
        	
        	List<String> roleList = new ArrayList<String>();
        	for(Role role:users.getRoles()) {
        		roleList.add(role.getRoleName());
        	}
        	
            return User.builder()
            	.username(users.getUsername())
            	//change here to store encoded password in db
            	.password( bCryptPasswordEncoder.encode(users.getPassword()) )
            	.disabled(users.isDisabled())
            	.accountExpired(users.isAccountExpired())
            	.accountLocked(users.isAccountLocked())
            	.credentialsExpired(users.isCredentialsExpired())
            	.roles(roleList.toArray(new String[0]))
            	.build();
        } else {
        	throw new UsernameNotFoundException("User Name is not Found");
        }   
    }
}


Spring Boot JPA Repository Layer

In the spring boot JPA Repository layer, create a User repository to get data from the database and authenticate the user information. The user repository get the data from mysql database and sends back to UserDetailsServiceImpl class.

UsersRepository.java

package com.yawintutor;

import java.util.Optional;

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

public interface UsersRepository extends JpaRepository<Users, Integer>{
	Optional<Users> findByUsername(String username);
}


Spring Boot Entity Layer

In this example, three tables are used for the user authentication. The users table contains the user related information. The role table contains the spring boot authentication roles. The user_role is the linking table that provides many to many relationship between users and role table. The below entity classes contains the entity mapping of the database tables.

Users.java

package com.yawintutor;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;

@Entity
public class Users {
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Id
	private long userId;
	private String username;
	private String password;
	private boolean disabled;
	private boolean accountExpired;
	private boolean accountLocked;
	private boolean credentialsExpired;

	@ManyToMany(fetch = FetchType.EAGER)
	@JoinTable(name = "user_role", 
		joinColumns = @JoinColumn(name = "user_id"), 
		inverseJoinColumns = @JoinColumn(name = "role_id"))
	List<Role> roles;

	public long getUserId() {
		return userId;
	}

	public void setUserId(long userId) {
		this.userId = userId;
	}

	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 boolean isDisabled() {
		return disabled;
	}

	public void setDisabled(boolean disabled) {
		this.disabled = disabled;
	}

	public boolean isAccountExpired() {
		return accountExpired;
	}

	public void setAccountExpired(boolean accountExpired) {
		this.accountExpired = accountExpired;
	}

	public boolean isAccountLocked() {
		return accountLocked;
	}

	public void setAccountLocked(boolean accountLocked) {
		this.accountLocked = accountLocked;
	}

	public boolean isCredentialsExpired() {
		return credentialsExpired;
	}

	public void setCredentialsExpired(boolean credentialsExpired) {
		this.credentialsExpired = credentialsExpired;
	}

	public List<Role> getRoles() {
		return roles;
	}

	public void setRoles(List<Role> roles) {
		this.roles = roles;
	}

}

Role.java

package com.yawintutor;

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

@Entity
public class Role {
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Id
	private int roleId;
	private String roleName;

	public int getRoleId() {
		return roleId;
	}

	public void setRoleId(int roleId) {
		this.roleId = roleId;
	}

	public String getRoleName() {
		return roleName;
	}

	public void setRoleName(String roleName) {
		this.roleName = roleName;
	}
}


Run the application

Run the spring boot security application after the configuration class has been added. The spring boot application will start the tomcat and run on port 8080. Call the default http:/localhost:8080 url from one of the browser windows. The default url will point to the home.jsp file. The default url is redirected to login.jsp file as the security module is configured.

Username : user
Password : password

After entering the valid username and password, the user can log in to the spring boot application. User can click the log out link to sign off the application.



Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *