Spring Security - Form Login



Form-based login is one form of Username/password authentication that Spring Security provides support for. This is provided through an Html form.

Whenever a user requests a protected resource, Spring Security checks for the authentication of the request. If the request is not authenticated/authorized, the user will be redirected to the login page. The login page must be somehow rendered by the application. Spring Security provides that login form by default.

Moreover, any other configuration, if needed, must be explicitly provided as given below −

protected void configure(HttpSecurity http) throws Exception {
http 
   // ... 
   .authorizeHttpRequests(
      request -> request.requestMatchers("/login").permitAll()
      .requestMatchers("/**").authenticated()
   )
   .formLogin(Customizer.withDefaults()) 
}

Let us start actual programming with Spring Security. Before you start writing your first example using Spring framework, you have to make sure that you have set up your Spring environment properly as explained in Spring Security - Environment Setup Chapter. We also assume that you have a bit of working knowledge on Spring Tool Suite IDE.

Now let us proceed to write a Spring MVC based Application managed by Maven, which will ask user to login, authenticate user and then provide option to logout using Spring Security Form Login Feature.

Create Project using Spring Initializr

Spring Initializr is great way to start with Spring Boot project. It provides a easy to use User Interface to create a project, add dependencies, select java runtime etc. It generates a skeleton project structure which once downloaded can be imported in spring tool suite and we can proceed with our readymade project structure.

We're choosing a maven project, naming the project as formlogin, with java version as 21. Following dependencies are added:

  • Spring Web

  • Spring Security

  • Thymeleaf

  • Spring Boot DevTools

Spring Initializr

Thymeleaf is a templating engine for Java. It allows us to quickly develop static or dynamic web pages for rendering in the browser. It is extremely extensible and allows us to define and customize the processing of our templates in fine detail. In addition to this, we can learn more about Thymeleaf by clicking this link.

Let's move on to generate our project and download it. We then extract it to a folder of our choice and use any IDE to open it. I shall be using Spring Tools Suite 4. It is available for free downloading from the https://spring.io/tools website and is optimized for spring applications.

pom.xml with all relevant dependencies

Let's take a look at our pom.xml file. It should look something similar to this −

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>3.3.1</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.tutorialspoint.security</groupId>
   <artifactId>formlogin</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>formlogin</name>
   <description>Demo project for Spring Boot</description>
   <url/>
   <licenses>
      <license/>
   </licenses>
   <developers>
      <developer/>
   </developers>
   <scm>
      <connection/>
      <developerConnection/>
      <tag/>
      <url/>
   </scm>
   <properties>
      <java.version>21</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-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>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>
         </plugin>
      </plugins>
   </build>
</project>

Spring Security Configuration Class

Inside of our config package, we have created the WebSecurityConfig class. We shall be using this class for our security configurations, so let's annotate it with an @Configuration annotation and @EnableWebSecurity. As a result, Spring Security knows to treat this class a configuration class. As we can see, configuring applications have been made very easy by Spring.

WebSecurityConfig

package com.tutorialspoint.security.formlogin.config; 

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain; 

@Configuration 
@EnableWebSecurity
public class WebSecurityConfig  { 

   @Bean 
   protected UserDetailsService userDetailsService() {
      UserDetails user = User.builder()
         .username("user")
         .password(passwordEncoder().encode("user123"))
         .roles("USER")
         .build();
      UserDetails admin = User.builder()
         .username("admin")
         .password(passwordEncoder().encode("admin123"))
         .roles("USER", "ADMIN")
         .build();
      return new InMemoryUserDetailsManager(user, admin);
   }

   @Bean 
   protected PasswordEncoder passwordEncoder() { 
      return new BCryptPasswordEncoder(); 
   }

   @Bean
   protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 
      return http
         .csrf(AbstractHttpConfigurer::disable)
         .authorizeHttpRequests(
            request -> request.requestMatchers("/login").permitAll()
            .requestMatchers("/**").authenticated()
         )
         .formLogin(Customizer.withDefaults())      
         .logout(config -> config  
         .logoutUrl("/logout") 
         .logoutSuccessUrl("/login")) 
         .build();
   }   
}

Configuration Class Details

Let's take a look at our configuration class.

  • First, we shall create a bean of our UserDetailsService class by using the userDetailsService() method. We shall be using this bean for managing our users for this application. Here, to keep things simple, we shall use an InMemoryUserDetailsManager instance to create users. These users, along with our given username and password, are mapped to User and Admin roles respectively.

Password Encoder

  • Now, let's look at our PasswordEncoder. We shall be using a BCryptPasswordEncoder instance for this example. Hence, while creating the user, we used the passwordEncoder to encode our plaintext password like this:

    .password(passwordEncoder().encode("user123"))
    

Http Security Configuration

After the above steps, we move on to our next configuration. Here, we've defined the filterChain method. This method takes HttpSecurity as a parameter. We shall be configuring this to use our form login and logout function.

We can observe that all these functionalities are available in Spring Security. Let’s study the below section in detail −

