Course – LSS – NPI (cat=Spring Security)
announcement - icon

If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:

>> LEARN SPRING SECURITY

1. Introduction

In this article, we’ll explore how we can control HTTP caching with Spring Security.

We’ll demonstrate its default behavior, and also explain the reasoning behind it. We’ll then look at ways to change this behavior, either partially or completely.

2. Default Caching Behaviour

By using cache control headers effectively, we can instruct our browser to cache resources and avoid network hops. This decreases latency, and also the load on our server.

By default, Spring Security sets specific cache control header values for us, without us having to configure anything.

First, let’s setup Spring Security for our application:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity
public class SpringSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http.build();
    }
}

We’re overriding configure() to do nothing, this means that we won’t need to be authenticated to hit an endpoint, enabling us to focus on purely testing caching.

Next, let’s implement a simple REST endpoint:

@GetMapping("/default/users/{name}")
public ResponseEntity<UserDto> getUserWithDefaultCaching(@PathVariable String name) {
    return ResponseEntity.ok(new UserDto(name));
}

The resulting cache-control header will look like this:

[cache-control: no-cache, no-store, max-age=0, must-revalidate]

Finally, let’s implement a test which hits the endpoint, and assert what headers are sent in the response:

given()
  .when()
  .get(getBaseUrl() + "/default/users/Michael")
  .then()
  .header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate")
  .header("Pragma", "no-cache");

Essentially, what this means is that a browser will never cache this response.

Whilst this may seem inefficient, there is actually a good reason for this default behavior – If one user logs out and another one logs in, we don’t want them to be able to see the previous users resources. It’s much safer to not cache anything by default, and leave us to be responsible for enabling caching explicitly.

3. Overriding the Default Caching Behaviour

Sometimes we might be dealing with resources which we do want to be cached. If we are going to enable it, it would be safest to do on a per resource basis. This means any other resources will still not be cached by default.

To do this, let’s try overriding the cache control headers in a single handler method, by use of the CacheControl cache. The CacheControl class is a fluent builder, which makes it easy for us to create different types of caching:

@GetMapping("/users/{name}")
public ResponseEntity<UserDto> getUser(@PathVariable String name) { 
    return ResponseEntity.ok()
      .cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
      .body(new UserDto(name));
}

Let’s hit this endpoint in our test, and assert that we have changed the headers:

given()
  .when()
  .get(getBaseUrl() + "/users/Michael")
  .then()
  .header("Cache-Control", "max-age=60");

As we can see, we’ve overridden the defaults, and now our response will be cached by a browser for 60 seconds.

4. Turning Off the Default Caching Behavior

We can also turn off the default cache control headers of Spring Security altogether. This is quite a risky thing to do, and not really recommended. But, we if we really want to, then we can try it by creating a SecurityFilterChain bean:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.headers().disable();
    return http.build();
}

Now, let’s make a request to our endpoint again and see what response we get:

given()
  .when()
  .get(getBaseUrl() + "/default/users/Michael")
  .then()
  .headers(new HashMap<String, Object>());

As we can see, no cache headers have been set at all. Again, this is not secure but proves how we can turn off the default headers if we want to.

5. Conclusion

This article demonstrates how Spring Security disables HTTP caching by default and explains that this is because we do not want to cache secure resources. We’ve also seen how we can disable or modify this behavior as we see fit.

The implementation of all these examples and code snippets can be found in the GitHub project.

Course – LSS (cat=Security/Spring Security)

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE
res – Security (video) (cat=Security/Spring Security)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.