Extra Login Fields with Spring Security

In many applications, the default login mechanism of the Spring Security which usually requires just the username and password sometimes it may not be sufficient. An application might need the additional information for the authentication such as the users role, department or the security question. Adding the extra fields to the login form can provides the more robust and customized authentication mechanism.

In this article, we will discuss through the steps to required to add the extra fields to the login form in the Spring Security project. We will use name and qualification as additional fields and we can demonstrate how to handle these fields during the authentication process.

The main concept in adding the extra login fields to the Spring Security project can involves around the customizing the authentication process to the include additional information beyond the default username and password. It involves creating the custom authentication provider and the custom web authentication details class, configuring the Spring Security to use these components and updating the login form to include the extra fields.

Implementation of the Extra Login Fields with Spring Security

Below are the steps to implement extra login fields in Spring Security.

Step 1: Create a new Spring Boot project using Spring Initializr and include the required dependencies as mentioned below:

  • Spring Web
  • Spring Security
  • Lombok
  • Spring DevTools
  • Thymeleaf

After the project creation done, the folder structure will be like the below image:.


Step 2: Open the application.properties file and add the configuration for the security username and password, database, thymeleaf of the Spring Security application in the project.

spring.application.name=spring-security-login-extra-fields

# MySQL Database configuration for production (example)
spring.datasource.url=jdbc:mysql://localhost:3306/example
spring.datasource.username=root
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

spring.main.allow-circular-references=true

app.user.username=maheshkm5001
app.user.password=Mahesh@123
app.user.name=Mahesh
app.user.qualification=B.Tech


Step 3: Create a new package named model and in that package, create a new Java class named User.

Go to src > org.example.springsecurityloginextrafields > model > User and put the below code.

Java
package org.example.springsecurityloginextrafields.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;
    private String name;
    private String qualification;
}


Step 4: Create a new package named repository and in that package, create new Java class named UserRepository.

Go to src > org.example.springsecurityloginextrafields > repository > UserRepository and put the below code.

Java
package org.example.springsecurityloginextrafields.repository;

import org.example.springsecurityloginextrafields.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}


Step 5: Create a new package named service and in that package, create a new Java class named UserService.

Go to src > org.example.springsecurityloginextrafields > service > UserService and put the below code.

Java
package org.example.springsecurityloginextrafields.service;

import org.example.springsecurityloginextrafields.model.User;

import org.springframework.stereotype.Service;


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

@Service
public class UserService {

    private List<User> users = new ArrayList<>();

    public void saveUser(User user) {
        users.add(user);
    }

    public User findByUsername(String username) {
        return users.stream()
                .filter(user -> user.getUsername().equals(username))
                .findFirst()
                .orElse(null);
    }
}


Step 6: Create a new package named service and in that package, create the new Java class named CustomerUserDetailsService.

Go to src > org.example.springsecurityloginextrafields > service > CustomerUserDetailsService and put the below code.

Java
package org.example.springsecurityloginextrafields.service;


import org.example.springsecurityloginextrafields.model.User;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.ArrayList;



@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
    {
        User user = userService.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>());
    }
}


Step 7: Create a new package named config and in that package, create the new Java class and it named as SecurityConfig.

Go to src > org.example.springsecurityloginextrafields > config > SecurityConfig and put the below code.

Java
package org.example.springsecurityloginextrafields.config;

import org.example.springsecurityloginextrafields.fliter.CustomAuthenticationFilter;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import org.springframework.security.web.SecurityFilterChain;

import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .requestMatchers("/login", "/perform_login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/perform_login")
                .defaultSuccessUrl("/home", true)
                .permitAll()
                .and()
                .addFilterBefore(customAuthenticationFilter(authenticationManager(http.getSharedObject(AuthenticationConfiguration.class))), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public CustomAuthenticationFilter customAuthenticationFilter(AuthenticationManager authenticationManager) throws Exception {
        CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager);
        return filter;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
}


Step 8: Create a new package and it named as the config and in that package, create the new Java class and it named as CustomAuthenticationProvider.

