20. 安全 HTTP 响应 Headers

本节讨论了 Spring Security 对向响应添加各种安全 Headers 的支持。

20.1 默认安全标题

Spring Security 允许用户轻松注入默认的安全 Headers,以帮助保护其应用程序。 Spring Security 的默认值为包含以下 Headers:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

Note

仅在 HTTPS 请求上添加严格传输安全性

有关这些标题中的每个标题的更多详细信息,请参阅相应的部分:

虽然这些 Headers 中的每一个均被视为最佳实践,但应注意,并非所有 Client 端都使用 Headers,因此鼓励进行其他测试。

您可以自定义特定的标题。例如,假设希望您的 HTTP 响应 Headers 如下所示:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block

具体来说,您希望所有默认 Headers 都具有以下自定义设置:

您可以使用以下 Java 配置轻松完成此操作:

@EnableWebSecurity
public class WebSecurityConfig extends
		WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			// ...
			.headers()
				.frameOptions().sameOrigin()
				.httpStrictTransportSecurity().disable();
	}
}

另外,如果您使用的是 Spring Security XML Configuration,则可以使用以下代码:

<http>
	<!-- ... -->

	<headers>
		<frame-options policy="SAMEORIGIN" />
		<hsts disable="true"/>
	</headers>
</http>

如果您不想添加默认值,并且希望对应使用的内容进行明确控制,则可以禁用默认值。下面提供了基于 Java 和 XML 的配置示例:

如果您使用的是 Spring Security 的 Java 配置,则以下内容只会添加Cache Control

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
	// ...
	.headers()
		// do not use any default headers unless explicitly listed
		.defaultsDisabled()
		.cacheControl();
}
}

以下 XML 仅会添加Cache Control

<http>
	<!-- ... -->

	<headers defaults-disabled="true">
		<cache-control/>
	</headers>
</http>

如有必要,可以使用以下 Java 配置禁用所有 HTTP 安全响应 Headers:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
	// ...
	.headers().disable();
}
}

如有必要,您可以使用以下 XML 配置禁用所有 HTTP 安全响应 Headers:

<http>
	<!-- ... -->

	<headers disabled="true" />
</http>

20.1.1 缓存控制

过去,Spring Security 要求您为 Web 应用程序提供自己的缓存控件。当时看来这是合理的,但浏览器缓存已 Developing 为包括用于安全连接的缓存。这意味着用户可以查看经过身份验证的页面,然后注销,然后恶意用户可以使用浏览器历史记录来查看缓存的页面。为了帮助缓解这种情况,Spring Security 添加了缓存控制支持,该支持将在响应中插入以下 Headers。

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

只需添加没有子元素的<headers>元素即可自动添加 Cache Control 和许多其他保护。但是,如果只需要缓存控制,则可以使用 Spring Security 的 XML 名称空间(带有<cache-control>元素和[email protected]属性)来启用此功能。

<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 设置的 Headers。这对于确保正确缓存 CSS,JavaScript 和图像之类的内容很有用。

使用 Spring Web MVC 时,通常在您的配置中完成。例如,以下配置将确保为您的所有资源设置缓存头:

@EnableWebMvc
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry
			.addResourceHandler("/resources/**")
			.addResourceLocations("/resources/")
			.setCachePeriod(31556926);
	}

	// ...
}

20.1.2Content Type 选项

历史上,包括 Internet Explorer 在内的浏览器都会尝试使用content sniffing来猜测请求的 Content Type。这允许浏览器通过猜测未指定 Content Type 的资源上的 Content Type 来改善用户体验。例如,如果浏览器遇到一个未指定 Content Type 的 JavaScript 文件,它将能够猜测该 Content Type 然后执行它。

Note

允许上传内容时,还有许多其他事情(即,仅在不同的域中显示文档,确保设置了 Content-Type Headers,清理文档等)。但是,这些措施不在 Spring Security 提供的范围之内。同样重要的是要指出在禁用内容嗅探时,必须指定 Content Type 才能使内容正常工作。

内容嗅探的问题在于,这允许恶意用户使用多义标记(即,可以作为多种 Content Type 有效的文件)执行 XSS 攻击。例如,某些网站可能允许用户向网站提交有效的附言文档并进行查看。恶意用户可能会创建后记文档也是有效的 JavaScript 文件并对其执行 XSS 攻击。

可以通过在响应中添加以下 Headers 来禁用内容嗅探:

X-Content-Type-Options: nosniff

