11

I wish to enable HTTP caching for some static resources such as images, for which access is restricted by Spring Security. (These resources are not security critical, but shouldn't be publicly accessible either). How do I avoid having Spring Security add HTTP response headers that disable caching?

If I add setCachePeriod() into my resource handler registration in WebMvcConfigurerAdapter.addResourceHandlers() as following:

registry.addResourceHandler("/static/**")
  .addResourceLocations("classpath:/static/").setCachePeriod(3600);

The resources are still returned with following headers that disable caching:

Cache-Control: max-age=3600, must-revalidate
Expires: Mon, 04 Aug 2014 07:45:36 GMT
Pragma: no-cache

I want to avoid introducing any XML configuration into the project, which currently uses only Java annotation configuration.

Are there better solutions than extending the Spring resource handler?

Samuli Pahaoja
  • 2,660
  • 3
  • 24
  • 32
  • Even overwriting/implementing a resource handler will not help. Spring Security by default disables caching for secured resources. If you don't want this disable caching for those resources. This can be done using the `HttpSecurity` something like `http.antMatcher("/static/**").headers().disable()` disables all headers set by Spring Security. This is also explained [here](http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#default-security-headers) in the Spring Security Reference Guide. – M. Deinum Oct 28 '14 at 15:31
  • Thanks M. Deinum. A further problem is that the configuration already has an `antMatchers()` invocation as following: `http.authorizeRequests().antMatchers("/login", "/static/public/**").permitAll().anyRequest().authenticated()`. How do I apply the `headers().disable()` rule to "/static/**" without overriding the above `antMatchers()` rule? – Samuli Pahaoja Oct 29 '14 at 08:11
  • Doesn't matter, add another one or chain the multiple configs with `and()`. Something like `authenticated().and().headers().disabled()`. – M. Deinum Oct 29 '14 at 08:19
  • I want to disable Spring Security headers for "/static/**". I understand your suggestion would disable them either for all requests, or for those matching the existing `antMatchers()` rule. – Samuli Pahaoja Oct 29 '14 at 08:28
  • No... Your understanding isn't correct... Also you can always add an additional `antMatcher` element. They are all merged together. – M. Deinum Oct 29 '14 at 08:29
  • According to http://docs.spring.io/autorepo/docs/spring-security/current/apidocs/org/springframework/security/config/annotation/web/builders/HttpSecurity.html#antMatcher%28java.lang.String%29 "Invoking antMatcher(String) will override previous invocations of requestMatchers(), antMatcher(String), regexMatcher(String), and requestMatcher(RequestMatcher)." and this matches my experiments. Similarly for `requestMatchers()`. – Samuli Pahaoja Oct 29 '14 at 08:32
  • If they override you are still able to configure multiple things on it hence the `and()`. Also `/static/**` and `/static/public/**` shouldn't override (although you would have to take care in the ordering). – M. Deinum Oct 29 '14 at 08:49
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/63858/discussion-between-samuli-karkkainen-and-m-deinum). – Samuli Pahaoja Oct 29 '14 at 11:15
  • Did you find a working solution that can selectively disable spring security headers? I only managed to disable them all or none. – xtian Nov 12 '14 at 22:17
  • No, I didn't find a solution. – Samuli Pahaoja Nov 13 '14 at 01:49

3 Answers3

8

You can use webContentInterceptor resource to allow static resource caching.

<mvc:interceptors>
    <mvc:interceptor>
    <mvc:mapping path="/static/*"/>
        <bean id="webContentInterceptor" class="org.springframework.web.servlet.mvc.WebContentInterceptor">
            <property name="cacheSeconds" value="31556926"/>
            <property name="useExpiresHeader" value="true"/>
            <property name="useCacheControlHeader" value="true"/>
            <property name="useCacheControlNoStore" value="true"/>
        </bean>
   </mvc:interceptor>
</mvc:interceptors>

Using annotations to configure cache interceptors is done following way. In your web config class you can add a bean for WebContentInterceptor class and add it into interceptors list.

@Bean
public WebContentInterceptor webContentInterceptor() {
    WebContentInterceptor interceptor = new WebContentInterceptor();
    interceptor.setCacheSeconds(31556926);
    interceptor.setUseExpiresHeader(true);;
    interceptor.setUseCacheControlHeader(true);
    interceptor.setUseCacheControlNoStore(true);
    return interceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(webContentInterceptor());
}

Refer this site to see how it's done.

Jeevan Patil
  • 6,029
  • 3
  • 33
  • 50
1

Spring 4 documentation has this solution, "If you actually want to cache specific responses, your application can selectively invoke HttpServletResponse.setHeader(String,String) to override the header set by Spring Security". This is useful to ensure things like CSS, JavaScript, and images are properly cached.

Below snippet can be used for springmvc configuration,

@EnableWebMvc
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry
        .addResourceHandler("/resources/**")
        .addResourceLocations("/resources/")
        .setCachePeriod(31556926);
  }

// ...
}

For reference: http://docs.spring.io/spring-security/site/docs/4.0.0.CI-SNAPSHOT/reference/htmlsingle/#headers-cache-control

inanutshellus
  • 9,683
  • 9
  • 53
  • 71
SunilGiri
  • 71
  • 8
  • This does override the `max-age` part of the `Cache-Control` header. However it doesn't remove the `must-revalidate` part of that header, nor the `Pragma: no-cache` header. – Samuli Pahaoja Nov 04 '14 at 10:57
  • Other option mentioned in doc is setting header directly, HttpServletResponse.setHeader(String,String) – SunilGiri Nov 04 '14 at 11:16
  • Where do you propose calling setHeader()? I've tried various places, and every time Spring Security overrides the headers. Is installing my own servlet filter the only way? – Samuli Pahaoja Nov 04 '14 at 11:25
  • 2
    Just tried it and does not seems to work,My bad.It seems to be an open issue https://jira.spring.io/browse/SEC-2728 – SunilGiri Nov 04 '14 at 11:38
  • I just tried the solution mentioned here. Worked beautifully. https://stackoverflow.com/questions/29530575/disable-caching-for-specific-url-in-spring-security/30949227#30949227 – dipan66 Nov 15 '15 at 15:23
0

You are already setup for caching. must-revalidate means once the cache expires (3600 seconds) do not use it anymore so your response headers, i think, are correct for what you want.

Community
  • 1
  • 1
JimB
  • 1,143
  • 1
  • 12
  • 18
  • Good point that `must-revalidate` doesn't prevent caching. However the `must-revalidate` semantics isn't what one typically wants, and `Pragma: no-cache` often prevents caching anyway. – Samuli Pahaoja Nov 04 '14 at 21:01