Go to src > org.example.springsecurityloginextrafields > config > CustomAuthenticationProvider and put the below code.

Java
package org.example.springsecurityloginextrafields.config;



import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
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.Component;




@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsService userDetailsService;

    public CustomAuthenticationProvider(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();

        UserDetails user = userDetailsService.loadUserByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }

        // Validate extra fields
        HttpServletRequest request = ((HttpServletRequest) authentication.getDetails());
        String name = (String) request.getSession().getAttribute("name");
        String qualification = (String) request.getSession().getAttribute("qualification");

        if (name == null || qualification == null) {
            throw new BadCredentialsException("Name or Qualification is missing");
        }

        // Implement your validation logic for name and qualification
        // For demonstration, we assume they must not be empty
        if (name.isEmpty() || qualification.isEmpty()) {
            throw new BadCredentialsException("Invalid name or qualification");
        }

        return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}


Step 9: Create a new package named filter and in that package. create the new Java class and it named as CustomAuthenticationFilter.

Go to src > org.example.springsecurityloginextrafields > filter > CustomAuthenticationFilter and put the below code.

Java
package org.example.springsecurityloginextrafields.filter;


import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


import java.io.IOException;

public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String name = request.getParameter("name");
        String qualification = request.getParameter("qualification");

        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

        // Store extra fields in a way accessible to AuthenticationProvider
        request.getSession().setAttribute("name", name);
        request.getSession().setAttribute("qualification", qualification);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        super.successfulAuthentication(request, response, chain, authResult);
    }
}


Step 10: Create a new package named controller and in that package, create the new Java class and it named as UserController.

Go to src > org.example.springsecurityloginextrafields > controller > UserController and put the below code.

Java
package org.example.springsecurityloginextrafields.controller;


import org.example.springsecurityloginextrafields.model.User;
import org.example.springsecurityloginextrafields.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;



import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/register")
    public String showRegistrationForm() {
        return "register";
    }

    @PostMapping("/register")
    public String registerUser(@RequestParam("username") String username,
                               @RequestParam("password") String password,
                               @RequestParam("confirmPassword") String confirmPassword,
                               @RequestParam("name") String name,
                               @RequestParam("qualification") String qualification) {

        if (!password.equals(confirmPassword)) {
            // Handle password mismatch
            return "redirect:/register?error=passwordsDoNotMatch";
        }

        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        user.setName(name);
        user.setQualification(qualification);

        userService.saveUser(user);

        return "redirect:/welcome";
    }

    @GetMapping("/login")
    public String showLoginForm() {
        return "login";
    }
}


Step 11: Open the main class and put the below code. (No change are required)

Java
package org.example.springsecurityloginextrafields;

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

@SpringBootApplication
public class SpringSecurityLoginExtraFieldsApplication {

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

}


Step 12: Create the Register HTML page.

Go to src > main > resources > templates > register.html and put the below code.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Registration Page</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f8f9fa;
        }

        .container {
            margin-top: 50px;
            display: flex;
            justify-content: center;
        }

        .card {
            width: 100%;
            max-width: 500px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            border-radius: 8px;
            background-color: #fff;
        }

        .card-header {
            background-color: #007bff;
            color: #fff;
            border-bottom: none;
            border-radius: 8px 8px 0 0;
            padding: 15px;
            text-align: center;
        }

        .card-body {
            padding: 25px;
        }

        .form-label {
            margin-bottom: 10px;
            font-weight: bold;
        }

        .form-control {
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border: 1px solid #ced4da;
            border-radius: 4px;
            font-size: 16px;
        }

        .btn-primary {
            background-color: #007bff;
            border: none;
            color: #fff;
            padding: 12px;
            font-size: 16px;
            border-radius: 4px;
            cursor: pointer;
            width: 100%;
        }

        .btn-primary:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="card">
        <div class="card-header">
            <h4>Register</h4>
        </div>
        <div class="card-body">
            <form method="post" action="/register">
                <div class="mb-3">
                    <label for="username" class="form-label">Username:</label>
                    <input type="text" class="form-control" id="username" name="username" required>
                </div>
                <div class="mb-3">
                    <label for="password" class="form-label">Password:</label>
                    <input type="password" class="form-control" id="password" name="password" required>
                </div>
                <div class="mb-3">
                    <label for="confirmPassword" class="form-label">Confirm Password:</label>
                    <input type="password" class="form-control" id="confirmPassword" name="confirmPassword" required>
                </div>
                <div class="mb-3">
                    <label for="name" class="form-label">Name:</label>
                    <input type="text" class="form-control" id="name" name="name" required>
                </div>
                <div class="mb-3">
                    <label for="qualification" class="form-label">Qualification:</label>
                    <input type="text" class="form-control" id="qualification" name="qualification" required>
                </div>
                <div class="d-grid">
                    <button type="submit" class="btn btn-primary">Register</button>
                </div>
            </form>
        </div>
    </div>