与高速缓存控制元素一样,在使用不带子元素的\ 元素时,默认情况下会添加 nosniff 指令。但是,如果您想进一步控制要添加的 Headers,可以使用<content-type-options>元素和[email protected]属性,如下所示:

<http>
	<!-- ... -->

	<headers defaults-disabled="true">
		<content-type-options />
	</headers>
</http>

默认情况下,Spring Security Java 配置添加了 X-Content-Type-Options 头。如果要对标题进行更多控制,则可以使用以下命令显式指定 Content Type 选项:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
	// ...
	.headers()
		.defaultsDisabled()
		.contentTypeOptions();
}
}

20.1.3 HTTP 严格传输安全性(HSTS)

当您 Importing 银行的网站时,您 Importingmybank.example.com 还是 Importinghttps://mybank.example.com?如果您忽略 https 协议,则可能会受到中间人袭击的攻击。即使网站执行到https://mybank.example.com的重定向,恶意用户也可能拦截初始 HTTP 请求并操纵响应(即,重定向到https://mibank.example.com并窃取其凭据)。

许多用户忽略了 https 协议,这就是创建HTTP 严格传输安全性(HSTS)的原因。将 mybank.example.com 添加为HSTS host后,浏览器可以提前知道对 mybank.example.com 的任何请求都应解释为https://mybank.example.com。这大大降低了发生中间人攻击的可能性。

Note

根据RFC6797,HSTSHeaders 仅注入 HTTPS 响应中。为了使浏览器能够确认 Headers,浏览器必须首先信任对用于构建连接的 SSL 证书(不仅仅是 SSL 证书)进行签名的 CA。

将站点标记为 HSTS 主机的一种方法是将主机预加载到浏览器中。另一个方法是在响应中添加“ Strict-Transport-Security”Headers。例如,以下内容将指示浏览器将域视为一年的 HSTS 主机(一年大约 31536000 秒):

Strict-Transport-Security: max-age=31536000 ; includeSubDomains

可选的 includeSubDomains 指令指示 Spring Security 子域(即 secure.mybank.example.com)也应被视为 HSTS 域。

与其他头文件一样,Spring Security 默认添加 HSTS。您可以使用<hsts>元素来自定义 HSTSHeaders,如下所示:

<http>
	<!-- ... -->

	<headers>
		<hsts
			include-subdomains="true"
			max-age-seconds="31536000" />
	</headers>
</http>

同样,您只能使用 Java 配置启用 HSTS Headers:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
	// ...
	.headers()
		.httpStrictTransportSecurity()
			.includeSubdomains(true)
			.maxAgeSeconds(31536000);
}
}

20.1.4 HTTP 公钥固定(HPKP)

HTTP 公共密钥固定(HPKP)是一项安全功能,它告诉 Web Client 端将特定的加密公共密钥与特定的 Web 服务器相关联,以防止伪造证书对中间人(MITM)的攻击。

为了确保 TLS 会话中使用的服务器公钥的真实性,此公钥被包装到 X.509 证书中,该证书通常由证书颁发机构(CA)签名。浏览器之类的 Web Client 端信任许多这样的 CA,它们都可以为任意域名创建证书。如果攻击者能够入侵单个 CA,则他们可以对各种 TLS 连接执行 MITM 攻击。 HPKP 可以通过告诉 Client 端哪个公钥属于某个 Web 服务器来规避 HTTPS 协议的这种威胁。 HPKP 是首次使用信任(TOFU)技术。 Web 服务器第一次通过特殊的 HTTP Headers 告诉 Client 端哪个公钥属于它时,Client 端会在给定的时间段内存储此信息。当 Client 端再次访问服务器时,它期望包含公钥的证书,该公钥的指纹已经通过 HPKP 知道。如果服务器提供了未知的公共密钥,则 Client 端应向用户显示警告。

Note

因为用户代理需要根据 SSL 证书链验证引脚,所以 HPKP Headers 仅注入 HTTPS 响应中。

为您的站点启用此功能就像通过 HTTPS 访问站点时返回 Public-Key-Pins HTTPHeaders 一样简单。例如,以下内容将指示用户代理仅将 2 个引脚的引脚验证失败报告给给定 URI(通过report-uri指令):

Public-Key-Pins-Report-Only: max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; report-uri="http://example.net/pkp-report" ; includeSubDomains

引脚验证失败报告是标准 JSON 结构,可以通过 Web 应用程序自己的 API 或公共托管的 HPKP 报告服务(例如REPORT-URI)捕获。

