# Spring Security: Custom Pre-Authentication Flow

# Introduction

In one of my project setups, the authentication process is offloaded to Keycloak and set up with an [Open Policy Agent](https://www.openpolicyagent.org/) sidecar in a typical Kubernetes deployment. Any request that comes to the service, will be pre-authenticated, and all credentials/information are stored in the headers.

Since I'm using Spring Boot with Spring Security, I wanted to figure out if there's a way to integrate seamlessly within the application through Spring Security without a custom solution (i.e. writing custom filters). After some research, I figured that the most suitable way to do so is via [Pre-Authentication Scenarios](https://docs.spring.io/spring-security/reference/5.8/servlet/authentication/preauth.html#_abstractpreauthenticatedprocessingfilter) described in the documentation.

# Goal

The goal is to be able to use the header(s) provided, construct and build into an [Authentication](https://docs.spring.io/spring-security/site/docs/5.8.3/api/org/springframework/security/core/Authentication.html) object, specifically a [PreAuthenticatedAuthenticationToken](https://docs.spring.io/spring-security/site/docs/5.8.3/api/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationToken.html) object, which can then use for all security-related needs.

In this tutorial, I explore how to make use of [RequestHeaderAuthenticationFilter](https://docs.spring.io/spring-security/reference/5.8/servlet/authentication/preauth.html#_request_header_authentication_siteminder), an existing implementation provided by Spring Security, that relies on a header to identify and extract the username.

# Spring Stack

At the time of writing, I am using the latest iteration of Spring Boot 2.7.13 with Spring Security 5.8.4 to ensure minimal changes are required when upgrading to Spring Boot 3.x and Spring Security 6.x in the future.

# Brief Intro to Spring Security

Before I go any further, I want to briefly go through the architecture of Spring Security, specifically in this context.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688883135271/c8fe61fe-e7e2-4395-a728-3413906f83ef.png align="center")

* When a client sends an HTTP request to the web server
    
* It passes through several Servlet Filters, one of which is `DelegatingFilterProxy` (created by Spring) to bridge between Servlet Lifecycle and Spring ApplicationContext
    
* Spring Security creates a `FilterChainProxy` to support `SecurityFilterChain` where it can have multiple instances of `SecurityFilterChain`
    
* Each of the `SecurityFilterChain` can have one or more `Filter` registered, in this case, we are looking specifically at `RequestHeaderAuthenticationFilter` which extends `AbstractPreAuthenticatedProcessingFilter`
    

# Implementation

Although Spring comes with some existing implementation, it doesn't quite fit my use case as I have a custom `UserDetails` and I need to load my user permission from the database, hence I also need to write my custom `AuthenticationUserDetailsService` and so on. Fortunately, Spring is super flexible and extensible, hence, I can easily extend and write my implementation as needed.

Without further ado, let's see how to implement this.

## Initialize project

This project is generated from [start.spring.io](https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.7.11&packaging=jar&jvmVersion=17&groupId=com.bwgjoseph&artifactId=spring-security-custom-preauthentication-flow&name=spring-security-custom-preauthentication-flow&description=Spring%20Security%20Custom%20Pre-Authentication%20Flow&packageName=com.bwgjoseph.spring-security-custom-preauthentication-flow&dependencies=devtools,lombok,configuration-processor,web,security)

## Overwrite Spring Security version

Spring Boot 2.7.13 comes with Spring Security 5.7.9 by default, so I have to overwrite the version to use Spring Security 5.8.4.

In `pom.xml`, add the following under `properties` section

```xml
<properties>
	<spring-security.version>5.8.4</spring-security.version>
</properties>
```

## Start Application

This is an extracted portion of the logs when starting the application

```plaintext
2023-07-08 17:05:12.169  WARN 8820 --- [  restartedMain] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: 70e6538f-cc9f-4764-9959-cd1d2a6cd5b5

This generated password is for development use only. Your security configuration must be updated before running your application in production.

2023-07-08 17:05:12.500  INFO 8820 --- [  restartedMain] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@3f690985, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@458bfb19, org.springframework.security.web.context.SecurityContextPersistenceFilter@5e6593, org.springframework.security.web.header.HeaderWriterFilter@1cce17a1, org.springframework.security.web.csrf.CsrfFilter@238de12d, org.springframework.security.web.authentication.logout.LogoutFilter@36d43e9f, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@3c373438, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@7fd33069, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@6c864ee5, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5a740d9c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@329f9df5, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@27623b68, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@66e2dd0b, org.springframework.security.web.session.SessionManagementFilter@692b3e18, org.springframework.security.web.access.ExceptionTranslationFilter@13ca9f33, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1cd5e041]
```

It's quite difficult to see from the logs what filters are being applied, so let's see it from a different format.

```plaintext
Security filter chain: [
  DisableEncodeUrlFilter
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  CsrfFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  DefaultLoginPageGeneratingFilter
  DefaultLogoutPageGeneratingFilter
  BasicAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]
```

> A total of 16 default filters

Two key things to learn from the log:

1. Default Generated Password
    
2. Default SecurityFilterChain
    

Without writing a single line of code, the application is protected by default through a series of sensible defaults (with best practices) provided out of the box by Spring Security. Isn't that awesome?

> Read more of what's happening behind the scene in the [documentation](https://docs.spring.io/spring-security/reference/5.8/servlet/getting-started.html#servlet-hello-auto-configuration)

## Configure SecurityFilterChain

Create `WebSecurityConfig` class to define our own `SecurityFilterChain` and let's disable everything that is not required.

```java
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .formLogin(AbstractHttpConfigurer::disable)
            .httpBasic(AbstractHttpConfigurer::disable)
            .anonymous(AbstractHttpConfigurer::disable)
            // we don't need to enable csrf, as 1) no view, 2) no session cookie authn
            .csrf(AbstractHttpConfigurer::disable)
            // disable logout
            .logout(AbstractHttpConfigurer::disable)
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .build();
    }
}
```

Exclude `UserDetailsServiceAutoConfiguration` to prevent it from creating a default user and (generated) password via the `application.yaml`

```yaml
spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration
```

Run the application again, and the updated `SecurityFilterChain` looks like this now

```plaintext
Security filter chain: [
  DisableEncodeUrlFilter
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  SessionManagementFilter
  ExceptionTranslationFilter
]
```

> Down to 8 filters (50% less!)

Start the application again, and the log that states the generated password is now gone (disable it via `application.yaml`).

## Configure custom UserDetails

It is very common to have a custom version of `UserDetails`, and mine is no different.

```java
public class PreAuthUserDetails implements UserDetails {
    private final String username;
    private final List<GrantedAuthority> authorities;

    public PreAuthUserDetails(String username, Collection<? extends GrantedAuthority> authorities) {
        this.username = username;
        this.authorities = new ArrayList<>(authorities);
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return "N/A";
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}
```

## Configure RequestHeaderAuthenticationFilter

As mentioned, I want to reuse the existing implementation as much as possible and overwrite it *only* when necessary. So let's create a `RequestHeaderAuthenticationFilter` bean and configure it.

```java
@Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() {
    RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
    requestHeaderAuthenticationFilter.setPrincipalRequestHeader("X-User");
    requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(true);
    
    return requestHeaderAuthenticationFilter;
}
```

What `RequestHeaderAuthenticationFilter` does is extracting the `principal` from `X-User` header which will be used in `AbstractPreAuthenticatedProcessingFilter#doAuthenticate` to create `PreAuthenticatedAuthenticationToken` object.

> While writing this blog, I realize that RequestHeaderAuthenticationFilter is not registered as part of the Spring Security Filter Chain but as a ServletFilter. So I wrote a separate [post](https://bwgjoseph.com/why-requestheaderauthenticationfilter-is-not-registered-as-part-of-spring-security-filter-chain) to explain why

Start the application now, and the following error will surface

```plaintext
Caused by: java.lang.IllegalArgumentException: An AuthenticationManager must be set
        at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.28.jar:5.3.28]
        at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.afterPropertiesSet(AbstractPreAuthenticatedProcessingFilter.java:127) ~[spring-security-web-5.8.4.jar:5.8.4]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863) ~[spring-beans-5.3.28.jar:5.3.28]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800) ~[spring-beans-5.3.28.jar:5.3.28]
        ... 56 common frames omitted
```

This is because `RequestHeaderAuthenticationFilter` extends `AbstractPreAuthenticatedProcessingFilter` which requires an implementation of `AuthenticationManager` and this brings us to the next point.

## Register AuthenticationManager

An `AuthenticationManager` must be provided to perform the `authenticate` method. But before that, there are two key components (ProviderManager and AuthenticationProvider) we need to understand first.

> AuthenticationManager is the API that defines how Spring Security’s Filters perform authentication.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688809612303/2d723b62-9138-43c0-af87-ca2eee880e0d.png align="center")

### ProviderManager

> ProviderManager is the most commonly used implementation of AuthenticationManager

### AuthenticationProvider

> Multiple AuthenticationProviders can be injected into ProviderManager. Each AuthenticationProvider performs a specific type of authentication.

The explanation is taken directly from the documentation [here](https://docs.spring.io/spring-security/reference/5.8/servlet/authentication/architecture.html#servlet-authentication-authenticationmanager), [here](https://docs.spring.io/spring-security/reference/5.8/servlet/authentication/architecture.html#servlet-authentication-providermanager) and [here](https://docs.spring.io/spring-security/reference/5.8/servlet/authentication/architecture.html#servlet-authentication-authenticationprovider).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688884303224/4b35b6fc-bd4e-4f12-92d2-2c43e75c23fc.png align="center")

In short, for `RequestHeaderAuthenticationFilter` to perform `doAuthenticate`. It requires the implementation of `AuthenticationManager` which outsources the actual authentication through the `AuthenticationProvider`, which relies on `AuthenticationUserDetailsService` to create and return the `UserDetails` object.

Since I am using a custom `UserDetails` object, I will need to create a custom `AuthenticationUserDetailsService` to construct our `PreAuthUserDetails` object.

> There is no need to create custom `PreAuthenticatedAuthenticationProvider` class since it has all I needed

### Configure AuthenticationUserDetailsService

```java
public class PreAuthUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {

    @Override
    public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token) throws UsernameNotFoundException {
        // hard-coded authority
        List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority("ADMIN"));
        return this.buildUserDetails(token, authorities);
    }

    protected UserDetails buildUserDetails(PreAuthenticatedAuthenticationToken token, Collection<? extends GrantedAuthority> authorities) {
        return new PreAuthUserDetails(token.getName(), authorities);
    }

}
```

The nice thing about this is that it doesn't care how the user details object is constructed. It could be loaded from the database, hard-coded, or anything as long it returns the `UserDetails` object.

For this, I will create a hard-coded authority first. In the later part of the blog, I will explore how to get this from the headers.

### Update WebSecurityConfig

With all the necessary classes created, and configured. It is time to update the `WebSecurityConfig` class to reflect the changes I've made thus far.

```java
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfig {

    @Bean // 1
    public AuthenticationProvider authenticationProvider() {
        PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
        provider.setPreAuthenticatedUserDetailsService(new PreAuthUserDetailsService());

        return provider;
    }

    @Bean // 2
    public AuthenticationManager authenticationManager(AuthenticationProvider authenticationProvider) {
        return new ProviderManager(authenticationProvider);
    }

    @Bean // 3
    public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter(AuthenticationManager authenticationManager) {
        RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
        requestHeaderAuthenticationFilter.setPrincipalRequestHeader("X-User");
        requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(true);
        requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager);

        return requestHeaderAuthenticationFilter;
    }

    @Bean // 4
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .formLogin(AbstractHttpConfigurer::disable)
            .httpBasic(AbstractHttpConfigurer::disable)
            .anonymous(AbstractHttpConfigurer::disable)
            // we don't need to enable csrf, as 1) no view, 2) no session cookie authn
            .csrf(AbstractHttpConfigurer::disable)
            // disable logout
            .logout(AbstractHttpConfigurer::disable)
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .build();
    }
}
```

1. Register `AuthenticationProvider` bean and setting `PreAuthUserDetailsService`
    
2. Register `AuthenticationManager` bean
    
3. Update `RequestHeaderAuthenticationFilter` bean to set `AuthenticationManager`
    
4. No change
    

> This is in line with what was drawn in the diagram above on the dependency between each class

At this stage, I have everything ready and the application should also start up just fine. Next, I will write some tests to verify if this is indeed working as intended.

## Verification

### Endpoint

Write a simple endpoint that returns `UserDetails`.

```java
@RestController
public class MeController {
    
    @GetMapping("/me")
    public UserDetails me(@AuthenticationPrincipal UserDetails userDetails) {
        return userDetails;
    }
}
```

> `@AuthenticationPrincipal` helps to resolve the current authenticated user

The controller exposes an HTTP GET API endpoint (`/me`) that returns the authenticated user details.

### Test Case

Then write a test to verify the interaction.

```java
import org.springframework.http.MediaType;

@WebMvcTest(MeController.class)
@Import(WebSecurityConfig.class)
class MeControllerTests {
    @Autowired
    private MockMvc mockMvc;

    @Test
    void whenCallMe_shouldGetValidResponse2() throws Exception {
        this.mockMvc
            .perform(MockMvcRequestBuilders
                .get("/me")
                .header("X-User", "joseph"))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
            .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("joseph"))
            .andExpect(MockMvcResultMatchers.jsonPath("$.authorities").isArray())
            .andExpect(MockMvcResultMatchers.jsonPath("$.authorities[0].authority").value("ADMIN"))
            .andDo(MockMvcResultHandlers.print());
    }
}
```

> Note that for sliced tests such as `@WebMvcTest`, we need to import our security configuration since Spring Boot 2.7.x due to the [deprecated WebSecurityConfigurerAdapter](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes#migrating-from-websecurityconfigureradapter-to-securityfilterchain).

> To send the request via curl
> 
> ```bash
> curl localhost:8080/me -H "X-User: joseph"
> ```

The test does the following

* Configure `mockMvc` to simulate an HTTP GET request to `/me` with `X-User` header
    
* Assert the (JSON) response contains the username and authorities
    
* Print out the log
    

Here's the log of the request after the test case is ran

```markdown
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /me
       Parameters = {}
          Headers = [X-User:"joseph"]
             Body = null
    Session Attrs = {SPRING_SECURITY_CONTEXT=SecurityContextImpl [Authentication=PreAuthenticatedAuthenticationToken [Principal=PreAuthUserDetails(username=joseph, authorities=[ADMIN]), Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ADMIN]]]}

Handler:
             Type = com.bwgjoseph.springsecuritycustompreauthenticationflow.MeController
           Method = com.bwgjoseph.springsecuritycustompreauthenticationflow.MeController#me(UserDetails)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = application/json
             Body = {"username":"joseph","authorities":[{"authority":"ADMIN"}],"enabled":true,"credentialsNonExpired":true,"accountNonExpired":true,"accountNonLocked":true,"password":"N/A"}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
```

It works! Of course, it does, I've spent quite some time writing this blog post, ensuring that it works, so why wouldn't it? 🤣🤣

## Configure AuthenticationDetailsSource

But.... is that all? Not quite. Remember that I hard-coded the authorities to `ADMIN` in our existing implementation? Now, how can we get the value from the other header key? Knowing that `RequestHeaderAuthenticationFilter` supports only getting one header value, which is the username/principal. So how do we extract the additional headers from the request headers, and use it?

For this, I need to create a custom implementation of `AuthenticationDetailsSource` and `WebAuthenticationDetails` and pass it to `RequestHeaderAuthenticationFilter`. Before that, let's try to understand why I need to do so.

If we look at the current implementation of [AbstractPreAuthenticatedProcessingFilter](https://github.com/spring-projects/spring-security/blob/main/web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java#L197-L199) and zoom into the following code

```java
PreAuthenticatedAuthenticationToken authenticationRequest = new PreAuthenticatedAuthenticationToken(principal, credentials);
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
```

We can learn that there is a property - `details` that we can store any information as long as it is an `Object` type through `AuthenticationDetailsSource`. Knowing this, I can then use it to store the additional headers information through the custom implementation of `AuthenticationDetailsSource` and provide it to `RequestHeaderAuthenticationFilter`.

```java
public class PreAuthAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {

    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new PreAuthenticationDetails(context);
    }

}
```

The key here is the implementation of `PreAuthenticationDetails` where it will grab the header from the request.

```java
@EqualsAndHashCode(callSuper = true)
public class PreAuthenticationDetails extends WebAuthenticationDetails implements GrantedAuthoritiesContainer {
    private static final String HEADER_AUTHORITY = "X-Authorities";
    private final List<String> authorities;

    public PreAuthenticationDetails(HttpServletRequest request) {
        super(request);
        this.authorities = List.of(request.getHeader(HEADER_AUTHORITY).split(","));
    }

    @Override
    public Collection<? extends GrantedAuthority> getGrantedAuthorities() {
        return this.authorities.stream().map(SimpleGrantedAuthority::new).toList();
    }

}
```

1. In addition to extending `WebAuthenticationDetails`, I'm also implementing `GrantedAuthoritiesContainer` to indicate that this object (`PreAuthenticationDetails`) can be used to obtain user authorities
    
2. I define the header to be of `X-Authorities` and expect it to be a comma-separated string format
    

This is just an example of how I can get additional headers value. The number of headers or the format of the headers is entirely dependent on the implementor.

> Ensure that proper validation is done while parsing the header such as checking if the key exists, the value is null, isBlank and so on

### Update PreAuthUserDetailsService

With that, I will be getting the authorities from the `PreAuthenticationDetails` instead of using the hard-coded values.

```java
public class PreAuthUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {

    @Override
    public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token) throws UsernameNotFoundException {
        // update to get the details from token, instead of hardcoded value
        PreAuthenticationDetails details = (PreAuthenticationDetails) token.getDetails();
        return this.buildUserDetails(token, details.getGrantedAuthorities());
    }

    protected UserDetails buildUserDetails(PreAuthenticatedAuthenticationToken token, Collection<? extends GrantedAuthority> authorities) {
        return new PreAuthUserDetails(token.getName(), authorities);
    }
}
```

### Update WebSecurityConfig

Lastly, I need to update the `WebSecurityConfig` class again to configure `RequestHeaderAuthenticationFilter` to use my custom `AuthenticationDetailsSource` class.

```java
@Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter(AuthenticationManager authenticationManager) {
    RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
    requestHeaderAuthenticationFilter.setPrincipalRequestHeader("X-User");
    requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(true);
    requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager);
    // add this line
    requestHeaderAuthenticationFilter.setAuthenticationDetailsSource(new PreAuthAuthenticationDetailsSource());

    return requestHeaderAuthenticationFilter;
}
```

### Update Test Case

I also need to update my test case to include the additional header and assertions.

```java
@Test
void whenCallMe_shouldGetValidResponse() throws Exception {
    this.mockMvc
        .perform(MockMvcRequestBuilders
            .get("/me")
            .header("X-User", "joseph")
            .header("X-Authorities", "moderator,user"))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
        .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("joseph"))
        .andExpect(MockMvcResultMatchers.jsonPath("$.authorities").isArray())
        .andExpect(MockMvcResultMatchers.jsonPath("$.authorities[0].authority").value("moderator"))
        .andExpect(MockMvcResultMatchers.jsonPath("$.authorities[1].authority").value("user"))
        .andDo(MockMvcResultHandlers.print());
}
```

The output of the test is as follows

```markdown
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /me
       Parameters = {}
          Headers = [X-User:"joseph", X-Authorities:"moderator,user"]
             Body = null
    Session Attrs = {SPRING_SECURITY_CONTEXT=SecurityContextImpl [Authentication=PreAuthenticatedAuthenticationToken [Principal=PreAuthUserDetails(username=joseph, authorities=[moderator, user]), Credentials=[PROTECTED], Authenticated=true, Details=PreAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[moderator, user]]]}

Handler:
             Type = com.bwgjoseph.springsecuritycustompreauthenticationflow.MeController
           Method = com.bwgjoseph.springsecuritycustompreauthenticationflow.MeController#me(UserDetails)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = application/json
             Body = {"username":"joseph","authorities":[{"authority":"moderator"},{"authority":"user"}],"enabled":true,"password":"N/A","accountNonExpired":true,"credentialsNonExpired":true,"accountNonLocked":true}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
```

> To send the request via curl
> 
> ```bash
> curl localhost:8080/me -H "X-User: joseph" -H "X-Authorities: moderator,user"
> ```

With that, I have everything I needed for the entire implementation.

# What's next?

I intend to build and package the implementation into a `spring-boot-starter` library shortly, and then open-source it internally within my organization so that other projects can benefit from it and use it with zero-configuration.

# Conclusion

In summary, we have looked at

* Spring Security Architecture
    
    * Understanding core components (`AuthenticationManager, Filter Chain, ProviderManager, AuthenticationProvider`)
        
* Configuring `SecurityFilterChain`
    
* Custom implementation of the various classes (`PreAuthUserDetailsService, PreAuthAuthenticationDetailsSource, PreAuthenticationDetails`) when the default implementation doesn't quite suit our needs
    
* Digging into some of the internal implementations of Spring Security
    
* Wiring up everything together
    

Documenting the various insights and digging into some of the internal implementations helps me to learn that it is not that difficult to implement it. And finally, allow me to gain more insights into Spring Security Architecture which has been quite elusive to me for the longest time. This should also serve as a note for myself when I start to forget about the details in the future!

# **Source Code**

As usual, the full source code is available on [**GitHub**](https://github.com/bwgjoseph/tutorials/tree/main/spring-security-custom-preauthentication-flow)

# Reference

* [https://stackoverflow.com/questions/65207826/abstractauthenticationprocessingfilter-triggered-no-matter-what-securityfilterch](https://stackoverflow.com/questions/65207826/abstractauthenticationprocessingfilter-triggered-no-matter-what-securityfilterch)
    
* [https://docs.spring.io/spring-security/reference/5.8/servlet/authentication/architecture.html](https://docs.spring.io/spring-security/reference/5.8/servlet/authentication/architecture.html)
