如何在Spring Boot中启用HTTP响应缓存

73

我使用Spring Boot 1.0.2实现了一个REST服务器。我在防止Spring设置禁用HTTP缓存的HTTP头方面遇到了一些问题。

我的控制器如下:

@Controller
public class MyRestController {
    @RequestMapping(value = "/someUrl", method = RequestMethod.GET)
    public @ResponseBody ResponseEntity<String> myMethod(
            HttpServletResponse httpResponse) throws SQLException {
        return new ResponseEntity<String>("{}", HttpStatus.OK);
    }
}

所有HTTP响应都包含以下头部信息:

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

我尝试了以下方法来移除或更改这些头部信息:

  1. 在控制器中调用setCacheSeconds(-1)
  2. 在控制器中调用httpResponse.setHeader(“Cache-Control”,“max-age = 123”)
  3. 定义@Bean,返回WebContentInterceptor,并对其调用setCacheSeconds(-1)
  4. application.properties中将属性spring.resources.cache-period设置为-1或正值。

以上方法均未生效。如何在Spring Boot中禁用或更改所有或个别请求的这些头部信息?


1
我不认为Spring Boot可以做到这一点(在我尝试的任何示例中都没有)。也许你可以分享一个响应头中包含这些内容的最小项目? - Dave Syer
2
你是对的。罪魁祸首竟然是Spring Security。 - Samuli Pahaoja
9个回答

67

原来的HTTP头中有“no-cache”字段,这是由Spring Security设置的。这在http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#headers中有讨论。

以下代码可以禁用HTTP响应头中的Pragma: no-cache,但并不能解决问题:

import org.springframework.context.annotation.Configuration;
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;

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Prevent the HTTP response header of "Pragma: no-cache".
        http.headers().cacheControl().disable();
    }
}

我最终完全禁用了Spring Security以处理公共静态资源,具体做法如下(在上述同一类中):

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

这需要配置两个资源处理程序以正确获取缓存控制头:

@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);
    }
}

参见在Spring Boot & Spring Security应用程序中提供静态Web资源


4
如果你想为特定的控制器操作设置不同的Cache-Control头部,就没有必要完全关闭Spring Security,这在https://dev59.com/LI3da4cB1Zd3GeqPtwLO#36459244中有详细说明。 - aha
这帮助了我(cacheControl().disable())解决了一个问题,即@font-face在第一次加载时无法加载。 - vicmac

24

使用Spring Boot 2.1.1并额外使用Spring Security 5.1.1,可以有很多种方式进行HTTP缓存。

1. 对于在代码中使用resourcehandler的资源:

您可以通过这种方式添加自定义资源的扩展名。

registry.addResourceHandler

用于添加URI路径以获取资源

.addResourceLocations

