Spring Security: Authentication Provider

This blog post teaches us how to use AuthenticationProvider in Spring Security to validate authentication logic with different providers and shows how to create a custom Authentication provider.

The Role of AuthenticationProvider

The AuthenticationProvider is an interface in Spring Security that encapsulates the authentication logic. It is responsible for verifying the authenticity of an authentication request and, if successful, returning a fully populated Authentication object.

In Spring Security, AuthenticationProvider is an interface.
public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication);
    boolean supports(Class<?> authentication);
}
Spring security provides a different implementation of AuthenticationProvider. For example, DaoAuthenticationProvider, LdapAuthenticationProvider, etc. We can also write our own custom authentication provider using the implementing AuthenticationProvider interface. 

Core Responsibilities

Process Authentication Requests: It accepts an Authentication object containing credentials submitted by the user and attempts to authenticate these credentials.

Supports Different Authentication Types: By implementing multiple AuthenticationProviders, Spring Security can support a wide range of authentication mechanisms.

Flexible and Extensible: Developers can create custom AuthenticationProviders to incorporate any authentication mechanism into their Spring Security configuration.

Implementing Custom AuthenticationProvider

Implementing an AuthenticationProvider involves overriding two key methods: authenticate and supports. The authenticate method contains the custom logic for validating user credentials, while the supports method indicates the type of Authentication objects that this provider can process.

Example 1: Creating a Custom AuthenticationProvider (hardcoded credentials) 

Let's start by implementing an AuthenticationProvider that performs authentication checks against hardcoded credentials.
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        // Implement your authentication logic here
        if ("admin".equals(username) && "password".equals(password)) {
            return new UsernamePasswordAuthenticationToken(username, password, List.of(new SimpleGrantedAuthority("ROLE_ADMIN")));
        } else {
            throw new BadCredentialsException("Invalid username or password");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
In this example, the authenticate method checks if the provided credentials match the hardcoded username and password. If successful, it returns an Authentication object with the ROLE_ADMIN authority.

Example 2: Database Authentication

One more common use case is authenticating users against credentials stored in a database. This requires loading user details from the database and comparing them with the credentials provided during the login attempt.
@Component
public class CustomDatabaseAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserService userService; // A service that loads user details from the database

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        UserDetails user = userService.loadUserByUsername(username);
        if (user != null && password.equals(user.getPassword())) {
            return new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities());
        } else {
            throw new BadCredentialsException("Authentication failed for " + username);
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

Example 2: Configuring Spring Security to Use the Custom AuthenticationProvider 

After defining the custom AuthenticationProvider, the next step is to configure Spring Security to use it. This configuration is typically done in the security configuration class.
@Configuration
@EnableWebSecurity
@ComponentScan("com.baeldung.security")
public class SecurityConfig {

    @Autowired
    private CustomAuthenticationProvider authProvider;

    @Bean
    public AuthenticationManager authManager(HttpSecurity http) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder =
            http.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.authenticationProvider(authProvider);
        return authenticationManagerBuilder.build();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http.authorizeHttpRequests(request -> request.anyRequest()
                .authenticated())
            .httpBasic(Customizer.withDefaults())
            .build();
    }
}

Example 4: External Service Authentication 

Another use case involves authenticating against an external service, such as LDAP or a third-party OAuth provider. This requires sending the credentials to the external service and interpreting the response to authenticate the user.
public class ExternalServiceAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        // Logic to authenticate against an external service
        if (externalService.authenticate(username, password)) {
            return new UsernamePasswordAuthenticationToken(username, password, Collections.emptyList());
        } else {
            throw new BadCredentialsException("External authentication failed");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

Conclusion 

The AuthenticationProvider interface is a cornerstone of Spring Security's authentication framework, offering the flexibility and extensibility needed to accommodate various authentication mechanisms. By implementing custom AuthenticationProviders, developers can tailor the authentication process to their application's unique security needs, ensuring robust and effective protection. Whether you're working with standard username-password authentication, integrating with external systems, or devising a multi-factor authentication flow, the AuthenticationProvider provides the necessary hooks to secure your application comprehensively.

Comments