19

I need to disable the cache control headers in my Spring Security conf.

According to the documentation a simple http.headers.disable() should do it, but I still see the

Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Expires:0
Pragma:no-cache

headers in responses.

My current security config is:

http.antMatcher("/myPath/**") // "myPath" is of course not the real path
    .headers().disable()
    .authorizeRequests()
     // ... abbreviated
    .anyRequest().authenticated();

Things I've tried so far:

application.properties

I added the security.headers.cache=false line, but that made no difference.

Using a filter

I tried the following filter:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  chain.doFilter(request, new HttpServletResponseWrapper((HttpServletResponse) response) {
      @Override
      public void setHeader(String name, String value) {
        if (name.equalsIgnoreCase("Cache-Control")) {
          value = "";
        } else if (name.equalsIgnoreCase("Expires")) {
          value = "";
        } else if (name.equalsIgnoreCase("Pragma")) {
          value = "";
        }
        super.setHeader(name, value);
      }
  });
}

After adding logging I saw that this filter only writes the X-XSS-Protection header, all the cache headers are written somewhere later and this filter doesn't have access to "override" them. This happens even if I add this filter at the last position of the security filter chain.

Using an interceptor

I tried the following interceptor:

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    String requestUri = request.getRequestURI();
    response.setHeader("Cache-Control", "max-age=3600");
    response.setHeader("Expires", "3600");
    response.setHeader("Pragma", "");
}

This (quite predictably) just added the headers, meaning that the original no-cache headers still appear in addition to the ones added by the interceptor.

I'm at my wits end here. How do I get rid of the cache control header set by Spring security?

j0ntech
  • 1,158
  • 3
  • 13
  • 27
  • For me it worked thanks to your question, after adding: security.headers.cache=false to my application.properties – lloiacono Aug 23 '18 at 10:37

6 Answers6

5

It may be help :

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    // ...
    .headers()
        .defaultsDisabled()
        .cacheControl();
}
}

http://docs.spring.io/spring-security/site/docs/current/reference/html/headers.html#headers-cache-control

Colin Shah
  • 166
  • 3
  • 11
2

You’ll need a class that extends WebSecurityConfigurerAdapter with two overidden configure methods to configure the filter and the authentication provider. For example, the following works at a bare minimum:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfigDemo extends WebSecurityConfigurerAdapter {

    @Autowired
    private DemoAuthenticationProvider demoAuthenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {        

    // Prevent the HTTP response header of "Pragma: no-cache".
    http.headers().cacheControl().disable();

    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {        
        auth.authenticationProvider(demoAuthenticationProvider);        
    }    

}

You can also disabe Spring Security completely for public static resources as following (in the same class as above):

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/static/public/**");
}

This requires configuring two resource handlers to get cache control headers right:

@Configuration
public class MvcConfigurer extends WebMvcConfigurerAdapter
        implements EmbeddedServletContainerCustomizer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // Resources without Spring Security. No cache control response headers.
        registry.addResourceHandler("/static/public/**")
            .addResourceLocations("classpath:/static/public/");

        // Resources controlled by Spring Security, which
        // adds "Cache-Control: must-revalidate".
        registry.addResourceHandler("/static/**")
            .addResourceLocations("classpath:/static/")
            .setCachePeriod(3600*24);
    }
}
SkyWalker
  • 28,384
  • 14
  • 74
  • 132
1

So, I found the answer myself: I finally made the Cache-Control header to change its value by creating a new entry in my yml configuration file called spring.resources.cachePeriod and set it to a value different than 0. The bad thing is that all resources use this setting, so no way to make it different depending on the resource, as far as I know.

The answer to this question helped a lot.

Community
  • 1
  • 1
Pedro Madrid
  • 1,887
  • 1
  • 20
  • 32
1

I had this issue after enabling OpenId Connect through @EnableOAuth2Sso on my application class. After about six hours of debugging and reading through docs, it turned out that @EnableOAuth2Sso MUST be placed on WebSecurityConfigurerAdapter or custom settings will be overridden by default settings:

Good

// In Application.java
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

//In WebSecurity.java
@Configuration
@EnableOAuth2Sso  // <- This MUST be here, not above
public class WebSecurity extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().disable();
    }
}

Bad

// In Application.java
@SpringBootApplication
@EnableOAuth2Sso  // <- Will overwrite config below
public class Application {
    //...
}

@Configuration
public class WebSecurity extends WebSecurityConfigurerAdapter {
    //...
}
ikh
  • 2,336
  • 2
  • 19
  • 28
0

So I had a similar problem, wanting most of my REST endpoints to have the standard "don't cache me" headers Spring is injecting, but on one endpoint I want to insert my own.

Specifying your own in the HttpHeaders object you give to the ResponseEntry does not work.

What DOES WORK is to explicitly set the headers DIRECTLY on the HttpServletResponse.

Spring is setting "Cache-Control", "Pragma" and "Expires". The following demonstrates how to override and set for a 1 minute caching:

response.setHeader("Cache-Control", "max-age=60");
response.setHeader("Pragma", "");
HttpHeaders headers = new HttpHeaders();
headers.setExpires(System.currentTimeMillis()+60000);
return new ResponseEntity<>(body, headers, HttpStatus.OK);
BrianT.
  • 394
  • 1
  • 6
-2

You are right that using

http
    .headers().disable()
    ...

will disable your headers. If you only want cache control disabled, you can use the following:

http
    .headers()
        .cacheControl().disable()
        .and()
    ...

I have posted a sample that demonstrates this working along with a test.

My guess is the problem you are having is that you have multiple HttpSecurity configurations. Remember that if you have:

http
    .antMatchers("/myPath/**")
    ...

Only URLs that start with /myPath/ will be impacted. Additionally, if you have multiple HttpSecurity instances each HttpSecurity instance is considered in order and only the first HttpSecurity instance is used. For example if you have:

@Configuration
@Order(1)
public class MyPathAdminWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .antMatchers("/myPath/admin/**")
            .authorizeRequests()
                .anyRequest().hasRole("ADMIN");
    }
}

@Configuration
@Order(2)
public class MyPathWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .antMatchers("/myPath/**")
            .headers()
                .cacheControl().disable();
    }
}

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated();
    }
}

If you request /myPath/admin/abc

First MyPathAdminWebSecurityConfig is considered. Since /myPath/admin/ starts with /myPath/admin/ we will use MyPathAdminWebSecurityConfig and not consider any other configuration. This means you will expect to get headers back for this request.

SkyWalker
  • 28,384
  • 14
  • 74
  • 132
Rob Winch
  • 21,440
  • 2
  • 59
  • 76
  • Your assumption is wrong. I don't have multiple security configurations for the subpaths of an already existing configuration. I do have other security configs for other paths (not in anyway related to "/mypath"), but those shouldn't really be a concern, I don't need to disable the headers there. – j0ntech Mar 16 '16 at 05:54