Spring Security's ObjectPostProcessor

2025 / 07 / 04  •  Daniel Garnier-Moiroux

Spring Security executes a delicate balancing act: it provides “sane” defaults, that work well in most cases, while being extremely customizable. Customization mostly happens in the HttpSecurity builder you use to create a SecurityFilterChain (or in ServerHttpSecurity if you’re using webflux). An example would be to set the URL users are redirected to when they log out, like so:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  return http
    // ... your rules ...
    .logout(logout -> {
      logout.logoutSuccessUrl("/welcome");
    })
    .build();
}

But exposing every configurable knob in the HttpSecurity API would make it too verbose. Users would have a hard time getting started: the common options would be drowned in a sea of methods that they probably wouldn’t need. Some configuration can be achieved through beans, but that’s on a case-by-case basis. Refer to the excellent reference documentation to learn about all the available configuration for the very specific feature you are using. Sometimes, you won’t find exactly what you need, so the last resort way to modify the defaults from Spring Security is to use an ObjectPostProcessor.

The ObjectPostProcessor API

Spring Framework has a BeanPostProcessor concept, that allows to modify beans when they are created. This is useful, because the beans in your Spring application context may have been created by some code that you do not own, for example, by Spring Boot. With “post-processing”, you can modify them without having to create full beans yourself. Trust Spring Boot, but sprinkle some custom behavior on top.

Spring Security creates of objects that are not beans, they are not in the application context. They are not intended to be used by the rest of the application, and so they are not exposed. If you want to modify them, you can register an ObjectPostProcessor. It is an extremely simple interface, with a single method that takes an object O and returns ? extends O, so either the object itself, another instance of the same type, or a subclass. The generic structure is like so, say, for post-processing all AuthenticationProviders:

class MyObjectPostProcessor implements ObjectPostProcessor<AuthenticationProvider> {

  @Override
  public <O extends AuthenticationProvider> O postProcess(O object) {
    // transform the "object" with your code
    return object;
  }

}

Then Spring Security uses that class to transform the objects it creates, essentially doing:

someSpringSecurityObject = this.objectPostProcessor.postProcess(someSpringSecurityObject);

Spring Security uses that pattern everywhere.

When, and how to use ObjectPostProcessor

Let’s take a concrete example. Let’s say you want to add a custom “authentication result converter” to the class OAuth2LoginAuthenticationFilter. The specific details of this class, or what that converter does in practice, are not relevant to this example: they’re advanced features that very few users need, and the idea is to show you where the configuration hooks are. The OAuth2LoginAuthenticationFilter is created when you do .oauth2Login(...) on your security filter chain configuration. It has a .setAuthenticationResultConverter(...) method, that you can call … But for this, you need an instance of that object. You have two choices: create your own instance manually, or modify an instance that Spring Security creates for you.

If you were to do it manually, you’d have to do something like:

var filter = new OAuth2LoginAuthenticationFilter(
  clientRegistrationRepository,
  authorizedClientService
);
filter.setAuthenticationResultConverter(new MyCustomConverter());

This is fine, but where do you get those parameters for the constructor? You have to figure that out by yourself, and you’ll probably have to read a fair bit of Spring Security code to get it right. So instead of doing that, let’s modify the instance Spring Security creates for you, with a post-processor. First, let’s create a post-processor:

class OAuth2LoginFilterPostProcessor implements ObjectPostProcessor<OAuth2LoginAuthenticationFilter> {

  @Override
  public <O extends OAuth2LoginAuthenticationFilter> O postProcess(O filter) {
    filter.setAuthenticationResultConverter(new MyCustomConverter());
    return filter;
  }

}

That post-processor will ONLY change OAuth2LoginAuthenticationFilter instances, and will not transform anything else. To make it do its magic, you only need to register it:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  return http
      // ... your custom configuration ...
      .oauth2Login(oauth2 ->
          oauth2.withObjectPostProcessor(new OAuth2LoginFilterPostProcessor())
      )
      .build();
}

By registering it inside .oauth2Login, you scope it to only objects in that part of the configuration, and you’re sure that it won’t modify anything else.

Advanced usage: delegation

There’s one more fun thing you can do with post-processors. Sometimes, the object that Spring Security gives you does not have the extension point you need, but has a lot of logic you’d like to reuse. So you could create a subclass, but it is sometimes impossible because some Spring Security classes are still final, e.g. AuthorizedClientServiceOAuth2AuthorizedClientManager. Another problem with subclasses is that you would have to think about all the dependencies of those classes and figure out how they should be set up. For example, the authentication provider used for oauth2 login is OidcUserService, with a total of 5 private fields. That’s a lot to think about!

Much easier is to do composition - you create a class that implements the correct interface, grab whatever Spring Security has wired in for you, and do pre or post-processing in addition to what the base class does. For example, with our OidcUserService above:

public class CustomOidcUserService extends OidcUserService {

  private final OidcUserService delegate;

  public CustomOidcUserService(OidcUserService delegate) {
    this.delegate = delegate;
  }

  @Override
  public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
    // Modify the parameters passed to the delegate
    var modifiedUserRequest = modifyUserRequest(userRequest);

    // Call the delegate that was configured by Spring Security
    var user = this.delegate.loadUser(modifiedUserRequest);

    // Modify the result from the delegate
    var modifiedUser = modifyUser(user);

    return modifiedUser;
  }

}

You could imagine that you do some pre-processing for rate-limiting, or some post-processing for checking people logging in against their work schedule. To use that, you would write the following post-processor:

class OidcUserServicePostProcessor implements ObjectPostProcessor<OidcUserService> {

  @Override
  public OidcUserService postProcess(OidcUserService userService) {
    return new CustomOidcUserService(userService);
  }

}

Don’t forget to register it, and voilà, you benefit from some Spring Security goodness with your custom behavior added on top!