”;
Spring Security allows us to customize our authentication process as much as we want. Starting from a custom login page to our very own customized authentication providers and authentication filters, we can pretty much customize every aspect of the authentication process. We can define our own authentication process which can range from basic authentication using a username and a password to a complex one such as two-factor authentication using tokens and OTP’s. Also, we can use various databases – both relational and non-relational, use various password encoders, lock malicious users out of their accounts, and so on.
Spring Security Architecture
The basic components of Spring Security, as we can see in the above diagram are given below. We shall discuss them briefly as we go along. We shall also discuss their roles in the authentication and authorization process.
AuthenticationFilter
This is the filter that intercepts requests and attempts to authenticate it. In Spring Security, it converts the request to an Authentication Object and delegates the authentication to the AuthenticationManager.
AuthenticationManager
It is the main strategy interface for authentication. It uses the lone method authenticate() to authenticate the request. The authenticate() method performs the authentication and returns an Authentication Object on successful authentication or throw an AuthenticationException in case of authentication failure. If the method can’t decide, it will return null. The process of authentication in this process is delegated to the AuthenticationProvider which we will discuss next.
AuthenticationProvider
The AuthenticationManager is implemented by the ProviderManager which delegates the process to one or more AuthenticationProvider instances. Any class implementing the AuthenticationProvider interface must implement the two methods – authenticate() and supports(). First, let us talk about the supports() method. It is used to check if the particular authentication type is supported by our AuthenticationProvider implementation class. If it is supported it returns true or else false. Next, the authenticate() method. Here is where the authentication occurs. If the authentication type is supported, the process of authentication is started. Here is this class can use the loadUserByUsername() method of the UserDetailsService implementation. If the user is not found, it can throw a UsernameNotFoundException.
On the other hand, if the user is found, then the authentication details of the user are used to authenticate the user. For example, in the basic authentication scenario, the password provided by the user may be checked with the password in the database. If they are found to match with each other, it is a success scenario. Then we can return an Authentication object from this method which will be stored in the Security Context, which we will discuss later.
Spring Security provides following major implementations of AuthenticationProvider.
-
DaoAuthenticationProvider − This provider is used to provide database based authentication.
-
LdapAuthenticationProvider − This provider is specialized for LDAP(Lightweight Directory Access Protocol) based authentication.
-
OpenIDAuthenticationProvider − This provider is used for OpenID based authentication and can be used with OpenID authentication providers like Google/Facebook etc.
-
JwtAuthenticationProvider − For JWT(Java Web Token) based authentication, we can use JwtAuthenticationProvider class.
-
RememberMeAuthenticationProvider − This class is used for user authentication based on remember me token of user.
We”ll be creating our own AuthenticationProvider in coming section.
UserDetailsService
It is one of the core interfaces of Spring Security. The authentication of any request mostly depends on the implementation of the UserDetailsService interface. It is most commonly used in database backed authentication to retrieve user data. The data is retrieved with the implementation of the lone loadUserByUsername() method where we can provide our logic to fetch the user details for a user. The method will throw a UsernameNotFoundException if the user is not found.
PasswordEncoder
Until Spring Security 4, the use of PasswordEncoder was optional. The user could store plain text passwords using in-memory authentication. But Spring Security 5 has mandated the use of PasswordEncoder to store passwords. This encodes the user’s password using one its many implementations. The most common of its implementations is the BCryptPasswordEncoder. Also, we can use an instance of the NoOpPasswordEncoder for our development purposes. It will allow passwords to be stored in plain text. But it is not supposed to be used for production or real-world applications.
Spring Security Context
This is where the details of the currently authenticated user are stored on successful authentication. The authentication object is then available throughout the application for the session. So, if we need the username or any other user details, we need to get the SecurityContext first. This is done with the SecurityContextHolder, a helper class, which provides access to the security context. We can use the setAuthentication() and getAuthentication() methods for storing and retrieving the user details respectively.
Custom Authenticator
We can create a custom Authenticator by implementing AuthenticationProvider interface. AuthenticatorProvider interface has two methods authenticate() and supports().
authenticate() method
@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = authentication.getCredentials().toString(); UserDetails user = userDetailsService.loadUserByUsername(username); if (user == null || !password.equals(user.getPassword())) { throw new BadCredentialsException("Invalid username or password"); } List<GrantedAuthority> authorities = new ArrayList(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); return new UsernamePasswordAuthenticationToken(username, password, authorities); }
Here in authenticate() method, we”re getting username and password using authentication object and we”re comparing username/password with the user credentials. In case user details are invalid, we”re throwing an exception as BadCredentialsException. Otherwise, a new role is prepared and UsernamePasswordAuthenticationToken is returned with required role.
supports() method
@Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); }
Spring Security Configuration
Use the AuthenticationProvider created in the AuthenticationManager and mark it as managed bean.
@Bean public AuthenticationManager authManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); WebAuthenticationProvider authProvider = new WebAuthenticationProvider(userDetailsService()); authenticationManagerBuilder.authenticationProvider(authProvider); return authenticationManagerBuilder.build(); }
That”s all we need. Now let”s see the complete code in action.
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
-
Spring Boot DevTools
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>
Authenticator Provider
Inside of our config package, we have created the WebAuthenticationProvider class by implementing AuthenticationProvider interface. As in authenticate() method, we”ve to compare username along with password, we”ve used UserDetailsService instance to get the user details.
package com.tutorialspoint.security.formlogin.config; import java.util.ArrayList; import java.util.List; 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.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; @Component public class WebAuthenticationProvider implements AuthenticationProvider { private final UserDetailsService userDetailsService; public WebAuthenticationProvider(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = authentication.getCredentials().toString(); UserDetails user = userDetailsService.loadUserByUsername(username); if (user == null || !password.equals(user.getPassword())) { throw new BadCredentialsException("Invalid username or password"); } List<GrantedAuthority> authorities = new ArrayList(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); return new UsernamePasswordAuthenticationToken(username, password, authorities); } }
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.authentication.AuthenticationManager; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class WebSecurityConfig { @Bean public AuthenticationManager authManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); WebAuthenticationProvider authProvider = new WebAuthenticationProvider(userDetailsService()); authenticationManagerBuilder.authenticationProvider(authProvider); return authenticationManagerBuilder.build(); } @Bean protected UserDetailsService userDetailsService() { UserDetails user = User.builder() .username("user") .password("user123") .roles("USER") .build(); UserDetails admin = User.builder() .username("admin") .password("admin123") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } @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.
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 −
return 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.
-
After that, we”re using formLogin() functionality of Spring Security as mentioned above. This makes browser to ask for a default login form for username/password and logout() to provide logout functionality.
Authentication Manager Configuration
Let”s take a look at our custom AuthenticationProvider configuration.
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); WebAuthenticationProvider authProvider = new WebAuthenticationProvider(userDetailsService()); authenticationManagerBuilder.authenticationProvider(authProvider); return authenticationManagerBuilder.build();
We”ve first built a AuthenticationManagerBuilder instance and pass it a authenticationProvider. WebAuthenticationProvider instance is created as custom authentication provider with a userDetailsService object.
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> <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 that browser is asking for username/password via system dialog.
Browser”s dialog for username/password
Enter Invalid Credential
If we enter invalid credential, then same dialog will popup again.
Home Page for User
If we enter valid credential for a User and it will load home page for User
”;