The AuthenticationManager is a primary interface in the Spring Security framework. It manages essential functions of the authentication process. You use it to handle and verify user credentials, along with applying authentication rules within the Spring application.
The role of AuthenticationManager
The AuthenticationManager oversees the entire authentication flow:
The central authentication point: It acts as a single entrance for authentication requests in the Spring application, thereby simplifying authorization management.
Delegating authentication to authentication providers: When a user tries to access a secured resource, an authentication object carrying the request gets sent to the AuthenticationManager. The AuthenticationManager then delegates this request to its list of AuthenticationProviders; each tries to authenticate the request based on their capabilities.
Decision making: The AuthenticationManager decides the outcome each time an authentication attempt is made. If an AuthenticationProvider successfully authenticates the request, it returns a fully authenticated authentication object. If every provider fails, the AuthenticationManager throws an AuthenticationException.
Interface method
The AuthenticationManager interface only contains a single method that needs implementation:
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}When invoking its authenticate() function, you might encounter one of three potential outcomes:
Successful Authentication: It returns an
Authenticationobject, usually marked with theauthenticatedflag astrue. This outcome verifies the successful authentication of user credentials, confirming that the provided details coincide with a recognized user. TheAuthenticationobject represents the principal's identity and credentials, including their allowed authorities.Authentication Exception: If the
AuthenticationManagerdeduces that the provided credentials are not valid, it throws anAuthenticationException. This exception, which is a type of runtime error, signifies an unsuccessful authentication attempt due to incorrect user credentials.Indecision: In situations where the
AuthenticationManagercannot definitively decide the validity of given credentials, it might returnnull. This reflects an ambiguous state, neither verifying nor rejecting the user's authenticity.
Custom AuthenticationManager
First, you need to create a new Kotlin class that implements the AuthenticationManager interface. For this example, we'll create a straightforward manager that authenticates a user with a pre-set username and password. However, in a real-world situation, you would typically check against a database or another user store:
Java
@Component
public class CustomAuthenticationManager implements AuthenticationManager {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getPrincipal().toString();
String password = authentication.getCredentials().toString();
if ("admin".equals(username) && "password".equals(password)) {
return new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
}
throw new BadCredentialsException("Authentication failed for " + username);
}
}Kotlin
@Component
class CustomAuthenticationManager : AuthenticationManager {
override fun authenticate(authentication: Authentication): Authentication {
val username = authentication.principal.toString()
val password = authentication.credentials.toString()
if ("admin" == username && "password" == password) {
return UsernamePasswordAuthenticationToken(username, password, listOf())
}
throw BadCredentialsException("Authentication failed for $username")
}
}Note that the authenticate method does not return null. In this case, the AuthenticationManager can clearly determine the user's authentication status, thereby ruling out an indecisive state.
After creating your CustomAuthenticationManager, you need to incorporate it into your Spring Security configuration. Adjust your SecurityConfig class to use the custom authentication manager:
Java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomAuthenticationManager authenticationManager;
public SecurityConfig(CustomAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(requests -> requests.anyRequest().authenticated());
http.httpBasic(Customizer.withDefaults());
http.authenticationManager(authenticationManager);
return http.build();
}
}Kotlin
@Configuration
@EnableWebSecurity
class SecurityConfig(
private val authenticationManager: CustomAuthenticationManager
) {
@Bean
fun configure(http: HttpSecurity): SecurityFilterChain {
http.authorizeHttpRequests { requests -> requests.anyRequest().authenticated() }
http.httpBasic(Customizer.withDefaults())
http.authenticationManager(authenticationManager)
return http.build()
}
}To test the security configuration, we need to add some restrictions to our application:
authorizeHttpRequests(): This method specifies the authorization rules for HTTP requests. By calling it, you establish the guidelines for which HTTP requests require authentication and which do not.requests -> requests.anyRequest().authenticated(): This lambda expression outlines the actual authorization policy.anyRequest()is a method that matches any HTTP request. When combined withauthenticated(), it stipulates that every request to the application must be authenticated. In other words, the user must log in to access any part of the application.httpBasic(): This method sets up HTTP Basic Authentication, which is a simple authentication scheme that sends user credentials in the HTTP header. It's commonly employed for basic username/password authentication, usually in combination with HTTPS to ensure encryption.Customizer.withDefaults(): This convenience method applies the default configuration for HTTP Basic Authentication. The default setup includes establishing the required authentication filter and challenge entry point, which prompts the user for their username and password.
ProviderManager
The ProviderManager is a default implementation of the AuthenticationManager that works in conjunction with a series of AuthenticationProvider instances. Each AuthenticationProvider within this chain is responsible for either authenticating, rejecting, or passing the authentication request to the next provider. If all providers fail to authenticate, the process ends with a ProviderNotFoundException. This error signifies that the types of supported authentication do not match the given Authentication type.
Each AuthenticationProvider involved in a ProviderManager specializes in handling a particular form of authentication. This modular approach allows the ProviderManager to support various authentication methods, ranging from username/password confirmation to complex mechanisms like SAML assertions. SAML, an acronym for Security Assertion Markup Language, is an open standard for exchanging authentication and authorization data between parties, allowing users to log in once and access multiple applications without needing to re-enter their credentials.
When an authentication request is received, typically as an Authentication object containing details like username and password, the ProviderManager iterates through its list of AuthenticationProvider instances to find one that can manage the request:
Every
AuthenticationProviderhas asupports(Class<?> authentication)method. TheProviderManageruses this method to ascertain if eachAuthenticationProvidercan handle the specific type ofAuthenticationobject it has received. For instance, a provider that handles username and password authentication would returntruewhen asked if it supports theUsernamePasswordAuthenticationTokenclass.When the
ProviderManagerfinds anAuthenticationProviderthat supports theAuthenticationinterface type, it calls theauthenticatemethod on that provider.Inside its
authenticatemethod, theAuthenticationProvidercarries out necessary checks, such as verifying the username and password against a database. If the authentication succeeds, the provider creates and returns a fully populatedAuthenticationobject, encompassing the user's authorities (roles).
Custom AuthenticationProvider
Let's create a simple Authentication Provider implementation, which will verify the username and password:
Java
@Component
public class CustomAuthenticationManager implements AuthenticationManager {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getPrincipal().toString();
String password = authentication.getCredentials().toString();
if ("admin".equals(username) && "password".equals(password)) {
return new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
}
throw new BadCredentialsException("Authentication failed for " + username);
}
}Kotlin
@Component
class CustomAuthenticationProvider : AuthenticationProvider {
override fun authenticate(authentication: Authentication): Authentication {
val username = authentication.principal.toString()
val password = authentication.credentials.toString()
if ("admin" == username && "password" == password) {
return UsernamePasswordAuthenticationToken(username, password, listOf())
}
throw BadCredentialsException("Authentication failed for $username")
}
override fun supports(auth: Class<*>): Boolean {
return Authentication::class.java.isAssignableFrom(auth)
}
}Remember to update your Security Config to register your new provider:
Java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomAuthenticationManager authenticationManager;
public SecurityConfig(CustomAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(requests -> requests.anyRequest().authenticated());
http.httpBasic(Customizer.withDefaults());
http.authenticationProvider(authProvider);
return http.build();
}
}Kotlin
@Configuration
@EnableWebSecurity
class SecurityConfig(
val authProvider: CustomAuthenticationProvider,
) {
@Bean
fun configure(http: HttpSecurity): SecurityFilterChain {
http.authorizeHttpRequests { requests -> requests.anyRequest().authenticated() }
http.httpBasic(Customizer.withDefaults())
http.authenticationProvider(authProvider)
return http.build()
}
}Testing and verification
After you configure the AuthenticationManager, you can check its operation. First, let's add a simple controller that will return "Hello, World!" when you request the "/" endpoint:
Java
@RestController
public class IndexController {
@GetMapping("/")
public String index() {
return "Hello, World!";
}
}Kotlin
@RestController
class IndexController {
@GetMapping("/")
fun index() = "Hello, World!"
}Now if you open the browser and try to make a request to this endpoint, you will see that your browser asks for credentials:
If you enter incorrect login details, the page will reload, and the window will reappear, preventing you access to the page's content. When you input the credentials you specified earlier (admin as the username and password as the password), the window will disappear, and you'll gain access to the requested resource:
Conclusion
In short, the AuthenticationManager in the Spring Security framework plays a critical role in securing applications. It manages authentication processes effectively, serving as a central point for all authentication requests and facilitating a unified, organized approach to security.