用于设置文件系统中资源所在的位置(可以使用classpath相对路径,也可以使用file://绝对路径)。

.setCacheControl

用于设置缓存头信息(不难理解)。

资源链和解析器是可选的(在这种情况下与默认值完全相同)。

@Configuration
public class CustomWebMVCConfig implements WebMvcConfigurer {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/*.js", "/*.css", "/*.ttf", "/*.woff", "/*.woff2", "/*.eot",
            "/*.svg")
            .addResourceLocations("classpath:/static/")
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)
                    .cachePrivate()
                    .mustRevalidate())
            .resourceChain(true)
            .addResolver(new PathResourceResolver());
    }
}

2. 对于使用应用程序属性配置文件的资源

与上面相同,不包括特定的模式,但现在作为配置。 此配置适用于列出的静态位置中的所有资源。

spring.resources.cache.cachecontrol.cache-private=true
spring.resources.cache.cachecontrol.must-revalidate=true
spring.resources.cache.cachecontrol.max-age=31536000
spring.resources.static-locations=classpath:/static/

3. 在控制器层

在控制器方法中,响应(HttpServletResponse)以参数的形式注入。

no-cache, must-revalidate, private

getHeaderValue函数将以字符串形式输出缓存选项,例如:

response.setHeader(HttpHeaders.CACHE_CONTROL,
            CacheControl.noCache()
                    .cachePrivate()
                    .mustRevalidate()
                    .getHeaderValue());

11

可以通过以下方式覆盖特定方法的默认缓存行为:

@Controller
public class MyRestController {
    @RequestMapping(value = "/someUrl", method = RequestMethod.GET)
    public @ResponseBody ResponseEntity<String> myMethod(
            HttpServletResponse httpResponse) throws SQLException {
        return ResponseEntity.ok().cacheControl(CacheControl.maxAge(100, TimeUnit.SECONDS)).body(T)
    }
}

8
我发现了这个 Spring 扩展程序:https://github.com/foo4u/spring-mvc-cache-control
你只需要完成三个步骤。
第一步(pom.xml):
<dependency>
    <groupId>net.rossillo.mvc.cache</groupId>
    <artifactId>spring-mvc-cache-control</artifactId>
    <version>1.1.1-RELEASE</version>
    <scope>compile</scope>
</dependency>

第二步(WebMvcConfiguration.java):
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CacheControlHandlerInterceptor());
    }
}

第三步(控制器):

@Controller
public class MyRestController {

    @CacheControl(maxAge=31556926)
    @RequestMapping(value = "/someUrl", method = RequestMethod.GET)
    public @ResponseBody ResponseEntity<String> myMethod(
            HttpServletResponse httpResponse) throws SQLException {
        return new ResponseEntity<String>("{}", HttpStatus.OK);
    }
}

似乎依赖项 spring-mvc-cache-control 自 2015 年以来没有更新,并且存在多个已知漏洞,因为它引用了旧版的 Spring MVC :-/ - rjdkolb

4

CacheControl类是一个流畅构建器,可以轻松创建不同类型的缓存:

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

让我们在测试中访问该端点,并断言我们已经更改了标头:
given()
  .when()
  .get(getBaseUrl() + "/users/Michael")
  .then()
  .header("Cache-Control", "max-age=60");

0
如果您不关心静态资源是否经过身份验证,可以这样做:
import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toStaticResources;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
    @Override
    public void configure(WebSecurity webSecurity) throws Exception {
        webSecurity
                .ignoring()
                .requestMatchers(toStaticResources().atCommonLocations());
    }
...
}

并且在你的application.properties文件中:

spring.resources.cache.cachecontrol.max-age=43200

请参见 ResourceProperties.java 获取更多可设置的属性。


使用spring boot 2.1.1和spring security 5.1.1,即使在configure方法中未执行忽略操作,spring.resources.cache.cachecontrol.max-age = 43200也可以正常工作。由于application.properties中的此配置会覆盖资源的spring security缓存标头。需要注意的一点是,这不适用于favicon.ico,而是由spring security获取普通缓存标头。 - Merv

0
@Configuration
@EnableAutoConfiguration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/resources/")
                .setCachePeriod(31556926);

    }
}

5
仅适用于静态资源。 - Giovanni Toraldo

0

我遇到了类似的问题。我想要在浏览器中缓存一些动态资源(图片)。如果图片发生更改(不是很频繁),我会更改URI的一部分...这是我的解决方案。

    http.headers().cacheControl().disable();
    http.headers().addHeaderWriter(new HeaderWriter() {

        CacheControlHeadersWriter originalWriter = new CacheControlHeadersWriter();

        @Override
        public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
            Collection<String> headerNames = response.getHeaderNames();
            String requestUri = request.getRequestURI();
            if(!requestUri.startsWith("/web/eventImage")) {
                originalWriter.writeHeaders(request, response);
            } else {
               //write header here or do nothing if it was set in the code
            }       
        }
    });

2
请注意,代码可以缩短为:http.headers().cacheControl().disable(); http.headers().addHeaderWriter(new DelegatingRequestMatcherHeaderWriter( new NegatedRequestMatcher(new AntPathRequestMatcher("/web/eventImage")), new CacheControlHeadersWriter() )); - yankee
我们应该如何处理headerNames变量?它没有被使用。 - Derzu

0

我在我的控制器中使用了以下代码。

ResponseEntity.ok().cacheControl(CacheControl.maxAge(secondWeWantTobeCached, TimeUnit.SECONDS)).body(objToReturnInResponse);

请注意,响应将具有缓存控制头和我们要缓存的秒数值。但是,如果我们在地址栏中输入网址并按回车键,则请求将始终从Chrome发送到服务器。但是,如果我们从某个链接点击网址,浏览器将不会发送新的请求,而是从缓存中获取。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接