Course – LSS – NPI (cat=Spring Security)
announcement - icon

If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:

>> LEARN SPRING SECURITY

1. Overview

In this quick article, we’re going to focus on using multiple mechanisms to authenticate users in Spring Security.

We’ll do that by configuring multiple authentication providers.

2. Authentication Providers

An AuthenticationProvider is an abstraction for fetching user information from a specific repository (like a database, LDAP, custom third party source, etc. ). It uses the fetched user information to validate the supplied credentials.

Simply put, when multiple authentication providers are defined, the providers will be queried in the order they’re declared.

For a quick demonstration, we’ll configure two authentication providers – a custom authentication provider and an in-memory authentication provider.

3. Maven Dependencies

Let’s first add the necessary Spring Security dependencies into our web application:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

And, without Spring Boot:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>6.1.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>6.1.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>6.1.5</version>
</dependency>

The latest version of these dependencies can be found at spring-security-web, spring-security-core, and spring-security-config.

4. Custom Authentication Provider

Let’s now create a custom authentication provider by implementing the AuthneticationProvider interface.

We’re going to implement the authenticate method – which attempts the authentication. The input Authentication object contains the username and password credentials supplied by the user.

The authenticate method returns a fully populated Authentication object if the authentication is successful. If authentication fails, it throws an exception of type AuthenticationException:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication auth) 
      throws AuthenticationException {
        String username = auth.getName();
        String password = auth.getCredentials()
            .toString();

        if ("externaluser".equals(username) && "pass".equals(password)) {
            return new UsernamePasswordAuthenticationToken
              (username, password, Collections.emptyList());
        } else {
            throw new 
              BadCredentialsException("External system authentication failed");
        }
    }

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

Naturally, this is a simple implementation for the purpose of our example here.

5. Configuring Multiple Authentication Providers

Let’s now add the CustomAuthenticationProvider and an in-memory authentication provider to our Spring Security configuration.

5.1. Java Configuration

In our configuration class, let’s now create and add the authentication providers using the AuthenticationManagerBuilder.

First, the CustomAuthenticationProvider and then, an in-memory authentication provider by using inMemoryAuthentication().

We are also making sure that access to the URL pattern “/api/**” needs to be authenticated:

@Configuration
@EnableWebSecurity
public class MultipleAuthProvidersSecurityConfig {

    @Autowired
    CustomAuthenticationProvider customAuthProvider;

    @Bean
    public AuthenticationManager authManager(HttpSecurity http) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.authenticationProvider(customAuthProvider);
        authenticationManagerBuilder.inMemoryAuthentication()
            .withUser("memuser")
            .password(passwordEncoder().encode("pass"))
            .roles("USER");
        return authenticationManagerBuilder.build();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authManager, HandlerMappingIntrospector introspector) throws Exception {
        MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
        http.httpBasic(Customizer.withDefaults())
            .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
                    authorizationManagerRequestMatcherRegistry
                            .requestMatchers(PathRequest.toH2Console()).authenticated()
                            .requestMatchers(mvcMatcherBuilder.pattern("/api/**")).authenticated())
                .authenticationManager(authManager);
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

5.2. XML Configuration

Alternatively, if we want to use XML configuration instead of Java configuration:

<security:authentication-manager>
    <security:authentication-provider>
        <security:user-service>
            <security:user name="memuser" password="pass" 
              authorities="ROLE_USER" />
        </security:user-service>
    </security:authentication-provider>
    <security:authentication-provider
      ref="customAuthenticationProvider" />
</security:authentication-manager>

<security:http>
    <security:http-basic />
    <security:intercept-url pattern="/api/**" 
      access="isAuthenticated()" />
</security:http>

6. The Application

Next, let’s create a simple REST endpoint that is secured by our two authentication providers.

To access this endpoint, a valid username and password must be supplied. Our authentication providers will validate the credentials and determine whether to allow access or not:

@RestController
public class MultipleAuthController {
    @GetMapping("/api/ping")
    public String getPing() {
        return "OK";
    }
}

7. Testing

Finally, let’s now test the access to our secure application. Access will be allowed only if valid credentials are supplied:

@Autowired
private TestRestTemplate restTemplate;

@Test
public void givenMemUsers_whenGetPingWithValidUser_thenOk() {
    ResponseEntity<String> result 
      = makeRestCallToGetPing("memuser", "pass");

    assertThat(result.getStatusCode().value()).isEqualTo(200);
    assertThat(result.getBody()).isEqualTo("OK");
}

@Test
public void givenExternalUsers_whenGetPingWithValidUser_thenOK() {
    ResponseEntity<String> result 
      = makeRestCallToGetPing("externaluser", "pass");

    assertThat(result.getStatusCode().value()).isEqualTo(200);
    assertThat(result.getBody()).isEqualTo("OK");
}

@Test
public void givenAuthProviders_whenGetPingWithNoCred_then401() {
    ResponseEntity<String> result = makeRestCallToGetPing();

    assertThat(result.getStatusCodeValue()).isEqualTo(401);
}

@Test
public void givenAuthProviders_whenGetPingWithBadCred_then401() {
    ResponseEntity<String> result 
      = makeRestCallToGetPing("user", "bad_password");

    assertThat(result.getStatusCode().value()).isEqualTo(401);
}

private ResponseEntity<String> 
  makeRestCallToGetPing(String username, String password) {
    return restTemplate.withBasicAuth(username, password)
      .getForEntity("/api/ping", String.class, Collections.emptyMap());
}

private ResponseEntity<String> makeRestCallToGetPing() {
    return restTemplate
      .getForEntity("/api/ping", String.class, Collections.emptyMap());
}

8. Conclusion

In this quick tutorial, we’ve seen how multiple authentication providers can be configured in Spring Security. We have secured a simple application using a custom authentication provider and an in-memory authentication provider.

And we’ve also written tests to verify that the access to our application requires credentials that can be validated by at least one of our authentication providers.

As always, the full source code of the implementation can be found over on GitHub.

Course – LSS (cat=Security/Spring Security)

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE
res – Security (video) (cat=Security/Spring Security)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.