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>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.2.2.RELEASE</version> </dependency>
The latest version of these dependencies can be found at spring-security-web, spring-security-cor, 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("pass") .roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic() .and() .authorizeRequests() .antMatchers("/api/**") .authenticated(); } }
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 { @RequestMapping("/api/ping") public String getPing() { return "OK"; } }
6. 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()); }
7. 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.
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.