http
   .csrf(AbstractHttpConfigurer::disable)
   .authorizeHttpRequests(
      request -> request.requestMatchers("/login").permitAll()
      .requestMatchers("/**").authenticated()
    )
   .formLogin(Customizer.withDefaults())      
   .logout(config -> config  
      .logoutUrl("/logout") 
      .logoutSuccessUrl("/login")) 
   .build();

There are a few points to note here −

  • We have disabled csrf or Cross-Site Request Forgery protection As this is a simple application only for demonstration purposes, we can safely disable this for now.

  • Then we add configuration which requires all requests to be authenticated. As we shall see later, we will have a single "/" endpoint for the index page of this application, for simplicity.

  • After that, we shall be using the formLogin() functionality of Spring Security as mentioned above. This generates a default login page.

  • And lastly, we have the logout() functionality. For this too, a default functionality has been provided by Spring security. Here it performs two important functions −

    • Invalidates the Http session, and unbinds objects bound to the session.

    • Removes the authentication from Spring’s Security context.

We also, provided a logoutSuccessUrl(), so that the application comes back to the login page after logout. This completes our application configuration.

Controller Class

In this class, we've created a mapping for single "/" endpoint for the index page of this application, for simplicity. This will redirect to index.html.

AuthController

package com.tutorialspoint.security.formlogin.controllers; 

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

@Controller 
public class AuthController { 
   @GetMapping("/") 
   public String home() { 
      return "index"; 
   }
}

Views

Create index.html in /src/main/resources/templates folder with following content to act as a home page.

index.html

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="https://www.thymeleaf.org" 
   xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6"> 
   <head> 
      <title>
         Hello World!
      </title> 
   </head>
   <body> 
      <h1 th:inline="text">Hello World!</h1> 
      <form th:action="@{/logout}" method="post"> 
         <input type="submit" value="Sign Out"/> 
      </form>
   </body> 
<html> 

Running the Application

As we've all component ready, let's run the Application. Right Click on the project, select Run As and then Spring Boot App as shown in image below:

Run as Spring Boot App

It will boot up the application and once application is started, we can run localhost:8080 to check the changes.


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 [32m :: Spring Boot ::  [39m               [2m (v3.3.1) [0;39m

 [2m2024-07-10T11:53:08.438+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mc.t.s.formlogin.FormloginApplication     [0;39m  [2m: [0;39m Starting FormloginApplication using Java 21.0.3 with PID 13252 (E:\security\formlogin\target\classes started by Tutorialspoint in E:\security\formlogin)
 [2m2024-07-10T11:53:08.441+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mc.t.s.formlogin.FormloginApplication     [0;39m  [2m: [0;39m No active profile set, falling back to 1 default profile: "default"
 [2m2024-07-10T11:53:08.495+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36m.e.DevToolsPropertyDefaultsPostProcessor [0;39m  [2m: [0;39m Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
 [2m2024-07-10T11:53:08.496+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36m.e.DevToolsPropertyDefaultsPostProcessor [0;39m  [2m: [0;39m For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
 [2m2024-07-10T11:53:09.395+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.s.b.w.embedded.tomcat.TomcatWebServer  [0;39m  [2m: [0;39m Tomcat initialized with port 8080 (http)
 [2m2024-07-10T11:53:09.407+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.apache.catalina.core.StandardService   [0;39m  [2m: [0;39m Starting service [Tomcat]
 [2m2024-07-10T11:53:09.407+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.apache.catalina.core.StandardEngine    [0;39m  [2m: [0;39m Starting Servlet engine: [Apache Tomcat/10.1.25]
 [2m2024-07-10T11:53:09.451+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.a.c.c.C.[Tomcat].[localhost].[/]       [0;39m  [2m: [0;39m Initializing Spring embedded WebApplicationContext
 [2m2024-07-10T11:53:09.452+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mw.s.c.ServletWebServerApplicationContext [0;39m  [2m: [0;39m Root WebApplicationContext: initialization completed in 956 ms
 [2m2024-07-10T11:53:09.751+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mr$InitializeUserDetailsManagerConfigurer [0;39m  [2m: [0;39m Global AuthenticationManager configured with UserDetailsService bean with name userDetailsService
 [2m2024-07-10T11:53:09.811+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.s.b.a.w.s.WelcomePageHandlerMapping    [0;39m  [2m: [0;39m Adding welcome page template: index
 [2m2024-07-10T11:53:10.092+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.s.b.d.a.OptionalLiveReloadServer       [0;39m  [2m: [0;39m LiveReload server is running on port 35729
 [2m2024-07-10T11:53:10.124+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.s.b.w.embedded.tomcat.TomcatWebServer  [0;39m  [2m: [0;39m Tomcat started on port 8080 (http) with context path '/'
 [2m2024-07-10T11:53:10.133+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mc.t.s.formlogin.FormloginApplication     [0;39m  [2m: [0;39m Started FormloginApplication in 2.051 seconds (process running for 2.795)

Output

Now open localhost:8080, you can see a nice looking default login page.

Login Page

Login Form

Login Page for Bad Credentials

Enter any invalid credential and it will show error.

Login Form Error

Home Page

Enter valid credential.

Login Form Valid Credentials

and it will load home page.

Login Form Success

After Logout

Now click on sign-out button, which will load the login page again.

Login Form
Advertisements