在这篇文章中,我想分享一下O&B的一个团队的经验教训。 他们正在使用带有Spring Security的Spring Boot。
默认情况下,Spring Security保护的所有内容都将通过以下HTTP标头发送到浏览器:
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
本质上,响应将永远不会被浏览器缓存。 尽管这似乎效率低下,但实际上有充分的理由要采用这种默认行为。 当一个用户注销时,我们不希望下一个登录的用户能够看到前一个用户的资源(如果缓存了,则可以这样做)。
默认情况下不缓存任何内容,并保留显式启用缓存是有意义的。 但是,如果不缓存任何内容,则不好,因为这会导致高带宽使用和缓慢的页面加载。
很好的是,在Spring Boot中启用静态内容缓存非常容易(即使使用Spring Security也是如此)。 只需配置一个缓存周期。 就是这样!
# Boot 2.x
spring.resources.cache.cachecontrol.max-age=14400# Boot 1.x
spring.resources.cache-period=14400
但是有一些陷阱! 对于某些版本,它不是那么简单! 让我进一步解释。
有几种方法可以返回内容:
- 通过Spring Boot自动配置的静态资源请求处理程序的静态内容
- 控制器方法返回视图名称(例如,解析为JSP)
- 返回
HttpEntity
(或ResponseEntity
)的控制器方法
启用静态内容缓存
第一个(提供静态内容)是通过配置所述属性(通常在如上所述的application.properties
中)来处理的。
通过HttpServletResponse设置
在第二种情况下,控制器处理程序方法可以选择通过HttpServletResponse
方法参数设置“ Cache-Control”标头。
@Controller
... class ... {@RequestMapping(...)public String ...(..., HttpServletResponse response) {response.setHeader("Cache-Control", "max-age=14400");return ...; // view name}
}
只要Spring Security不会覆盖它,它就可以工作。
通过HttpEntity / ResponseEntity设置
在第三种情况下,控制器处理程序方法可以选择设置返回的HTTP实体的“ Cache-Control”标头。
@Controller
... class ... {@RequestMapping(...)public ResponseEntity<...> ...(...) {return ResponseEntity.ok().cacheControl(...).body(...);}
}
只要Spring Security还没有编写自己的“ Cache-Control”头文件,它就可以工作。
引擎盖下
引擎盖下
要了解其工作时间和原因,以下是相关序列。
对于Spring Security Web 4.0.x,4.2.0和4.2.4及更高版本,将发生以下顺序:
- 如果不存在缓存头,则
HeaderWriterFilter
委托CacheControlHeadersWriter
编写“ Cache-Control”头(包括“ Pragma”和“ Expires”)。 - 调用控制器处理程序方法(如果匹配)。 该方法可以:
- 在
HttpServletResponse
明确设置标头。 - 或者,设置在返回的报头
HttpEntity
或ResponseEntity
(参照handleReturnValue()
的方法HttpEntityMethodProcessor
)。- 请注意,如果
HttpEntityMethodProcessor
还不存在 ,HttpEntityMethodProcessor
仅将头(来自HttpEntity
)写入实际响应。 这是一个问题,因为早在#1中,标头已经设置好了。
- 请注意,如果
- 在
- 如果没有控制器处理该请求,那么Spring Boot自动配置的静态资源请求处理程序将获得机会。 它尝试提供静态内容,并且如果配置为缓存,它将覆盖“ Cache-Control”标头(并清除“ Pragma”和“ Expires”标头的值,如果有的话)。 静态资源处理程序是
ResourceHttpRequestHandler
对象(请参阅其WebContentGenerator
基类中的applyCacheControl()
方法)。- 但是,在Spring Web MVC 4.2.5中,
WebContentGenerator
仅在不存在时才写入“ Cache-Control”标头! 。 这是一个问题,因为早在#1中,标头已经设置好了。 - 在Spring Web MVC 4.2.6及更高版本中,即使它已经存在,它也会添加“ Cache-Control”标头。 因此,即使已在#1中设置了标头也没有问题。
- 但是,在Spring Web MVC 4.2.5中,
使用Spring Security Web 4.1.x,4.2.5和更高版本(在Spring Boot 1.5.11中使用版本4.2.5),顺序已更改。 它是这样的:
- 调用控制器处理程序方法(如果匹配)。 该方法可以:
- 在
HttpServletResponse
明确设置标头。 - 或者,设置在返回的报头
HttpEntity
或ResponseEntity
(参照handleReturnValue()
的方法HttpEntityMethodProcessor
)。- 请注意,如果
HttpEntityMethodProcessor
还不存在 ,HttpEntityMethodProcessor
仅将头(来自HttpEntity
)写入实际响应。 没问题,因为尚未设置标题。
- 请注意,如果
- 在
- 如果没有控制器处理该请求,那么Spring Boot自动配置的静态资源请求处理程序将获得机会。 它尝试提供静态内容,并且如果配置为缓存,它将覆盖“ Cache-Control”标头(并清除“ Pragma”和“ Expires”标头的值,如果有的话)。
- 如果不存在缓存头,则
HeaderWriterFilter
委托CacheControlHeadersWriter
编写“ Cache-Control”头(包括“ Pragma”和“ Expires”)。- 没问题,因为如果已经设置了缓存头,它将不会覆盖。
工作版本
以上三种控制缓存的情况都在Spring Boot 1.5.11和Spring Boot 2.x中起作用。 但是,如果无法升级到那些版本,请参见以下类,并检查它是否具有您想要的行为(使用上述顺序):
-
HeaderWriterFilter
(请参阅doFilterInternal
方法) -
CacheControlHeadersWriter
(请参阅writeHeaders()
方法) -
WebContentGenerator
(请参阅applyCacheControl()
方法) -
HttpEntityMethodProcessor
(请参见handleReturnValue()
方法)
-
另外,请注意,Spring Security Web 4.2.5及更高版本将写入以下HTTP标头(即使已设置它们,也将其覆盖,例如,在控制器中):
- 通过
XContentTypeOptionsHeaderWriter
X-Content-Type-Options
- 通过
HstsHeaderWriter
Strict-Transport-Security
HstsHeaderWriter
- 通过
XFrameOptionsHeaderWriter
X-Frame-Options
- 通过
XXssProtectionHeaderWriter
X-XSS-Protection
- 通过
这是因为,与CacheControlHeadersWriter
不同,上述内容的标头编写器不会检查标头是否已经存在。 他们只需设置各自的HTTP标头即可。 请参考各自的标题编写器类,并发布#5193 。
另一种选择是让Spring Security忽略静态资源请求。 这样,配置的缓存周期将不会被覆盖。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/css/**", "/js/**");// If the above paths are served by the// Spring Boot auto-configured// static resource request handler,// and a cache period is specified,// then it will have a "Cache-Control"// HTTP header in its response.// And it would NOT get overwritten by Spring Security.}
}
目前为止就这样了。 希望这可以清除一切。
翻译自: https://www.javacodegeeks.com/2018/07/caching-spring-boot-spring-security.html