Computer scienceBackendSpring BootSpring SecuritySpring Security internals

Authentication Providers

16 minutes read

Authentication providers are a key part of the Spring Security authentication structure. In this topic, you'll learn what authentication providers are and how they factor into the Spring Security authentication structure.

What is an authentication provider?

An authentication provider is tasked with authenticating a user, verifying their credentials for validity, and returning an Authentication object that Spring Security uses to represent the user in the SecurityContext.

The AuthenticationProvider interface is the core of the Spring Security authentication structure. Each authentication provider needs to implement this interface. Here is its definition:

package org.springframework.security.authentication

interface AuthenticationProvider {
    // The @Throws annotation is not mandatory in Kotlin, but it can be used for Java interoperability.
    @Throws(AuthenticationException::class)
    fun authenticate(authentication: Authentication): Authentication

    fun supports(authentication: Class<*>): Boolean
}

The authenticate() method is tasked with authenticating a user and returning an Authentication object that Spring Security uses to represent the user in the SecurityContext.

The supports() method returns true if the AuthenticationProvider supports the indicated Authentication object. In simple terms, this method indicates that the AuthenticationProvider can process the Authentication object given to it. The ProviderManager selects an AuthenticationProvider capable of performing authentication at runtime.

You'll learn more about the ProviderManager and the AuthenticationProvider's methods in the following parts.

Why would we need an authentication provider?

Before we delve into the details about authentication providers, let's step back and question why we would need an authentication provider.

With authentication and Spring Security, we generally have three scenarios:

  1. we can access the user's hashed password because we have it stored in our database,

  2. we cannot access the user's hashed password because it is stored in an external system, like a third-party identity management system,

  3. we don't have a password at all because we're using a different authentication mechanism, like a token-based authentication mechanism, such as "Login with Google" through OAuth2.

Authentication providers are useful in the first two scenarios. For the first scenario, Spring Security provides the DaoAuthenticationProvider. We'll learn more about it in the coming sections. For the second scenario, we can generate our own authentication provider.

Before moving to the next section, let's highlight one of the programming principles that help us write maintainable code, the single-responsibility principle.

This principle states that every class should have a single responsibility, and that responsibility should be fully contained within the class. Regarding the authentication provider, its singular responsibility is to authenticate a user. We will understand different authentication providers that implement varying authentication mechanisms. They all share the same single responsibility, to authenticate a user, but they do this in distinct ways and use different dependencies.

The big picture

We are already aware that Spring Security uses servlet filters for authentication. The application's requests are intercepted by a filter chain, which is a series of filters, before they reach the servlet.

The filter chain delegates the authentication process to the AuthenticationManager. The ProviderManager, being the default implementation of AuthenticationManager, iterates through the list of authentication providers with which it is configured and uses the one supporting the type of Authentication object that it has to authenticate the user.

The actual authentication work is done by the respective authentication provider, which refers to the specific implementation of the AuthenticationProvider interface. Spring Security comes with several AuthenticationProvider implementations that you can use directly. You also have the option to create your own implementation if necessary. We shall learn more about the various implementations in the following sections, but for now, let's see how this all comes together.

Relation between filters, authentication manager and authentication provider.

AuthenticationProvider implementations

Previously, we learned that all authentication providers must implement the AuthenticationProvider interface.

One such implementation is the org.springframework.security.authentication.dao.DaoAuthenticationProvider. This implementation uses a UserDetailsService to retrieve user details and a PasswordEncoder to encode the password.

Let's remind ourselves that the UserDetailsService is a simple interface with one method:

// The @Throws annotation is not mandatory in Kotlin, but it can be used for Java interoperability.
@Throws(UsernameNotFoundException::class)
fun loadUserByUsername(username: String): UserDetails

This method locates the user based on the username.

The org.springframework.security.crypto.password.PasswordEncoder, also an interface, has the responsibility of password encoding.

The DaoAuthenticationProvider subclasses the AbstractUserDetailsAuthenticationProvider, which is in the org.springframework.security.authentication.dao package. This abstract class serves as a base AuthenticationProvider and allows subclasses to override and work with UserDetails objects.

The following diagram represents the relationship between different classes and interfaces.

Authentication provider and dependencies class diagram.

A closer look

