1. Introduction

This tutorial demonstrates Spring Security Form Login with Spring Boot – how to configure form-based authentication using Spring Security in a Spring Boot application. We’ll cover setting up the required dependencies, creating a configuration class to secure the application, and customizing various aspects of the login process. This article is an up-to-date article, works with Spring Security 6.2.x.

2. Maven Dependencies

When using Spring Boot, you can simply include the spring-boot-starter-security dependency in your pom.xml file. This will automatically bring in Spring Security and its required dependencies:

pom.xml
...
<dependency>
   <groupid>org.springframework.boot</groupid>
   <artifactid>spring-boot-starter-security</artifactid>
  <version>3.2.5</version>
</dependency>

3. Spring Security Configuration

3.1 Enable Spring Security

To enable Spring Security in a Spring Boot application, create a @Configuration class and annotate it with @EnableWebSecurity. This annotation allows Spring to find and automatically apply the class to the global Web Security configuration.

Java
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

@Configuration
@EnableWebSecurity
public class AppSecurityConfig {
    // Security configuration goes here

    // User details - 
    @Bean
    public UserDetailsService userDetailsService() {}
    
    //Filter Chain configurations
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {}
    
    //Password Encoder
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

3.2 Configure In-Memory Authentication

For simplicity, we’ll use an in-memory authentication provider to define our users. In a real-world application, you would likely use a database or an external authentication system.

Java
@Configuration
@EnableWebSecurity
public class AppSecurityConfig {

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user1 = User.withUsername("user1")
                .password(passwordEncoder().encode("user_password_1"))
                .roles("USER")
                .build();

        //Avoid using - User.withDefaultPasswordEncoder() as it is deprecated
        UserDetails user2 = User.withUsername("user2")
                .password(passwordEncoder().encode("user_password_2"))
                .roles("USER")
                .build();

        UserDetails admin1 = User.withDefaultPasswordEncoder()
                .username("admin1")
                .password(passwordEncoder().encode("admin_password_1"))
                .roles("ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user1, user2, admin1);
    }
    
    //password Encoder
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    //Filter Chain
  }

In this example, we define three users: user1 and user2 with the USER role, and admin with the ADMIN role. The User.withDefaultPasswordEncoder() method automatically encodes the passwords using the default PasswordEncoder configured in Spring Security.

3.3 Configure URL Authorization and Form Login

Next, we’ll configure URL authorization rules and the form-based login mechanism using the HttpSecurity object.

Java
@Configuration
@EnableWebSecurity
public class AppSecurityConfig {

//other configs mentioned above

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .requestMatchers("/api/admins/**").hasRole("ADMIN")
                .requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
                .requestMatchers("/anonymous/**").anonymous()
                .requestMatchers("/login").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin((form) -> form
                .loginPage("/login")
                .defaultSuccessUrl("/homepage", true)
                .failureUrl("/login?error=true")
                .permitAll()
            )
            .logout((logout) -> logout.permitAll());

        return http.build();
    }
}

Let’s break down this configuration:

  1. URL Authorization: The authorizeHttpRequests method configures the authorization rules for different URL patterns. In this example, we:
  • Allow access to /api/admin/** URLs only for users with the ADMIN role.
  • Allow access to /api/users/** URLs only for users with the USER role.
  • Allow anonymous access to /anonymous* URLs.
  • Allow access to the /login URL for everyone (required for the login page).
  • Require authentication for all other URLs.
  1. Form Login: The formLogin method configures the form-based login mechanism:
  • loginPage("/login") specifies the custom login page URL.
  • defaultSuccessUrl("/homepage", true) sets the landing page after successful login. The true flag indicates that the user should always be redirected to this URL, even if they initially requested a different URL.
  • failureUrl("/login?error=true") sets the URL to redirect to after a failed login attempt.
  • permitAll() allows public access to the login page.
  1. Logout: The logout method configures the logout mechanism and allows public access to the logout URL.

3.4 Customize Login Page

Create a login.html file in your application’s resources/templates directory with the following content:

HTML
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login Page</title>
</head>
<body>
    <h1>Login</h1>
    <p th:if="${param.error}" class="error">Invalid username or password</p>
    <form th:action="@{/login}" method="post">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" autofocus="autofocus" />
        <br />
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" />
        <br />
        <input type="submit" value="Log in" />
    </form>
</body>
</html>

This login page uses Thymeleaf templating to display an error message if the error parameter is present in the URL (which happens after a failed login attempt). The th:action="@{/login}" attribute specifies the URL to submit the form to, which matches the configured loginProcessingUrl in Spring Security.

3.5 Customize Landing Pages

To customize the landing pages after successful or failed login attempts, you can create the corresponding HTML files in the resources/templates directory:

  • homepage.html: The landing page after successful login.
  • login.html: The login page with an error message displayed after a failed login attempt.
HTML
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Homepage</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
    <div class="row mt-5">
        <div class="col-md-6 offset-md-3">
            <div class="card">
                <div class="card-header">
                    <h3 class="text-center">Welcome</h3>
                </div>
                <div class="card-body">
                    <p th:text="'Hello, ' + ${#authentication.getPrincipal().getUsername()} + '!'"></p>
                    <p>You have successfully logged in to our application.</p>
                    <div th:if="${#authorization.expression('hasRole(''ADMIN'')')}">
                        <p>You have administrative privileges.</p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

To use this homepage.html template, make sure you have configured Thymeleaf in your Spring Boot application. You can do this by adding the spring-boot-starter-thymeleaf dependency to your pom.xml file. With this configuration in place, when a user successfully logs in, they will be redirected to the /homepage URL, and the homepage.html template will be rendered, displaying a personalized welcome message and information about their role (if they have the “ADMIN” role).

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

4. Conclusion

In this tutorial, we learned how to configure form-based authentication using Spring Security in a Spring Boot application. We covered setting up the required dependencies, creating a security configuration class, defining in-memory users, configuring URL authorization rules, customizing the login process, and creating custom login and landing pages.

By |Last Updated: May 19th, 2024|Categories: Spring Security|