After answering a question on stackoverflow about how to configure Spring Security with your own authentication mechanism I’d like to go into more details in this post. I’ll implement a simple use case where the actual authentication is done by reading the username and password from the header of a request.

Important

The example method we implement in this post is NOT a secure way of implementing authentication. I just choose it for the sake of simplicity to demonstrate how to register your own authentication in Spring Security.

Source Code

A working example can be found on Github

Let’s first have a look in a successful test to understand how we do our authentication:

@Test
public void testAuthentication_validHeadersSet_status200() {
	// First we set headers that identifies us
	HttpHeaders headers = new HttpHeaders();
	headers.add("x-user", "Marcus Held");
	headers.add("x-password", "SuperSecret");
	
	// Then we do the request to the secured endpoint
	RequestEntity requestEntity = new RequestEntity(headers, HttpMethod.GET, URI.create("/secure"));
	ResponseEntity<String> response = testClient.exchange(requestEntity, String.class);

	// And this request should be processed by our server
	Assertions.assertThat(response.getStatusCode().value()).isEqualTo(200);
}

The flow we want to implement is the following:

blockdiag seqdiag { client -> DemoController [label = ”GET /secure”]; DemoController -> AuthenticationFilter; AuthenticationFilter -> AuthenticationFilter [label = “Read Headers”]; AuthenticationFilter -> InMemoryUserStore [label = “findByUsernameAndPassword”]; AuthenticationFilter <- InMemoryUserStore [label = “Principal”]; AuthenticationFilter -> SecurityContext [label = “setAuthentication”]; DemoController <- AuthenticationFilter; client <- DemoController; } client DemoController AuthenticationFilter InMemoryUserStore SecurityContext GET /secure Read Headers findByUsernameAndPassword Principal setAuthentication

When a request to a secured endpoint is received by our server the AuthenticationFilter should be processed and decide, based on the sent headers, if the associated user is authenticated to access the endpoint. Our DemoController doesn’t do anything interesting and is simply for demonstration purpose:

@GetMapping("secure")
@ResponseBody
public String securedEndpoint(Principal principal) {
    return principal.toString();
}

Spring Security Configuration

Spring Security provides the tools to secure specific endpoints precisely. This is done by implementing a WebSecurityConfigurerAdapter:

@EnableWebSecurity
public class SecurityConfig {

	@RequiredArgsConstructor
	@Configuration
	@Slf4j
	public static class UserWebSecurity extends WebSecurityConfigurerAdapter {

		private final> InMemoryUserStore userStore;

		@Override
		protected void configure(HttpSecurity http) throws Exception {
			/*
			 We need to decide where in the filter chain our filter is tested.
			 For our example we still want to support the default session functionality, 
			 so we put our own filter after the session filter. That way the
			 authentication by session is done before our own filter.
			 */
			http.addFilterAfter(new CustomAuthenticationFilter(userStore), ConcurrentSessionFilter.class);

			http.requestMatchers()
					.antMatchers("/**");

			http
					.authorizeRequests()
					.anyRequest().authenticated();
		}

	}
}

Within this configuration we are able to add custom Filter that gets registered by Spring Security in the Servlet Container. Spring already registers a bunch of security filters which are executed in a specific order:

  1. ChannelProcessingFilter, because it might need to redirect to a different protocol
  2. SecurityContextPersistenceFilter, so a SecurityContext can be set up in the SecurityContextHolder at the beginning of a web request, and any changes to the SecurityContext can be copied to the HttpSession when the web request ends (ready for use with the next web request)
  3. ConcurrentSessionFilter, because it uses the SecurityContextHolder functionality and needs to update the SessionRegistry to reflect ongoing requests from the principal
  4. Authentication processing mechanisms - UsernamePasswordAuthenticationFilter, CasAuthenticationFilter, BasicAuthenticationFilter etc - so that the SecurityContextHolder can be modified to contain a valid Authentication request token
  5. The SecurityContextHolderAwareRequestFilter, if you are using it to install a Spring Security aware HttpServletRequestWrapper into your servlet container
  6. The JaasApiIntegrationFilter, if a JaasAuthenticationToken is in the SecurityContextHolder this will process the FilterChain as the Subject in the JaasAuthenticationToken
  7. RememberMeAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, and the request presents a cookie that enables remember-me services to take place, a suitable remembered Authentication object will be put there
  8. AnonymousAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, an anonymous Authentication object will be put there
  9. ExceptionTranslationFilter, to catch any Spring Security exceptions so that either an HTTP error response can be returned or an appropriate AuthenticationEntryPoint can be launched
  10. FilterSecurityInterceptor, to protect web URIs and raise exceptions when access is denied

Within this chain we need to put our own Filter to a proper position. In this example we put it after the ConcurrentSessionFilter. That way we support session handling but if that’s not successful we authenticate by our own mechanism.

Filter Implementation

Our CustomAuthenticationFilter extends from GenericFilterBean which is registered as a bean automatically as soon as an implementation is found by Spring Boot. That bean gives us the possibility to execute code and our goal is to call SecurityContextHolder.getContext().setAuthentication(authentication) with a valid authentication. By doing so the request is authenticated. Keep in mind that this authentication is only valid for this very request and therefore a valid authentication must be set for every other request as well. That’s why we send the necessary header to authenticate with every request on a secured endpoint.

@RequiredArgsConstructor
public class CustomAuthenticationFilter extends GenericFilterBean {

	private final> InMemoryUserStore userStore;

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;

		/*
		For our example we simply read the user and password information from the header and check if against our
		internal user store.
		IMPORTANT: THIS IS NOT A SECURE WAY TO DO AUTHENTICATION AND JUST FOR DEMONSTRATION PURPOSE!
		 */
		// First read the custom headers
		String user = request.getHeader("x-user");
		String password = request.getHeader("x-password");

		// No we check if they are existent in our internal user store
		Optional<CustomPrincipal> optionalCustomPrincipal = userStore.findByUsernameAndPassword(user, password);

		// When they are present we authenticate the user in the SecurityContextHolder
		if (optionalCustomPrincipal.isPresent()) {
			CustomAuthentication authentication = new CustomAuthentication(optionalCustomPrincipal.get());
			SecurityContextHolder.getContext().setAuthentication(authentication);
		}

		// In either way we continue the filter chain to also apply filters that follow after our own.
		chain.doFilter(request, response);

	}
}

In our example we check if the user with the given credentials is present in our custom InMemoryUserStore and if so we set the authentication in the SecurityContextHolder.

And that’s it. You now successfully authenticated the request and it will be processed by Spring MVC. For a full example don’t forget to checkout the example project on Github.