Now that we have observed the key players in the authentication process, let's take a more detailed look at how things work. The ProviderManager contains a list of authentication providers. When asked to authenticate a user, it will look through the list of authentication providers and use the one that supports the type of Authentication object that it is given to authenticate the user. The check that the authentication provider supports the type of Authentication object is performed by the supports() method of the AuthenticationProvider interface. The first authentication provider that supports the type is used to authenticate the user.

So what does it mean for an authentication provider to support the type of Authentication object? Let's check out the DaoAuthenticationProvider, one of the implementations of the AuthenticationProvider interface, to find out.

The definition of the supports() method is as follows:

override fun supports(authentication: Class<*>): Boolean {
    return UsernamePasswordAuthenticationToken::class.java.isAssignableFrom(authentication)
}

The UsernamePasswordAuthenticationToken is a concrete implementation of the Authentication interface designed for simple presentation of a username and password.

This implies that if the authentication object is the same type as UsernamePasswordAuthenticationToken or is a child of UsernamePasswordAuthenticationToken, then the authentication provider supports this type of authentication.

Next, let's examine the authenticate() method of the AuthenticationProvider.

The method receives an Authentication request object as a parameter and returns a fully authenticated object including credentials. If authentication is not successful, an AuthenticationException will be thrown.

In the case of the DaoAuthenticationProvider, the method attempts to retrieve user details using the UserDetailsService and uses the PasswordEncoder to verify if the password is valid. If everything checks out, it returns an Authentication object, an instance of UsernamePasswordAuthenticationToken, containing the user details.

In the next section, we'll see how to create a DaoAuthenticationProvider instance, configure it, and use it in the filter chain.

Using an authentication provider

Once you add the Spring Security dependency to your application, you'll need to log in with the default user and the password that is generated for you and displayed in the console.

But what if you want another user to log in?

You can achieve this by creating an instance of the DaoAuthenticationProvider and configuring it to use the InMemoryUserDetailsManager implementation of the UserDetailsService interface and the default BCryptPasswordEncoder as the PasswordEncoder.

The configuration below does exactly that. The password encoder is set in the constructor of the DaoAuthenticationProvider.

The authentication provider is appended to the list of providers used by the AuthenticationManager in line (1).

This configuration lets you log in with the user jane and the password j0n3.

@Configuration
class MySecurityConfiguration {

    @Bean
    @Throws(Exception::class) // The @Throws annotation is not mandatory in Kotlin, but it can be used for Java interoperability.
    fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http
            .authorizeHttpRequests { authz ->
                authz.anyRequest().authenticated()
            }
            .httpBasic(withDefaults())
            .authenticationProvider(authenticationProvider()) // (1)
        return http.build()
    }

    @Bean
    fun userDetailsService(): InMemoryUserDetailsManager {
        val user = User.withDefaultPasswordEncoder()
            .username("jane")
            .password("j0n3")
            .build()
        return InMemoryUserDetailsManager(user)
    }

    @Bean
    fun authenticationProvider(): DaoAuthenticationProvider {
        val provider = DaoAuthenticationProvider()
        provider.setUserDetailsService(userDetailsService())
        return provider
    }
}

Note that by defining our own beans, we are overriding the default configuration provided by Spring Security. This implies that the auto-configuration provided by Spring Security will not apply, as it steps aside in favor of our configuration.

Other implementations of the AuthenticationProvider interface

Besides the implementation we just explored, Spring Security offers numerous other implementations of the AuthenticationProvider interface:

  • RememberMeAuthenticationProvider authenticates a user based on a remember-me cookie.

  • OpenIDAuthenticationProvider authenticates a user using an OpenID token.

  • LdapAuthenticationProvider uses an LDAP server to authenticate a user.

  • JaasAuthenticationProvider uses the Java Authentication and Authorization Service (JAAS) to authenticate a user.

  • CasAuthenticationProvider uses JA-SIG Central Authentication Service (CAS) to authenticate a user.

  • ActiveDirectoryLdapAuthenticationProvider - is a specialized LDAP authentication provider that uses Active Directory configuration rules.

Conclusion

In this topic, you learned about authentication providers and how they factor into the Spring Security authentication architecture. You took a detailed look at the DaoAuthenticationProvider and its dependencies, and you learned how to configure and use it in the filter chain.

9 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo