如果在Spring Security中使用@SessionAttributes,会覆盖Cache-Control头
在Spring Security中,有一个功能可以给HTTP响应头添加标头,同时还会设置与缓存相关的标头。然而,如果执行带有@SessionAttributes注解的处理时,会出现覆盖该标头的情况。
环境
- Spring Boot 2.1.0.RELEASE ( Spring Security 5.1.1.RELEASE )
根据Spring Security的不同版本,可能不会发生这种情况。
Spring Security 提供的缓存控制相关头信息
Spring Security默认配置会添加以下与缓存控制相关的标头。
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
确认事物
确认事件。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
对于认证过程,我们将忽略它,而是定义两种类型的控制器。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll();
}
}
@RestController
public class SampleController {
@GetMapping("/sample")
public String sample() {
return "sample";
}
}
@RestController
@SessionAttributes(value = "test")
public class SessionController {
@GetMapping("/session")
public String session() {
return "session";
}
}
}
当尝试发送请求时,会返回以下类型的头,并且在附有@SessionAttributes的Controller中,可以看到仅将Cache-Control设定为no-store。
$ curl -I http://localhost:8080/sample
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 6
Date: Mon, 19 Nov 2018 13:26:59 GMT
$ curl -I http://localhost:8080/session
HTTP/1.1 200
Cache-Control: no-store
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 7
Date: Mon, 19 Nov 2018 13:27:03 GMT
因由
如果添加了@SessionAttributes,那么在RequestMappingHandlerAdapter的handleInternal方法中,会执行添加Cache-Control头的操作。
Spring Security 在 HeaderWriterFilter 的 doFilterInternal 方法中添加标头,但这是在上述处理完成后执行的。
并且,实际上关于缓存控制的头部是由CacheControlHeadersWriter的writeHeaders方法添加的。
在此过程中,如果Cache-Control、Expires或Pragma中的任何一个已添加到头部,则不会执行添加头部的操作。
在这种情况下,由于已经添加了Cache-Control头部,所以会跳过处理,从而导致Spring Security默认设置的头部被覆盖。
对策
在查看了源代码后,我发现要在RequestMappingHandlerAdapter中不添加Cache-Control,只需将cacheSecondsForSessionAttributeHandlers设置为负值即可。
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Override
protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.setCacheSecondsForSessionAttributeHandlers(-1);
return adapter;
}
}
在设定了上述状态的情况下,调用被@SessionAttributes注解标记的处理,可以发现Spring Security默认设置的头部已经被设置。
$ curl -I http://localhost:8080/session
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 7
Date: Mon, 19 Nov 2018 13:57:31 GMT
总结
可以采取上述的设置措施,但我认为在像Apache或Nginx这样的Web服务器端添加头信息也可以。实际上,我觉得很少会有仅使用AP服务器运营的情况。
然而,Spring Security和Spring WebMVC之间微妙的差异行为感觉不舒服。
而且根据版本,有时候Spring Security会先于Spring WebMVC添加响应头,这种情况下就不会出现这样的问题。
例如,Spring Security 4.2.4.RELEASE。