安全的HTTP相应头
21.1 默认安全头
Spring Security 允许用户轻松注入默认安全标头以帮助保护他们的应用程序。Spring Security 默认包含以下标头:
缓存控制:无缓存,无存储,最大年龄 = 0,必须重新验证 Pragma:无缓存 过期:0 X-Content-Type-Options:nosniff Strict-Transport-Security:max-age = 31536000 ; includeSubDomains X-Frame-Options: DENY X-XSS-Protection: 1; 模式=块
Strict-Transport-Security 仅在 HTTPS 请求上添加 |
有关每个标头的更多详细信息,请参阅相应部分:
虽然这些标头中的每一个都被认为是最佳实践,但应该注意并非所有客户端都使用这些标头,因此鼓励进行额外的测试。
您可以自定义特定的标题。例如,假设您希望 HTTP 响应标头如下所示:
缓存控制:无缓存,无存储,最大年龄= 0,必须重新验证编译 指示:无缓存 过期:0 X-Content-Type-Options:nosniff X-Frame-Options:SAMEORIGIN X-XSS-Protection : 1; 模式=块
具体来说,您需要所有具有以下自定义的默认标头:
- X-Frame-Options允许来自同一域的任何请求
- 不会将HTTP 严格传输安全 (HSTS)添加到响应中
您可以使用以下 Java 配置轻松完成此操作:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .frameOptions().sameOrigin() .httpStrictTransportSecurity().disable(); } }
或者,如果您使用的是 Spring Security XML 配置,则可以使用以下内容:
<http>
<!-- ... -->
<headers>
<frame-options policy = "SAMEORIGIN" />
<hsts disable = "true" />
</headers>
</http>
如果您不想添加默认值并希望明确控制应使用的内容,则可以禁用默认值。下面提供了基于 Java 和 XML 的配置示例:
如果您使用的是 Spring Security 的 Java Configuration,则以下内容只会添加Cache Control。
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() // 不要使用任何默认标头,除非明确列出 .defaultsDisabled() .cacheControl(); } }
下面的 XML 只会添加Cache Control。
<http>
<!-- ... -->
<headers defaults-disabled = "true" >
<cache-control/>
</headers>
</http>
如有必要,您可以使用以下 Java 配置禁用所有 HTTP 安全响应标头:
@EnableWebSecurity 公共 类WebSecurityConfig扩展 WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers().disable(); } }
如有必要,您可以使用以下 XML 配置禁用所有 HTTP 安全响应标头:
<http>
<!-- ... -->
<headers disabled = "true" />
</http>
过去 Spring Security 要求您为 Web 应用程序提供自己的缓存控制。这在当时似乎是合理的,但浏览器缓存已经发展到包括用于安全连接的缓存。这意味着用户可以查看经过身份验证的页面、注销,然后恶意用户可以使用浏览器历史记录来查看缓存页面。为了帮助缓解这种情况,Spring Security 添加了缓存控制支持,它将在您的响应中插入以下标头。
缓存控制:无缓存,无存储,max-age=0,必须重新验证 Pragma:无缓存 过期:0
简单地添加没有子元素的<headers>元素将自动添加缓存控制和许多其他保护。但是,如果您只需要缓存控制,则可以使用带有<cache-control > 元素和headers@defaults-disabled属性的 Spring Security 的 XML 命名空间来启用此功能。
<http>
<!-- ... -->
<headers defaults-disable = "true" >
<cache-control />
</headers>
</http>
同样,您可以使用以下命令在 Java 配置中仅启用缓存控制:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .defaultsDisabled() .cacheControl(); } }
如果你真的想缓存特定的响应,你的应用程序可以有选择地调用HttpServletResponse.setHeader(String,String)来覆盖 Spring Security 设置的标头。这有助于确保正确缓存 CSS、JavaScript 和图像等内容。
使用 Spring Web MVC 时,这通常在您的配置中完成。例如,以下配置将确保为所有资源设置缓存标头:
@EnableWebMvc 公共 类WebMvcConfiguration实现WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry .addResourceHandler( "/resources/**" ) .addResourceLocations( "/resources/" ) .setCachePeriod( 31556926 ); } // ... }
历史上,包括 Internet Explorer 在内的浏览器会尝试使用内容嗅探来猜测请求的内容类型。这允许浏览器通过猜测未指定内容类型的资源的内容类型来改善用户体验。例如,如果浏览器遇到没有指定内容类型的 JavaScript 文件,它可以猜测内容类型然后执行它。
当允许上传内容时,还有许多其他事情应该做(即仅在不同的域中显示文档、确保设置了 Content-Type 标头、清理文档等)。但是,这些措施超出了 Spring Security 提供的范围。同样重要的是要指出,在禁用内容嗅探时,您必须指定内容类型才能正常工作。 |
内容嗅探的问题在于,这允许恶意用户使用多语言(即一个文件作为多种内容类型有效)来执行 XSS 攻击。例如,某些站点可能允许用户向网站提交有效的 postscript 文档并进行查看。恶意用户可能会创建一个也是有效 JavaScript 文件的 postscript 文档,并使用它执行 XSS 攻击。
可以通过在我们的响应中添加以下标头来禁用内容嗅探:
X 内容类型选项:nosniff
与缓存控制元素一样,在使用不带子元素的 <headers> 元素时默认添加 nosniff 指令。但是,如果你想更好地控制添加哪些标头,你可以使用<content-type-options > 元素和headers@defaults-disabled属性,如下所示:
<http>
<!-- ... -->
<headers defaults-disabled = "true" >
<content-type-options />
</headers>
</http>
X-Content-Type-Options 标头默认添加到 Spring Security Java 配置中。如果你想更多地控制标题,你可以使用以下内容明确指定内容类型选项:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .defaultsDisabled() .contentTypeOptions(); } }
当您输入银行网站时,您是输入 mybank.example.com 还是输入https://mybank.example.com?如果省略 https 协议,您可能容易受到中间人攻击。即使网站执行重定向到https://mybank.example.com,恶意用户也可以拦截初始 HTTP 请求并操纵响应(即重定向到https://mibank.example.com并窃取他们的凭据)。
许多用户忽略了 https 协议,这就是创建HTTP 严格传输安全 (HSTS)的原因。一旦将 mybank.example.com 添加为HSTS 主机,浏览器就可以提前知道对 mybank.example.com 的任何请求都应解释为https://mybank.example.com。这大大降低了中间人攻击发生的可能性。
根据RFC6797,HSTS 标头仅注入到 HTTPS 响应中。为了让浏览器确认标头,浏览器必须首先信任签署用于建立连接的 SSL 证书(而不仅仅是 SSL 证书)的 CA。 |
将站点标记为 HSTS 主机的一种方法是将主机预加载到浏览器中。另一种方法是将“Strict-Transport-Security”标头添加到响应中。例如,以下内容将指示浏览器将域视为一年的 HSTS 主机(一年大约有 31536000 秒):
严格传输安全:max-age=31536000;包括子域
可选的 includeSubDomains 指令指示 Spring Security 子域(即 secure.mybank.example.com)也应被视为 HSTS 域。
与其他标头一样,Spring Security 默认添加 HSTS。您可以使用<hsts > 元素自定义 HSTS 标头,如下所示:
<http>
<!-- ... -->
<headers>
<hsts
include-subdomains = "true"
max-age-seconds = "31536000" />
</headers>
</http>
同样,您可以使用 Java 配置仅启用 HSTS 标头:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .httpStrictTransportSecurity() .includeSubdomains(true) .maxAgeSeconds( 31536000 ); } }
HTTP 公钥固定 (HPKP) 是一种安全功能,它告诉 Web 客户端将特定的加密公钥与特定的 Web 服务器相关联,以防止使用伪造证书的中间人 (MITM) 攻击。
为确保 TLS 会话中使用的服务器公钥的真实性,该公钥被包装到 X.509 证书中,该证书通常由证书颁发机构 (CA) 签名。浏览器等 Web 客户端信任很多这些 CA,它们都可以为任意域名创建证书。如果攻击者能够破坏单个 CA,他们就可以对各种 TLS 连接执行 MITM 攻击。HPKP 可以通过告诉客户端哪个公钥属于某个 Web 服务器来规避 HTTPS 协议的这种威胁。HPKP 是一种首次使用信任 (TOFU) 技术。Web 服务器第一次通过特殊的 HTTP 标头告诉客户端哪些公钥属于它,客户端会在给定的时间段内存储此信息。当客户端再次访问服务器时,它需要一个包含公钥的证书,该公钥的指纹已通过 HPKP 获知。如果服务器提供了未知的公钥,客户端应该向用户发出警告。
因为用户代理需要根据 SSL 证书链验证引脚,所以 HPKP 标头仅注入到 HTTPS 响应中。 |
为您的网站启用此功能非常简单,只需在通过 HTTPS 访问您的网站时返回 Public-Key-Pins HTTP 标头即可。例如,以下将指示用户代理仅向给定 URI(通过report-uri指令)报告 2 个引脚的引脚验证失败:
仅公钥密码报告:max-age=5184000;pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; report-uri="https://example.net/pkp-report" ; 包括子域
pin验证失败报告是一种标准的 JSON 结构,可以由 Web 应用程序自己的 API 或由公共托管的 HPKP 报告服务(例如REPORT-URI)捕获。
可选的 includeSubDomains 指令指示浏览器也使用给定的引脚验证子域。
与其他标头相反,Spring Security 默认不添加 HPKP。您可以使用<hpkp > 元素自定义 HPKP 标头,如下所示:
<http>
<!-- ... -->
<headers>
<hpkp
include-subdomains = "true"
report-uri = "https://example.net/pkp-report" >
<pins>
<pin algorithm = "sha256" > d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM= </pin>
<pin 算法= "sha256" > E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g= </pin>
</pins>
</hpkp>
</headers>
</ HTTP>
同样,您可以使用 Java 配置启用 HPKP 标头:
@EnableWebSecurity 公共 类WebSecurityConfig扩展 WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .httpPublicKeyPinning() .includeSubdomains(true) .reportUri( "https://example.net /pkp-report" ) .addSha256Pins( "d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" , "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; } }
允许将您的网站添加到框架可能是一个安全问题。例如,使用巧妙的 CSS 样式可能会诱骗用户点击他们不想要的东西(视频演示)。例如,登录到其银行的用户可能会单击一个按钮,向其他用户授予访问权限。这种攻击称为点击劫持。
另一种处理点击劫持的现代方法是使用第 21.1.7 节“内容安全策略 (CSP)”。 |
有多种方法可以减轻点击劫持攻击。例如,要保护旧版浏览器免受点击劫持攻击,您可以使用断帧代码。虽然不完美,但打破框架的代码是您可以为旧版浏览器做的最好的事情。
一种更现代的解决点击劫持的方法是使用X-Frame-Options标头:
X 框架选项:拒绝
X-Frame-Options 响应标头指示浏览器阻止响应中具有此标头的任何站点在框架内呈现。默认情况下,Spring Security 禁用 iframe 内的渲染。
您可以使用frame-options元素自定义 X-Frame-Options。例如,以下将指示 Spring Security 使用“X-Frame-Options: SAMEORIGIN”,它允许同一域内的 iframe:
<http>
<!-- ... -->
<headers>
<frame-options
policy = "SAMEORIGIN" />
</headers>
</http>
同样,您可以自定义框架选项以使用以下方法在 Java 配置中使用相同的来源:
@EnableWebSecurity 公共 类WebSecurityConfig扩展 WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .frameOptions() .sameOrigin(); } }
一些浏览器内置了对过滤反射 XSS 攻击的支持。这绝不是万无一失的,但确实有助于 XSS 保护。
过滤通常默认启用,因此添加标头通常只是确保它已启用并指示浏览器在检测到 XSS 攻击时执行的操作。例如,过滤器可能会尝试以侵入性最小的方式更改内容以仍然呈现所有内容。有时,这种类型的替换本身会成为 XSS 漏洞。相反,最好阻止内容而不是尝试修复它。为此,我们可以添加以下标头:
X-XSS-保护:1;模式=块
默认情况下包含此标头。但是,如果需要,我们可以自定义它。例如:
<http>
<!-- ... -->
<headers>
<xss-protection block = "false" />
</headers>
</http>
同样,您可以使用以下内容在 Java 配置中自定义 XSS 保护:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .xssProtection() .block(false); } } }
内容安全策略 (CSP)是一种机制,Web 应用程序可以利用该机制来缓解内容注入漏洞,例如跨站点脚本 (XSS)。CSP 是一种声明性策略,它为 Web 应用程序作者提供一种工具来声明并最终通知客户端(用户代理)有关 Web 应用程序期望从中加载资源的来源。
内容安全策略并非旨在解决所有内容注入漏洞。相反,可以利用 CSP 来帮助减少内容注入攻击造成的危害。作为第一道防线,Web 应用程序作者应该验证他们的输入并对他们的输出进行编码。 |
Web 应用程序可以通过在响应中包含以下 HTTP 标头之一来使用 CSP:
- 内容安全政策
- 仅内容安全策略报告
这些标头中的每一个都用作向客户端提供安全策略的机制。安全策略包含一组安全策略指令(例如,script-src和object-src),每个指令负责声明对特定资源表示的限制。
例如,Web 应用程序可以通过在响应中包含以下标头来声明它希望从特定的可信源加载脚本:
内容安全策略:脚本源 https://trustedscripts.example.com
尝试从不同于script-src指令中声明的其他来源加载脚本将被用户代理阻止。此外,如果在安全策略中声明了report-uri指令,则用户代理将向声明的 URL 报告违规情况。
例如,如果 Web 应用程序违反了已声明的安全策略,则以下响应标头将指示用户代理将违规报告发送到策略的 report-uri 指令中指定的URL。
内容安全策略:脚本源 https://trustedscripts.example.com;报告 uri /csp-报告端点/
违规报告是标准的 JSON 结构,可以由 Web 应用程序自己的 API 或公共托管的 CSP 违规报告服务(例如REPORT-URI)捕获。
Content -Security-Policy-Report-Only标头为 Web 应用程序作者和管理员提供了监视安全策略的能力,而不是强制执行它们。此标头通常在试验和/或开发站点的安全策略时使用。当一个策略被认为有效时,它可以通过使用Content-Security-Policy头字段来强制执行。
给定以下响应标头,该策略声明可以从两个可能来源之一加载脚本。
Content-Security-Policy-Report-Only: script-src 'self' https://trustedscripts.example.com; 报告 uri /csp-报告端点/
如果站点违反此策略,通过尝试从evil.com加载脚本,用户代理将向report-uri指令指定的声明 URL 发送违规报告,但仍然允许加载违规资源。
需要注意的是,Spring Security默认不添加Content Security Policy。Web 应用程序作者必须声明安全策略以强制执行和/或监视受保护的资源。
例如,给定以下安全策略:
脚本源“自我”https://trustedscripts.example.com;对象源 https://trustedplugins.example.com;报告 uri /csp-报告端点/
您可以使用带有<content-security-policy > 元素的 XML 配置启用 CSP 标头,如下所示:
<http>
<!-- ... -->
<headers>
<content-security-policy
policy-directives = "script-src 'self' https://trustedscripts.example.com; object-src https:// trustedplugins.example.com; report-uri /csp-report-endpoint/" />
</headers>
</http>
要启用 CSP “仅报告”标头,请按如下方式配置该元素:
<http>
<!-- ... -->
<headers>
<content-security-policy
policy-directives = "script-src 'self' https://trustedscripts.example.com; object-src https:// trustedplugins.example.com; report-uri /csp-report-endpoint/"
report-only = "true" />
</headers>
</http>
同样,您可以使用 Java 配置启用 CSP 标头,如下所示:
@EnableWebSecurity 公共 类WebSecurityConfig扩展 WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .contentSecurityPolicy( "script-src 'self' https://trustedscripts.example.com;对象源 https://trustedplugins.example.com; report-uri /csp-report-endpoint/" ); } }
要启用 CSP “仅报告”标头,请提供以下 Java 配置:
@EnableWebSecurity 公共 类WebSecurityConfig扩展 WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .contentSecurityPolicy( "script-src 'self' https://trustedscripts.example.com;对象源 https://trustedplugins.example.com; report-uri /csp-report-endpoint/" ) .reportOnly(); } }
将内容安全策略应用于 Web 应用程序通常是一项艰巨的任务。以下资源可以为您的站点制定有效的安全策略提供进一步的帮助。
Referrer Policy是 Web 应用程序可以用来管理 referrer 字段的一种机制,该字段包含用户上次访问的页面。
Spring Security 的做法是使用Referrer Policy header,它提供了不同的策略:
推荐人政策:同源
Referrer-Policy 响应标头指示浏览器让目的地知道用户之前所在的来源。
Spring Security默认不添加Referrer Policy header。
您可以使用带有<referrer-policy > 元素的 XML 配置来启用 Referrer-Policy 标头,如下所示:
<http>
<!-- ... -->
<headers>
<referrer-policy policy = "same-origin" />
</headers>
</http>
同样,您可以使用 Java 配置启用 Referrer Policy 标头,如下所示:
@EnableWebSecurity 公共 类WebSecurityConfig扩展 WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .referrerPolicy(ReferrerPolicy.SAME_ORIGIN); } }
Spring Security 有一些机制可以方便地将更常见的安全标头添加到您的应用程序。但是,它还提供了挂钩以启用添加自定义标头。
有时您可能希望将自定义安全标头注入到您的应用程序中,但开箱即用不受支持。例如,给定以下自定义安全标头:
X-Custom-Security-Header:标头值
使用 XML 命名空间时,可以使用<header > 元素将这些标头添加到响应中,如下所示:
<http>
<!-- ... -->
<headers>
<header name = "X-Custom-Security-Header" value = "header-value" />
</headers>
</http>
同样,可以使用 Java 配置将标头添加到响应中,如下所示:
@EnableWebSecurity 公共 类WebSecurityConfig扩展 WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .addHeaderWriter( new StaticHeadersWriter( "X-Custom-Security-Header" , "header-value " )); } }
当命名空间或 Java 配置不支持您想要的标头时,您可以创建自定义HeadersWriter
实例,甚至提供HeadersWriter
.
让我们看一个使用自定义实例的例子XFrameOptionsHeaderWriter
。也许您想允许相同来源的内容框架。这很容易通过将策略属性设置为“SAMEORIGIN”来支持,但让我们看一个使用ref属性的更明确的示例。
<http>
<!-- ... -->
<headers>
<header ref = "frameOptionsWriter" />
</headers>
</http>
<!-- 需要 c 命名空间。
请参阅 https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-c-namespace
-->
<beans:bean id = "frameOptionsWriter"
class = "org.springframework. security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter"
c:frameOptionsMode = "SAMEORIGIN" />
我们还可以使用 Java 配置将内容框架限制为同源:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .addHeaderWriter( new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN)); } } }
有时您可能只想为某些请求编写标头。例如,也许您只想保护您的登录页面不被框住。您可以使用DelegatingRequestMatcherHeaderWriter
来这样做。使用 XML 命名空间配置时,可以通过以下方式完成:
<http>
<!-- ... -->
<headers>
<frame-options disabled = "true" />
<header ref = "headerWriter" />
</headers>
</http>
<beans:bean id = "headerWriter"
class = "org.springframework.security.web.header.writers.DelegatingRequestMatcherHeaderWriter" >
<beans:constructor-arg>
<bean class = "org.springframework.security.web.util.matcher.AntPathRequestMatcher"
c:pattern = "/登录" />
</beans:构造函数参数>
<beans:构造函数参数>
<beans:bean
class = "org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter" />
</beans:constructor-arg>
</beans:bean>
我们还可以使用 java 配置阻止将内容框架化到登录页面:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { RequestMatcher matcher = new AntPathRequestMatcher( "/login" ); } DelegatingRequestMatcherHeaderWriter headerWriter = new DelegatingRequestMatcherHeaderWriter(matcher, new XFrameOptionsHeaderWriter()); HTTP // ... .headers() .frameOptions().disabled() .addHeaderWriter(headerWriter); } }