1. Overview

The Spring Security Java configuration support provides us with a powerful fluent APIs – to define security mappings and rules for an application.

In this quick article, we’ll see how we can take this one step forward and actually define a custom configurer; this is an advanced and flexible way to introduce custom logic into a standard security configuration.

For our quick example here, we’ll add functionality that logs errors for authenticated users depending on a given list of error status codes.

2. A Custom SecurityConfigurer

To start defining our configurer, first we need to extend the AbstractHttpConfigurer class:

public class ClientErrorLoggingConfigurer 
  extends AbstractHttpConfigurer<ClientErrorLoggingConfigurer, HttpSecurity> {

    private List<HttpStatus> errorCodes;
    
    // standard constructors
    
    @Override
    public void init(HttpSecurity http) throws Exception {
        // initialization code
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
       http.addFilterAfter(
         new ClientErrorLoggingFilter(errorCodes), 
         FilterSecurityInterceptor.class);
    }
}

Here, the main method we need to override is the configure() method – which contains the security configuration that this configurer will apply to.

In our example, we’ve registered a new filter after the last Spring Security filter. Also, since we intend to log response status error codes, we’ve added an errorCodes List property that we can use to control the error codes that we’ll log.

We can also optionally add additional configuration in the init() method, which is executed before the configure() method.

Next, let’s define the Spring Security filter class that we register in our custom implementation:

public class ClientErrorLoggingFilter extends GenericFilterBean {

    private static final Logger logger = LogManager.getLogger(
      ClientErrorLoggingFilter.class);
    private List<HttpStatus> errorCodes;

    // standard constructor

    @Override
    public void doFilter(
      ServletRequest request, 
      ServletResponse response, 
      FilterChain chain) 
      throws IOException, ServletException {
        //...

        chain.doFilter(request, response);
    }
}

This is a standard Spring filter class which extends GenericFilterBean and overrides the doFilter() method. It has two properties representing the logger we’ll use to display messages and the List of errorCodes.

Let’s take a closer look at the doFilter() method:

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null) {
    chain.doFilter(request, response);
    return;
}
int status = ((HttpServletResponse) response).getStatus();
if (status < 400 || status >= 500) {
    chain.doFilter(request, response);
    return;
}
if (errorCodes == null) {
    logger.debug("User " + auth.getName() + " encountered error " + status);
} else {
    if (errorCodes.stream().anyMatch(s -> s.value() == status)) {
        logger.debug("User " + auth.getName() + " encountered error " + status);
    }
}

If the status code is a client error status code, meaning between 400 and 500, then we’ll check the errorCodes list.

If this is empty, then we’ll display any client error status code. Otherwise, we’ll first check if the error code is part of the given List of status codes.

3. Using the Custom Configurer

Now that we have our custom API, we can add it to the Spring Security configuration by defining the bean, then by using the apply() method of HttpSecurity:

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            //...
            .and()
            .apply(clientErrorLogging());
        return http.build();
    }

    @Bean
    public ClientErrorLoggingConfigurer clientErrorLogging() {
        return new ClientErrorLoggingConfigurer();
    }

}

We can also define the bean with a specific list of error codes we want to log:

@Bean
public ClientErrorLoggingConfigurer clientErrorLogging() {
    return new ClientErrorLoggingConfigurer(Arrays.asList(HttpStatus.NOT_FOUND)) ;
}

And that’s all! Now our Security Configuration will include the custom filter and display the log messages.

If we want the custom configurer to be added in by default, we can use the META-INF/spring.factories file:

org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = com.baeldung.dsl.ClientErrorLoggingConfigurer

And to disable it manually, we can then use the disable() method:

//...
.apply(clientErrorLogging()).disable();

4. Conclusion

In this quick tutorial, we focused on an advanced feature of the Spring Security configuration support – we’ve seen how to define our own, custom SecurityConfigurer.

As always, the full source code of the example can be found over on GitHub.


» 下一篇: Jinq与Spring