</div>
</body>
</html>


Step 13: Create the Login HTML page

Go to src > main > resources > templates > login.html and put the below code.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login Page</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f8f9fa;
        }

        .container {
            margin-top: 50px;
            display: flex;
            justify-content: center;
        }

        .card {
            width: 100%;
            max-width: 500px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            border-radius: 8px;
            background-color: #fff;
        }

        .card-header {
            background-color: #007bff;
            color: #fff;
            border-bottom: none;
            border-radius: 8px 8px 0 0;
            padding: 15px;
            text-align: center;
        }

        .card-body {
            padding: 25px;
        }

        .form-label {
            margin-bottom: 10px;
            font-weight: bold;
        }

        .form-control {
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border: 1px solid #ced4da;
            border-radius: 4px;
            font-size: 16px;
        }

        .btn-primary {
            background-color: #007bff;
            border: none;
            color: #fff;
            padding: 12px;
            font-size: 16px;
            border-radius: 4px;
            cursor: pointer;
            width: 100%;
        }

        .btn-primary:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="card">
        <div class="card-header">
            <h4>Login</h4>
        </div>
        <div class="card-body">
            <form method="post" action="/perform_login">
                <div class="mb-3">
                    <label for="username" class="form-label">Username:</label>
                    <input type="text" class="form-control" id="username" name="username" required>
                </div>
                <div class="mb-3">
                    <label for="password" class="form-label">Password:</label>
                    <input type="password" class="form-control" id="password" name="password" required>
                </div>
                <div class="mb-3">
                    <label for="name" class="form-label">Name:</label>
                    <input type="text" class="form-control" id="name" name="name" required>
                </div>
                <div class="mb-3">
                    <label for="qualification" class="form-label">Qualification:</label>
                    <input type="text" class="form-control" id="qualification" name="qualification" required>
                </div>
                <div class="d-grid">
                    <button type="submit" class="btn btn-primary">Login</button>
                </div>
            </form>
        </div>
    </div>
</div>
</body>
</html>


Step 13: Create the Welcome HTML page.

Go to src > main > resources > templates > welcome.html and put the below code.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 <h1>Welcome User</h1>

</body>
</html>


pom.xml:

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>3.2.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>spring-security-login-extra-fields</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-security-login-extra-fields</name>
    <description>spring-security-login-extra-fields</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</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-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity6</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


Step 14: Once the project completed successfully, it will start at port 8080.

Register page:

API:

http://localhost:8080/register

Output:

Below is the Registration page.


Enter the username, password, name and qualification for the register and these credentials can save into the database.

Login page:

API:

http://localhost:8080/login

Output:

Below is the Login page.


Enter the registered username, password, name and qualification. The custom authentication provider will be handle these additional fields during the authentication process.

Welcome page:

`

This project demonstrates how to add the extra fields to the Spring Security login form that allows more customized and secure the authentication processes. By following these steps outlined in this article, we can enhance the Spring Boot application to meet specific authentication requirements.



Contact Us