可选的 includeSubDomains 指令指示浏览器也使用给定的引脚来验证子域。

与其他 Headers 相反,Spring Security 默认情况下不添加 HPKP。您可以使用<hpkp>元素来自定义 HPKPHeaders,如下所示:

<http>
	<!-- ... -->

	<headers>
		<hpkp
			include-subdomains="true"
			report-uri="http://example.net/pkp-report">
			<pins>
					<pin algorithm="sha256">d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=</pin>
					<pin algorithm="sha256">E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=</pin>
			</pins>
		</hpkp>
	</headers>
</http>

同样,您可以使用 Java 配置启用 HPKP Headers:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

		@Override
		protected void configure(HttpSecurity http) throws Exception {
				http
				// ...
				.headers()
						.httpPublicKeyPinning()
								.includeSubdomains(true)
								.reportUri("http://example.net/pkp-report")
								.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
		}
}

20.1.5 X-Frame-Options

允许将您的网站添加到框架可能是一个安全问题。例如,使用聪明的 CSS 样式用户可能会被诱骗点击他们不想要的内容(video demo)。例如,登录到其银行的用户可以单击将按钮授予其他用户访问权限。这种攻击称为Clickjacking

Note

处理点击劫持的另一种现代方法是使用第 20.1.7 节“内容安全策略(CSP)”

有许多方法可以缓解点击劫持攻击。例如,要保护旧版浏览器免受点击劫持攻击,可以使用帧破码。虽然不完美,但是对于传统浏览器而言,破帧代码是最好的选择。

解决点击劫持的更现代方法是使用X-Frame-OptionsHeaders:

X-Frame-Options: DENY

X-Frame-Options 响应 Headers 指示浏览器阻止响应中带有此 Headers 的任何网站呈现在框架中。默认情况下,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
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
	// ...
	.headers()
		.frameOptions()
			.sameOrigin();
}
}

20.1.6 X-XSS-Protection

一些浏览器内置了对过滤反映的 XSS 攻击的支持。这绝非万无一失,但确实有助于 XSS 保护。

通常默认情况下会启用过滤,因此添加 Headers 通常只会确保 Headers 已启用,并指示浏览器在检测到 XSS 攻击时应采取的措施。例如,过滤器可能会尝试以最小侵入性的方式更改内容以仍然呈现所有内容。有时,这种替换可以成为XSS 漏洞本身。相反,最好是阻止内容,而不要尝试对其进行修复。为此,我们可以添加以下 Headers:

X-XSS-Protection: 1; mode=block

默认情况下包含此 Headers。但是,我们可以根据需要自定义它。例如:

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

20.1.7 内容安全 Policy(CSP)

内容安全 Policy(CSP)是 Web 应用程序可以利用的一种机制来减轻内容注入漏洞,例如跨站点脚本(XSS)。 CSP 是一种声明性策略,为 Web 应用程序作者提供了一种工具,可以声明该 Web 应用程序希望从中加载资源的来源,并最终将这些信息通知 Client 端(用户代理)。

Note

内容安全策略并非旨在解决所有内容注入漏洞。取而代之的是,可以利用 CSP 帮助减少内容注入攻击所造成的危害。作为第一道防线,Web 应用程序作者应验证其 Importing 并对输出进行编码。

Web 应用程序可以通过在响应中包括以下 HTTP Headers 之一来使用 CSP:

  • Content-Security-Policy

  • Content-Security-Policy-Report-Only

这些 Headers 中的每一个都用作向 Client 端传递 *安全策略 *的机制。安全策略包含一组 *安全策略指令 (例如 script-src object-src *),每个指令负责声明对特定资源表示形式的限制。

例如,Web 应用程序可以通过在响应中包括以下 Headers 来声明它希望从特定的受信任源中加载脚本:

Content-Security-Policy: script-src https://trustedscripts.example.com

尝试从* script-src *指令中未声明的其他来源加载脚本的尝试将被用户代理阻止。此外,如果在安全策略中声明了report-uri指令,则用户代理会将违规报告到声明的 URL。

例如,如果 Web 应用程序违反了声明的安全策略,则以下响应 Headers 将指示用户代理将违规报告发送到策略的* report-uri *指令中指定的 URL。

Content-Security-Policy: script-src https://trustedscripts.example.com; report-uri /csp-report-endpoint/

Violation reports是标准 JSON 结构,可以由 Web 应用程序自己的 API 或由公共托管的 CSP 违规报告服务(例如REPORT-URI)捕获。

