Spring Security - Redirection



In web application, we're often required to land to different pages based on user profile. For example, a normal user may land on user home page whereas an admin may land on Admin console. We can achieve this requirement very easily using spring security which provides supports to handle login success and based on user role, we can decide which page to be shown to the user or simply redirect the user to the required page.

In order to achieve redirection, we need to handle successHandler of formLogin in Spring Security Configuration as shown below:

protected void configure(HttpSecurity http) throws Exception {
http 
   // ... 
   // key should be unique
   .formLogin(form -> form.loginPage("/login")
      .defaultSuccessUrl("/")
      .failureUrl("/login?error=true")
      .successHandler(authenticationSuccessHandler())
      .permitAll())   
   //
   .build();
}

Here authenticationSuccessHandler() method is another bean to handle login success and redirect user to the required page.

@Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
   return new AuthenticationHandler();
}

AuthenticationSuccessHandler

In order to achieve redirection, we first need to create a class by implementing AuthenticationSuccessHandler as shown below. In this class, we've to implement onAuthenticationSuccess() method. onAuthenticationSuccess() method is called once user is logged in successfully. Now using Authentication object, we can check the role of the logged in user and then determine the redirection url. Using HttpServletResponse.sendRedirect() method, we can then redirect user to the required page.

public class AuthenticationHandler implements AuthenticationSuccessHandler {

   @Override
   public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
      Authentication authentication) throws IOException, ServletException {
      String redirect = request.getContextPath();

      if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
         redirect = "/admin";
      } else if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_USER"))) {
         redirect = "/user";
      }

      response.sendRedirect(redirect);
   }
}

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.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;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

@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(form -> form.loginPage("/login")
            .defaultSuccessUrl("/")
            .failureUrl("/login?error=true")
            .successHandler(authenticationSuccessHandler())
            .permitAll())       
         .rememberMe(config -> config.key("123456")
         .tokenValiditySeconds(3600))   
         .logout(config -> config  
            .logoutUrl("/logout") 
            .logoutSuccessUrl("/login")
            .invalidateHttpSession(true)
            .deleteCookies("JSESSIONID"))
         .build();
   }  
   
   @Bean
   public AuthenticationSuccessHandler authenticationSuccessHandler() {
      return new AuthenticationHandler();
   }
}

Here we've used the authenticationSuccessHandler() in successHandler() method to do the required redirection. The AuthenticationHandler class should be in same package.

AuthenticationHandler class

Following class implements AuthenticationSuccessHandler as shown below. In this class, we've implemented onAuthenticationSuccess() method. onAuthenticationSuccess() method is called once user is logged in successfully.

AuthenticationHandler

package com.tutorialspoint.security.formlogin.config;

import java.io.IOException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class AuthenticationHandler implements AuthenticationSuccessHandler {

   @Override
   public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
      Authentication authentication) throws IOException, ServletException {
      String redirect = request.getContextPath();

      if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
         redirect = "/admin";
      } else if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_USER"))) {
         redirect = "/user";
      }

      response.sendRedirect(redirect);
   }
}

Controller Class

In this class, we've created a mapping for "/" endpoint and for "/login" for the index page and login page of this application. For user.html and admin.html we've two methods user() and admin() added.

AuthController.java

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"; 
   }
   @GetMapping("/login") 
   public String login() { 
      return "login"; 
   }
   @GetMapping("/user")
   public String user() {
      return "user";
   }
   @GetMapping("/admin")
   public String admin() {
      return "admin";
   }
}

Views

Let's create index.html in /src/main/resources/templates folder with following content to act as a home page and to display logged in user name.

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-springsecurity3"> 
   <head> 
      <title>
         Hello World!
      </title> 
   </head>
   <body> 
      <h1 th:inline="text">Hello <span sec:authentication="name"></span>!</h1> 
      <form th:action="@{/logout}" method="post"> 
         <input type="submit" value="Sign Out"/> 
      </form>
   </body> 
<html> 

login.html

Let's create the login.html in /src/main/resources/templates folder with following content to act as a login page. We're using default name username, password and remember-me for text fields. In case of other name, we need to set the same in spring security config class as well.

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml"      
   xmlns:th="https://www.thymeleaf.org" 
   xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> 
   <head> 
      <title>Spring Security Example</title> 
   </head> 
   <body> 
      <div th:if="${param.error}"> 
         <p>Bad Credentials</p>
      </div> 
      <div th:if="${param.logout}">You have been logged out.</div> 
      <form th:action="@{/login}" method="post">
         <h1>Please sign in</h1>
         <table>
            <tr>
               <td><label for="username"><b>Username</b></label></td>
               <td><input type="text" placeholder="Enter Username" name="username" id="username" required></td>
            </tr>
            <tr>
               <td><label for="password"><b>Password</b></label></td>
               <td><input type="password" placeholder="Enter Password" name="password" id="password" required></td>
            </tr>
            <tr>
               <td><label for="remember-me"><b>Remember Me</b></label> </td>
               <td><input type="checkbox" name="remember-me" /></td>
            </tr>
            <tr>
               <td> </td>
               <td><input type="submit"  value="Sign In" /></td>
            </tr>		
         </table>       
      </form> 
   </body>
</html>

In login form, we're using POST method to login while using input fields with name and id as username, password and remember-me checkbox. If checkbox is selected, a remember-me Cookie will be created when user logs in the application.

admin.html

Let's create the admin.html in /src/main/resources/templates folder with following content to act as a home page for user when logged using ADMIN Role credentials.

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="https://www.thymeleaf.org" 
   xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> 
   <head> 
      <title>
         Hello Admin!
      </title> 
   </head>
   <body> 
      <p>Admin Console</p>
      <h1 th:inline="text">Welcome <span sec:authentication="name"></span>!</h1> 
      <a href="/logout" alt="logout">Sign Out</a>
   </body> 
<html> 

user.html

Let's create the user.html in /src/main/resources/templates folder with following content to act as a home page for user when logged using USER Role credentials.

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="https://www.thymeleaf.org" 
   xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> 
   <head> 
      <title>
         Hello User!
      </title> 
   </head>
   <body> 
      <p>User Dashboard</p>
      <h1 th:inline="text">Hello <span sec:authentication="name"></span>!</h1> 
      <a href="/logout" alt="logout">Sign Out</a>
   </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.

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

Output

Now open localhost:8080, you can see our login page.

Login Page with User Details entered

Login Form with User credential

Home Page for User

When we enter valid credential for a User and it will load home page for User as user.html

User Home Page

Now click on Sign out link to logout and on login Page use Admin Details.

Login Form with Admin credential

Home Page for Admin

When we enter valid credential for a Admin and it will load home page for Admin as admin.html

Admin Home Page
Advertisements