Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

NPI – Frontegg – Security – (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

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.

November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Security footer banner
Comments are closed on this article!