In this post, we will see how to create two login form in a login page using spring boot security in java. The spring boot security module is used to validate these two login forms in one login page. The multiple login forms can be added as like the example explained here.
In many application, we need to handle two different group of users to login from a default login page. There should be two different login entry points in this page. In this case, we need to create two login form in a single login page. One login form is used for the regular customers. the another login form is for administrators or company employees.
In this post, we address two login form in a login page design. The step by step procedure to create the two login form with screenshots are added.
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.
The following xml shows the actual pom.xml file. Please note that tomcat dependence is also added along with this.
<?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.7.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.yawintutor</groupId>
<artifactId>Spring-Application</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringBootSecurityMultipleForm</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-security</artifactId>
</dependency>
<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.springframework.security</groupId>
<artifactId>spring-security-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-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 SpringBootSecurityMultipleFormApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSecurityMultipleFormApplication.class, args);
}
}
Spring Boot Controller Layer
The controller class contains all the method that invokes jsp pages. The login page is same for both admin and application user. After the successful login, admin login will redirect to admin dashboard. The user login will redirect to users dashboard.
src/main/java/com/yawintutor/TestController.java
package com.yawintutor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping
public class TestController {
@RequestMapping("/")
public ModelAndView defaultHome() {
return new ModelAndView("login");
}
@RequestMapping("/login")
public ModelAndView login() {
return new ModelAndView("login");
}
@RequestMapping("/dashboard")
public ModelAndView userDashboard() {
return new ModelAndView("dashboard");
}
@RequestMapping("/admin/")
public ModelAndView admin() {
return new ModelAndView("login");
}
@RequestMapping("/admin/login")
public ModelAndView adminlogin() {
return new ModelAndView("login");
}
@RequestMapping("/admin/dashboard")
public ModelAndView admindashboard() {
return new ModelAndView("admin/dashboard");
}
}
Spring Boot JSP Layer
The login jsp file contains two login form, the user login form is used for the normal user of the application. The second login form is for admin users. The admin privileged users can login to this form.
src/main/webapp/WEB-INF/jsp/login.jsp
<table width="100%" border=1>
<tr>
<td width=50%>
<center>
<h1>Welcome to Admin Spring Boot Security</h1>
<% if("true".equals(request.getParameter("adminerror"))) {%>
<h2 style="color:red;">Invalid username / password</h2>
<% } %>
<form method="POST" action="/admin/login">
User Name : <input type="text" name="username" value="admin"/><br><br>
Password : <input type="password" name="password" value="password"/><br><br>
<input type="submit" name="submit"/>
</form>
</center>
</td>
<td width=50%>
<center>
<h1>Welcome to Spring Boot Security</h1>
<% if("true".equals(request.getParameter("usererror"))) {%>
<h2 style="color:red;">Invalid username / password</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>
</td>
</tr>
</table>
src/main/webapp/WEB-INF/jsp/dashboard.jsp
<center>
<h1>Welcome to Spring Boot User DashBoard</h1>
<h2>You are in Spring Boot User DashBoard Page</h2>
<br><a href="/logout">logout</a>
</center>
src/main/webapp/WEB-INF/jsp/admin/dashboard.jsp
<center>
<h1>Welcome to Spring Boot Admin DashBoard</h1>
<h2>You are in Spring Boot Admin DashBoard Page</h2>
<br><a href="/logout">logout</a>
</center>
Spring Boot Application.properties Configurations
The application.properties file contains configuration for the jsp files and the mysql database configurations. This example uses mysql database to validate the user and admin credentials. In the application.properties change the database details according to your environment.
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 Admin Security Configuration
Create an admin security configuration java file to extend the WebSecurityConfigurerAdapter class. In the login page, if an admin user tries to login /admin/login url is called, this admin security configuration will validate the request. The url is configured as “/admin/**.” When you log in successfully, the admin page is redirected to “/admin/dashboard”.
The @order(1) annotation will inform the security of the spring boot to validate this java configuration class first.
src/main/java/com/yawintutor/SpringBootAdminSecurityConfiguration.java
package com.yawintutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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
@Order(1)
public class SpringBootAdminSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
UserDetailsService adminDetailsServiceImpl;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(adminDetailsServiceImpl).passwordEncoder(bCryptPasswordEncoder);
// Use this code for inmemeory database authentication
//auth.inMemoryAuthentication()
// .withUser("admin").password("{noop}password").roles("ADMIN");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.authorizeRequests().anyRequest().authenticated()
.and().formLogin().loginPage("/admin/login")
.defaultSuccessUrl("/admin/dashboard", true)
.failureUrl("/admin/login?adminerror=true")
.permitAll()
.and().logout().logoutUrl("/admin/logout").logoutSuccessUrl("/admin/login");
http.csrf().disable();
}
}
Spring Boot User Security Configuration
Create another Java security configuration file for the regular user of the application. The regular user will invoke the login page using the default “http:/localhost:8080/” url. The user login page “http:/localhost:8080/login” will be shown to the user if the user is not authenticated. After successful login, the user is redirected to the “http:/localhost:8080/dashboard” user dashboard page.
The @Order(2) annotation tells the security of the spring boot to invoke this security configuration after the admin configuration.
src/main/java/com/yawintutor/SpringBootUserSecurityConfiguration.java
package com.yawintutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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
@Order(2)
public class SpringBootUserSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
UserDetailsService userDetailsServiceImpl;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(bCryptPasswordEncoder);
// use this code for inmemory database authentication
//auth.inMemoryAuthentication()
// .withUser("user").password("{noop}password").roles("USER");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests().anyRequest().authenticated()
.and().formLogin().loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
.failureUrl("/login?usererror=true")
.permitAll()
.and().logout().logoutSuccessUrl("/login");
http.csrf().disable();
}
}
Spring Boot Admin Details Service Class
In the spring boot service layer, create a service class with annotation @Service for admin login by implementing spring boot security interface UserDetailsService. In the service class, the username is sent to the JPA repository class and find the admin user details from the database. A UserDetails class object is created from the admin user and sent to the admin spring boot security configurations file.
src/main/java/com/yawintutor/AdminDetailsServiceImpl.java
package com.yawintutor;
import java.util.ArrayList;
import java.util.List;
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 AdminDetailsServiceImpl implements UserDetailsService {
@Autowired
private UsersRepository usersRepository;
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
List<Users> usersList = usersRepository.findAdmin(userName);
if (usersList != null && usersList.size() == 1) {
Users users = usersList.get(0);
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 User Details Service Class
In the spring boot service layer, create a service class with annotation @Service for user login by implementing spring boot security interface UserDetailsService. In the service class, the username is sent to the JPA repository class and find the user details from the database. A UserDetails class object is created from the database user and sent to the spring boot security configurations file.
src/main/java/com/yawintutor/UserDetailsServiceImpl.java
package com.yawintutor;
import java.util.ArrayList;
import java.util.List;
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 {
List<Users> usersList = usersRepository.findUser(userName);
if (usersList != null && usersList.size() == 1) {
Users users = usersList.get(0);
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 Bean Configuration
The spring boot bean configuration loads the BCryptPasswordEncoder for both admin and user validation.
src/main/java/com/yawintutor/SpringBootConfiguration.java
package com.yawintutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SpringBootConfiguration {
@Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
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. Two API is created in JPA Repository class, findUser() and findAdmin(). The findUser() api gets the user data from mysql database and sends back to UserDetailsServiceImpl class. The findAdmin() api gets the admin user data from mysql database and sends back to AdminDetailsServiceImpl class.
src/main/java/com/yawintutor/UsersRepository.java
package com.yawintutor;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface UsersRepository extends JpaRepository<Users, Integer>{
@Query("SELECT u FROM Users u join u.roles r WHERE u.username = :username and r.roleName='USER'")
public List<Users> findUser(@Param("username") String username);
@Query("SELECT u FROM Users u join u.roles r WHERE u.username = :username and r.roleName='ADMIN'")
public List<Users> findAdmin(@Param("username") String username);
}