Spring Form Based Authentication
The example Spring Boot Security form based JDBC authentication using UserDetailsService will show you how to use custom login form with Spring’s j_spring_security_check
to authenticate a user. You may also look into form based JDBC authentication using UserDetailsService on Spring MVC framework. In this example, I will implement the form based authentication system using Spring Boot framework.
Prerequisites
Java 1.8+, Gradle 5.4.1/Maven 3.8.5, Spring Boot 2.1.6/2.7.4
Go through the below sections to implement Spring Boot Security form based JDBC authentication using UserDetailsService.
Project Setup
Create a gradle or maven based project in your favorite IDE or tool. The project name is given as spring-boot-security-form-based-jdbc-userdetailsservice-auth.
For maven based project, you can use the following pom.xml file for your project configurations:
<?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>com.roytuts</groupId>
<artifactId>spring-boot-security-form-based-jdbc-userdetailsservice-auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
</parent>
<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-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Once the gradle project gets created, open build.gradle script in editor and update to include the required dependencies.
I have added Spring Boot web starter, security starter for our web application with security implementation.
I have also added jasper and jstl as I will use jsp pages as a view technology in our application.
I have included here H2 in-memory database to make our application quickly build. You can also use other database server for your application.
buildscript {
ext {
springBootVersion = '2.1.6.RELEASE'
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
implementation("org.springframework.boot:spring-boot-starter-security:${springBootVersion}")
implementation("org.springframework:spring-jdbc:5.1.8.RELEASE")
implementation('org.apache.tomcat.embed:tomcat-embed-jasper:9.0.22')
implementation('javax.servlet:jstl:1.2')
runtime("com.h2database:h2:1.4.196")
}
Configuring View Resolvers
Add application.properties file under src/main/resources to configure view resolvers. You can also create bean as a view resolver but specifying in the application.properties is fair enough for the Spring Boot application.
So I will put the jsp files under webapp/views folder and the jsp file’s extension is .jsp.
spring.mvc.view.prefix=/views/
spring.mvc.view.suffix=.jsp
SQL Scripts
In our application I am going to authenticate users using Spring JDBC. So I need to create SQL scripts for creating tables and inserting some data to test our application.
Table – user
The below table stores user information. The SQL script user.sql is kept under src/main/resources folder.
/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`user_name` varchar(30) NOT NULL,
`user_pass` varchar(255) NOT NULL,
`enable` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`user_name`)
);
/*Data for the table `user` */
insert into `user`(`user_name`,`user_pass`,`enable`) values ('admin','$2a$10$dl8TemMlPH7Z/mpBurCX8O4lu0FoWbXnhsHTYXVsmgXyzagn..8rK',1);
If you find any issue while creating table in the database then you can use simply tinyint
instead of tinyint(1)
in the above table creation script.
/*Table structure for table `user` */
CREATE TABLE IF NOT EXISTS `user` (
`user_name` varchar(30) NOT NULL,
`user_pass` varchar(255) NOT NULL,
`enable` tinyint NOT NULL DEFAULT '1',
PRIMARY KEY (`user_name`)
);
/*Data for the table `user` */
insert into `user`(`user_name`,`user_pass`,`enable`) values ('admin','$2a$10$dl8TemMlPH7Z/mpBurCX8O4lu0FoWbXnhsHTYXVsmgXyzagn..8rK',1);
Table – user_role
The below table stores user information. The SQL script user_role.sql is kept under src/main/resources folder.
/*Table structure for table `user_role` */
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`user_name` varchar(30) NOT NULL,
`user_role` varchar(15) NOT NULL,
FOREIGN KEY (`user_name`) REFERENCES `user` (`user_name`)
);
/*Data for the table `user_role` */
insert into `user_role`(`user_name`,`user_role`) values ('admin','ROLE_ADMIN');
Database Config Class
I need to create data source for Spring JDBC API to work with the database. Note that I don’t need to pass database credentials to connect to the database as Spring Boot will connect to default instance with default credentials of H2 database.
As I am not using Datasource
into our Security Config class (later in this tutorial), so I am creating a bean for JdbcTemplate
to fetch data from the database.
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.H2) // .H2 or .DERBY, etc.
.addScript("user.sql").addScript("user-role.sql").build();
return db;
}
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
}
Model Class
I need to create a model class that will map Java object to database table.
public class User {
private String username;
private String password;
private String role;
public User() {
}
public User(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
}
//getters and setters
}
RowMapper Class
I have created a model class in the above section but I need to map model class attribute to table column. Therefore I will do it through RowMapper class.
public class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return new User(rs.getString("user_name"), rs.getString("user_pass"), rs.getString("user_role"));
}
}
DAO Class
Now I need to interact with database to perform fetch operation for a user details.
The below method inside the below class fetches the user details from the database for a given username.
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public User getUser(final String username) {
return jdbcTemplate.queryForObject(
"select u.user_name user_name, u.user_pass user_pass, ur.user_role user_role from user u, user_role ur where u.user_name = ? and u.user_name = ur.user_name",
new String[] { username }, new UserRowMapper());
}
}
For Spring Boot version 2.7.4, replace the above code (above format of the queryForObject()
method has been deprecated) with the following code:
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public User getUser(final String username) {
return jdbcTemplate.queryForObject(
"select u.user_name user_name, u.user_pass user_pass, ur.user_role user_role from `user` u, user_role ur where u.user_name = ? and u.user_name = ur.user_name",
new UserRowMapper(), username);
}
}
Service Class
I will create a service class that will implement Spring’s UserDetailsService
interface to override the method with our own implementation.
This method will fetch user details from the database and map to Spring’s UserDetails
object to authenticate the user.
@Service
public class UserAuthService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.getUser(username);
if (user == null) {
throw new UsernameNotFoundException("User '" + username + "' not found.");
}
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(user.getRole());
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
Arrays.asList(grantedAuthority));
}
}
Security Config Class
I need to create Java config class to configure Spring Security for our form based authentication.
I have put comments after each line in the below Spring Security configuration class to understand it clearly.
The below WebSecurityConfigclass, which is responsible for all security configurations, extends WebSecurityConfigurerAdapter and overrides configure(HttpSecurity http) and authenticationManager() methods. I have also ignored security for static resources such as js, css, images etc that have path /static**
, root path /
, /login
, /error
. I have overridden method configure(HttpSecurity http) to apply security for all URLs including the URL which is having pattern /admin with role as ADMIN.
Here, I have implemented JDBC authentication using Spring’s UserDetailsService
API unlike the application Spring Boot Security form based JDBC authentication, where I have used only JDBC authentication using Datasource. You can also apply LDAP or any other third party API to authenticate your application users.
I have used PasswordEncoder because plain text password is not acceptable in current version of Spring Security and you will get below exception if you do not use PasswordEncoder.
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:244)
As the passwords are in encrypted format in the below class, so you won’t find it easier until I tell you. The password for admin is admin.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserAuthService userAuthService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userAuthService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http// ,
.authorizeRequests()// , authorize request
.antMatchers("/", "/login", "/static/**", "/error**").permitAll().anyRequest().authenticated()// ,
// ignore
// /,login
// page,static
// resources, error
// pages
.antMatchers("/admin")// Ensures that request with "/admin" to
// our application requires the user to
// be authenticated
.access("hasRole('ADMIN')")// Any URL that starts with
// "/admin" will
// be restricted to users who have the
// role "ROLE_ADMIN",
.and()// ,
.formLogin()// Allows users to authenticate with form based
// login,
.loginPage("/login")// specifies the location of the log in
// page,
.loginProcessingUrl("/j_spring_security_check")// login
// processing
// URL,
.defaultSuccessUrl("/admin")// default-target-url,
.failureUrl("/login?error")// authentication-failure-url,
.usernameParameter("username")// overrides Spring's default
// j_username with
// username-parameter,
.passwordParameter("password");// overrides Spring's default
// j_password with
// password-parameter
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
For Spring Boot version 2.7.4, use the following piece of code:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private UserAuthService userAuthService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userAuthService).passwordEncoder(passwordEncoder);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http// ,
.authorizeHttpRequests()// , authorize request
.antMatchers("/", "/login", "/static/**", "/error**").permitAll()// ,
// ignore
// /,login
// page,static
// resources, error
// pages
.antMatchers("/admin")// Ensures that request with "/admin" to
// our application requires the user to
// be authenticated
.hasRole("ADMIN")// Any URL that starts with
// "/admin" will
// be restricted to users who have the
// role "ROLE_ADMIN",
.and()// ,
.formLogin()// Allows users to authenticate with form based
// login,
.loginPage("/login")// specifies the location of the log in
// page,
.loginProcessingUrl("/j_spring_security_check")// login
// processing
// URL,
.defaultSuccessUrl("/admin")// default-target-url,
.failureUrl("/login?error")// authentication-failure-url,
.usernameParameter("username")// overrides Spring's default
// j_username with
// username-parameter,
.passwordParameter("password");// overrides Spring's default
// j_password with
// password-parameter
return http.build();
}
}
Also you need a separate password encoder configuration otherwise, it will throw circular dependency error:
@Configuration
public class EncoderConfig {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
REST Controller
Now create below REST Controller class to test Spring Boot Security form based authentication.
I have defined few end-points in the below REST controller class, where the path /
and /login
do not need to be authenticated and only /admin
needs to be authenticated.
@Controller
public class SpringSecurityController {
@GetMapping("/")
public String defaultPage(Model model) {
model.addAttribute("msg", "Welcome to Spring Security");
return "index";
}
@GetMapping("/login")
public String loginPage(Model model, @RequestParam(value = "error", required = false) String error,
@RequestParam(value = "logout", required = false) String logout) {
if (error != null) {
model.addAttribute("error", "Invalid Credentials");
}
if (logout != null) {
model.addAttribute("msg", "You have been successfully logged out");
}
return "login";
}
@GetMapping("/logout")
public String logoutPage(Model model, HttpServletRequest request) {
request.getSession().invalidate();
return "redirect:/login?logout";
}
@GetMapping("/admin")
public String adminPage(Model model) {
model.addAttribute("title", "Administrator Control Panel");
model.addAttribute("message", "This page demonstrates how to use Spring security");
return "admin";
}
}
View Files
I will create three view files, i.e., jsp pages for path – /
, /login
and /admin
.
Path – /
Create index.jsp file under src/main/webapp/views folder with the below content.
It will just show you home page that has a link to go to the Admin page.
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Spring Security Basic - Form Based JDBC Authentication</title>
</head>
<body>
<div align="center">
<h1>Home Page</h1>
<a href="${pageContext.request.contextPath}/admin">Go to Administrator page</a>
</div>
</body>
</html>
Path – /login
Create a file login.jsp under src/main/webapp/views folder with below content.
This page shows you login form when you click on Go to Administrator page link on home page.
In this page you need to put admin/admin
as credentials to go to the admin page otherwise you will get login error.
<%@ page language="java" session="true"
contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>Spring Security Basic - Form Based JDBC Authentication</title>
<link rel="stylesheet" type="text/css"
href="<c:url value="/static/css/style.css"/>" />
</head>
<body>
<div id="login-box">
<h2>Login Here</h2>
<c:if test="${not empty error}">
<div class="error">${error}</div>
</c:if>
<c:if test="${not empty msg}">
<div class="msg">${msg}</div>
</c:if>
<form name='loginForm'
action="<c:url value='j_spring_security_check' />" method='POST'>
<table>
<tr>
<td>User:</td>
<td><input type='text' name='username' value=''></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='password' /></td>
</tr>
<tr>
<td colspan='2'><input name="submit" type="submit"
value="Submit" /></td>
</tr>
</table>
<input type="hidden" name="${_csrf.parameterName}"
value="${_csrf.token}" />
</form>
</div>
</body>
</html>
Path – /admin
Create a file admin.jsp page under src/main/webapp/views folder with below content.
This page shows admin page once you successfully logged in to the application using login page.
In this page you will see your username and logout link with some message. On clicking on the logout link you will be redirected to the login page with successful message.
<%@ page language="java" session="true"
contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Spring Security Basic - Form Based JDBC Authentication</title>
</head>
<body>
<div align="center">
<h1>${title}</h1>
<h2>${message}</h2>
<c:if test="${pageContext.request.userPrincipal.name != null}">
<h2>
Welcome
: ${pageContext.request.userPrincipal.name} | <a
href="<c:url value='logout'/>">Logout</a>
</h2>
</c:if>
</div>
</body>
</html>
Applying Style
Now I apply some basic style for our pages. So create below style.css file under src/main/webapp/static/css folder.
.error {
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}
.msg {
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
}
#login-box {
width: 500px;
padding: 20px;
margin: 50px auto;
background: #fff;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border: 1px solid #000;
}
Spring Boot Main Class
Creating a main class would be sufficient to deploy Spring Boot application into the Tomcat server. This is a great advantage that you just need to let Spring know that it is your Spring Boot Application using @SpringBootApplication
and main class.
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Deploying the Application
Just run the above main class to deploy the application into embedded Tomcat server. The server will start on default port 8080.
Testing the Application
The home page will show the following page:
Login page will appear with the following form:
Invalid credentials:
For valid credentials, you will see the following page:
Hope you got an idea how to authenticate user based on his/her role using form based authentication in Spring security.