Multiple Authentication Providers in 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>5.0.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>5.0.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.0.4.RELEASE</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:

@EnableWebSecurity
public class MultipleAuthProvidersSecurityConfig
  extends WebSecurityConfigurerAdapter {
    @Autowired
    CustomAuthenticationProvider customAuthProvider;

    @Override
    public void configure(AuthenticationManagerBuilder auth)
      throws Exception {

        auth.authenticationProvider(customAuthProvider);
        auth.inMemoryAuthentication()
            .withUser("memuser")
            .password(encoder().encode("pass"))
            .roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic()
            .and()
            .authorizeRequests()
            .antMatchers("/api/**")
            .authenticated();
    }


    @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.getStatusCodeValue()).isEqualTo(200);
    assertThat(result.getBody()).isEqualTo("OK");
}

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

    assertThat(result.getStatusCodeValue()).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.getStatusCodeValue()).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.

Leave a Reply

Your email address will not be published.