*** Content-Security-Policy-Report-Only** Headers 为 Web 应用程序作者和 Management 员提供了监视安全策略而不是强制执行这些策略的功能。该标题通常在试验和/或开发站点的安全策略时使用。当某个策略被认为有效时,可以通过使用 Content-Security-Policy *Headers 字段来实施该策略。

给定以下响应头,该策略声明可以从两个可能的来源之一加载脚本。

Content-Security-Policy-Report-Only: script-src 'self' https://trustedscripts.example.com; report-uri /csp-report-endpoint/

如果网站违反了此 Policy,则通过尝试从* evil.com 加载脚本,用户代理会将违规报告发送到 report-uri *指令指定的声明的 URL,但仍允许违规资源尽管如此。

配置内容安全策略

重要的是要注意,Spring Security *默认不添加 * Content Security Policy。 Web 应用程序作者必须声明安全策略以强制执行和/或监视受保护的资源。

例如,给定以下安全策略:

script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/

您可以使用带有<content-security-policy>元素的 XML 配置来启用 CSPHeaders,如下所示:

<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 *'report-only'*Headers,请按以下方式配置元素:

<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 Headers,如下所示:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
	// ...
	.headers()
		.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/");
}
}

要启用 CSP *'report-only'*Headers,请提供以下 Java 配置:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
	// ...
	.headers()
		.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
		.reportOnly();
}
}

Additional Resources

将内容安全策略应用于 Web 应用程序通常是一项艰巨的任务。以下资源可以为您的站点制定有效的安全策略提供进一步的帮助。

内容安全策略简介

CSP 指南-Mozilla 开发人员网络

W3C 候选人推荐

20.1.8 推荐人 Policy

Referrer Policy是一种机制,Web 应用程序可以利用该机制来 Management 引荐来源网址字段,该字段包含用户所在的最后一页。

Spring Security 的方法是使用Referrer PolicyHeaders,它提供了不同的policies

Referrer-Policy: same-origin

Referrer-Policy 响应 Headers 指示浏览器让目的地知道用户先前所在的源。

配置引荐来源网址策略

Spring Security *不会添加 * Referrer Policy Headers 默认情况下。

您可以使用带有<referrer-policy>元素的 XML 配置来启用 Referrer-PolicyHeaders,如下所示:

<http>
	<!-- ... -->

	<headers>
		<referrer-policy policy="same-origin" />
	</headers>
</http>

同样,您可以使用 Java 配置启用 Referrer Policy Headers,如下所示:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
	// ...
	.headers()
		.referrerPolicy(ReferrerPolicy.SAME_ORIGIN);
}
}

20.2 自定义标题

Spring Security 具有使您可以方便地将更常见的安全 Headers 添加到您的应用程序的机制。但是,它也提供了钩子来启用添加自定义 Headers。

20.2.1 静态标题

有时您可能希望将不支持的自定义安全 Headers 注入应用程序中。例如,给定以下自定义安全 Headers:

X-Custom-Security-Header: header-value

使用 XML 名称空间时,可以使用<header>元素将这些 Headers 添加到响应中,如下所示:

<http>
	<!-- ... -->

	<headers>
		<header name="X-Custom-Security-Header" value="header-value"/>
	</headers>
</http>

同样,可以使用 Java 配置将 Headers 添加到响应中,如下所示:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
	// ...
	.headers()
		.addHeaderWriter(new StaticHeadersWriter("X-Custom-Security-Header","header-value"));
}
}

20.2.2HeadersWriter

如果名称空间或 Java 配置不支持所需的 Headers,则可以创建自定义HeadersWriter实例,甚至提供HeadersWriter的自定义实现。

我们来看一个使用XFrameOptionsHeaderWriter的自定义实例的示例。也许您希望允许对同一来源的内容进行框架化。通过将policy属性设置为“ SAMEORIGIN”可以轻松地支持此功能,但是让我们来看一个使用ref属性的更明确的示例。

<http>
	<!-- ... -->

	<headers>
		<header ref="frameOptionsWriter"/>
	</headers>
</http>
<!-- Requires the c-namespace.
See http://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));
}
}

20.2.3 DelegatingRequestMatcherHeaderWriter

有时您可能只想为某些请求编写 Headers。例如,也许您只想保护登录页面免于陷害。您可以使用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="/login"/>
	</beans:constructor-arg>
	<beans:constructor-arg>
		<beans:bean
			class="org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter"/>
	</beans:constructor-arg>
</beans:bean>

我们还可以使用 Java 配置防止将内容 Framework 到登录页面:

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