本节讨论SpringSecurity对向响应中添加各种安全头的支持。
10.8.1 Default Security Headers
SpringSecurity允许用户轻松地注入默认的安全头信息,以帮助保护他们的应用程序。SpringSecurity的默认值包括以下报文头:
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
只会在HTTPS请求上添加 Strict-Transport-Security
有关这些标题的其他详细信息,请参阅相应的部分:
HTTP Strict Transport Security
虽然每个头都被认为是最佳实践,但应该注意的是,并非所有客户端都使用头,因此鼓励进行额外的测试。
您可以自定义特定的头。例如,假设你希望HTTP响应头看起来像下面这样:
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
具体来说,您希望所有的默认头都具有以下自定义设置:
X-Frame-Options 允许来自同一域的任何请求
HTTP Strict Transport Security (HSTS) 不被添加到响应中
您可以通过以下Java配置轻松地实现这一点:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.headers()
.frameOptions().sameOrigin()
.httpStrictTransportSecurity().disable();
}
}
或者,如果你使用的是SpringSecurityXML配置,则可以使用以下内容:
<http>
<!-- ... -->
<headers>
<frame-options policy="SAMEORIGIN" />
<hsts disable="true"/>
</headers>
</http>
如果不希望添加默认值,并且希望对使用的内容进行显式控制,则可以禁用默认值。下面提供了基于Java和XML的配置的示例:
如果使用Spring Security的Java配置,下面是只添加Cache控件的示例。
@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控件
<http>
<!-- ... -->
<headers defaults-disabled="true">
<cache-control/>
</headers>
</http>
如果需要,您可以下面的Java配置来禁用HTTP安全响应头:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.headers().disable();
}
}
如果需要,可以使用以下XML配置禁用所有HTTP安全响应头:
<http>
<!-- ... -->
<headers disabled="true" />
</http>
Cache Control
以前的Spring Security要求您为Web应用程序提供自己的缓存控制。这在当时似乎是合理的,但是浏览器缓存已经发展到包括安全连接的缓存。这意味着用户可以查看已验证的页面、注销,然后恶意用户可以使用浏览器历史记录查看缓存的页面。为了帮助减轻这种情况,SpringSecurity增加了缓存控制支持,它将在响应中插入以下头文件。
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
只添加不带子元素的 <headers> 元素将自动添加缓存控制和许多其他保护。但是,如果您只需要缓存控制,那么可以使用Spring Security的XML命名空间和 <cache-control> 元素以及 headers@defaults-disabled 属性来启用此功能。
<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) 来覆盖SpringSecurity设置的头。这对于确保CSS、javascript和images 等内容被正确缓存非常有用。
当使用SpringWebMVC时,这通常是在您的配置中完成的。例如,以下配置将确保为所有资源设置缓存头:
@EnableWebMvc
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/resources/**")
.addResourceLocations("/resources/")
.setCachePeriod(31556926);
}
// ...
}
Content Type Options
历史上,包括InternetExplorer在内的浏览器都会尝试使用内容嗅探来猜测请求的内容类型。这允许浏览器通过猜测未指定内容类型的资源的内容类型来改善用户体验。例如,如果浏览器遇到一个没有指定内容类型的javascript文件,它将能够猜测内容类型,然后执行它。
在允许上载内容时,还应该做许多其他的事情(即只在不同的域中显示文档、确保设置了 Content-Type 头、清理文档等)。但是,这些措施超出了Spring Security 提供的范围。同样重要的是,在禁用内容嗅探时,必须指定内容类型,才能使内容正常工作。
内容嗅探的问题在于,这允许恶意用户使用polyglots(即作为多个内容类型有效的文件)来执行XSS攻击。例如,某些网站可能允许用户向网站提交有效的PostScript文档并查看它。恶意用户可能会创建一个 postscript document that is also a valid JavaScript file ,并使用它执行XSS攻击。
通过向响应中添加以下头,可以禁用内容嗅探:
X-Content-Type-Options: nosniff
与cache control元素一样,当使用不带子元素的<headers>元素时,默认情况下会添加nosniff指令。但是,如果您想要更多地控制添加的headers,可以使用 <content-type-options> 元素和 headers@defaults-disabled 属性,如下所示:
<http>
<!-- ... -->
<headers defaults-disabled="true">
<content-type-options />
</headers>
</http>
在Spring Security Java配置中默认添加X-CaseType选项头。如果希望对标题进行更多控制,可以使用以下命令显式指定内容类型选项:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.headers()
.defaultsDisabled()
.contentTypeOptions();
}
}
HTTP Strict Transport Security (HSTS)
当您输入银行网站时,您是输入 mybank.example.com 还是输入 https://mybank.example.com?如果省略了HTTPS协议,则很容易受到中间人攻击( Man in the Middle attacks)。即使网站执行重定向至 https://mybank.example.com,恶意用户也可能截获初始http请求并操纵响应(即重定向至https://mibank.example.com 并窃取其凭据)。
许多用户省略了HTTPS协议,这就是创建HTTP Strict Transport Security(HSTS)的原因。一旦mybank.example.com被添加为HSTS主机,浏览器就可以提前知道对mybank.example.com的任何请求都应解释为https://mybank.example.com。这大大降低了中间人攻击的可能性。
根据RFC6797,HSTS头只注入到HTTPS响应中。为了让浏览器确认报头,浏览器必须首先信任签署用于建立连接的SSL证书的CA(而不仅仅是SSL证书)。
将站点标记为HSTS主机的一种方法是将主机预加载到浏览器中。另一种方法是将“Strict-Transport-Security”头添加到响应中。例如,下面将指示浏览器将域视为一年的HSTS主机(一年大约有31536000秒):
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
可选的 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 Public Key Pinning (HPKP)
HTTP公钥Pinning(HPKP)是一种安全功能,它告诉Web客户端将特定的加密公钥与特定的Web服务器关联起来,以防止中间人(MITM)使用伪造的证书进行攻击。
为了确保在TLS会话中使用的服务器公钥的真实性,该公钥被包装成一个X.509证书,该证书通常由证书颁发机构(CA)签名。浏览器等Web客户机信任许多这样的CA,它们都可以为任意域名创建证书。如果攻击者能够破坏单个CA,他们可以对各种TLS连接执行MITM攻击。HPKP可以通过告诉客户端哪些公钥属于某个Web服务器来规避HTTPS协议的这种威胁。HPKP是一种Trust on First Use(TOFU)技术。当Web服务器第一次通过一个特殊的HTTP头告诉客户端哪些公钥属于它时,客户端在给定的时间段内存储这些信息。当客户端再次访问服务器时,它需要一个包含其指纹已经被HPKP知晓的公钥的证书。如果服务器传递未知的公钥,客户端应该向用户发出警告。
因为 user-agent 需要根据SSL证书链验证pins ,所以 HPKP 头只被注入到 https 响应中。
为您的站点启用此功能非常简单,只需在通过HTTPS访问站点时返回 Public-Key-Pins HTTP头即可。例如,下面的报文头将告知 user-agent 仅把 pin 验证失败发送给指定的URI(通过 report-uri 指令)报告2个 pins:
Public-Key-Pins-Report-Only: max-age=5184000 ;
pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" ;
pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ;
report-uri="https://example.net/pkp-report" ; includeSubDomains
pin validation failure report 是一种标准的JSON结构,可以由Web应用程序自己的API或公共托管的 HPKP 报告服务(如report-uri)捕获。
可选的 includeSubDomains 指令指示浏览器也使用给定的 pins 验证子域。
与其他头文件不同,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 algorithm="sha256">E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=</pin>
</pins>
</hpkp>
</headers>
</http>
类似地,可以使用Java配置启用HPKP标头:
@EnableWebSecurity
public class WebSecurityConfig extends 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=";
}
}
X-Frame-Options
允许将网站添加到 frame 中可能是一个安全风险。例如,使用巧妙的CSS样式,用户可能会被诱骗点击他们无意点击的东西。例如,登录到其银行的用户可能单击一个按钮,授予其他用户访问权限。这种攻击行为被称为点击劫持(Clickjacking)。
另一种流行的处理点击劫持的方法是使用 “Content Security Policy (CSP)”。
有很多方法可以减轻点击劫持攻击。例如,为了保护传统浏览器不受点击劫持攻击,您可以使用破坏 frame 的代码。虽然不是完美的,但是 frame 破坏代码是对传统浏览器最好的选择。
解决点击劫持问题的一种更现代的方法是使用 X-Frame-Options 头文件:
X-Frame-Options: DENY
X-Frame-Options 响应头指示浏览器阻止响应中具有此头的任何站点在 frame 里呈现。默认情况下,Spring Security 禁止在 iframe 中呈现。
可以使用 frame-options 元素自定义 X-Frame-Options。例如,下面将指示Spring Security使用“X-Frame-Options: SAMEORIGIN”,它允许iframes在同一域中:
<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();
}
}
X-XSS-Protection
一些浏览器内置了过滤反射的XSS攻击的支持。这绝不是万无一失的,但有助于XSS保护。
过滤通常在默认情况下是启用的,因此添加头通常只是确保它被启用,并指示浏览器在检测到XSS攻击时要做什么。例如,过滤器可能会尝试以最低侵入性的方式更改内容,并仍然渲染内容。有时,这种类型的替换本身可能成为XSS漏洞。相反,最好是阻止内容,而不是尝试修复它。为此,我们可以添加以下header:
X-XSS-Protection: 1; mode=block
默认情况下包含此标题。但是,如果需要,我们可以定制它。例如:
<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);
}
}
Content Security Policy (CSP)
Content Security Policy (CSP) 是一种机制,Web应用程序可以利用它来缓解内容注入漏洞,例如 cross-site scripting (XSS)。CSP是一种声明性策略,它为Web应用程序作者提供了一种工具,用于声明并最终通知客户端(用户代理)Web应用程序希望从中加载资源的源。
内容安全策略并非旨在解决所有内容注入漏洞。相反,可以利用CSP来帮助减少内容注入攻击造成的危害。作为第一道防线,Web应用程序作者应该验证其输入并对其输出进行编码。
Web应用程序可以通过在响应中包含以下的一个HTTP头来使用CSP:
Content-Security-Policy
Content-Security-Policy-Report-Only
这些头中的每一个都被用作向客户端传递 security policy 的机制。安全策略包含一组安全策略指令(例如,script-src和 object-src),每个指令负责声明特定资源表示的限制。
例如,Web应用程序可以通过在响应中包含以下头来声明它期望从特定的可信源加载脚本:
Content-Security-Policy: script-src https://trustedscripts.example.com
从脚本src指令中声明的源以外的其他源加载脚本的尝试,将会被 user-agent 阻止。此外,如果安全策略中声明了report-uri 指令,那么用户代理将向声明的URL报告冲突。
例如,如果Web应用程序违反声明的安全策略,下面的响应头将指示用户代理将冲突报告发送到策略的report uri指令中指定的URL。
Content-Security-Policy: script-src https://trustedscripts.example.com; report-uri /csp-report-endpoint/
违规报告是标准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; report-uri /csp-report-endpoint/
如果站点违反此策略,通过尝试从evil.com加载脚本,用户代理将向report uri指令指定的声明的url发送冲突报告,但仍然允许加载冲突资源。
Configuring Content Security Policy
需要注意的是,Spring安全性在默认情况下 不会添加 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配置启用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
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“仅报告”标题,请提供以下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();
}
}
Referrer Policy
Referrer Policy 是Web应用程序可以利用的一种机制,用于管理referer字段,该字段包含用户所访问的最后一个页面。
Spring Security的方法是使用 Referrer Policy 头,它提供不同的策略:
Referrer-Policy: same-origin
Referrer-Polic 响应头指示浏览器让目标知道用户以前所在的源。
Configuring Referrer Policy
默认情况下,Spring安全性不会添加referer策略头。
您可以使用带有<referer policy>元素的XML配置启用referer策略头,如下所示:
<http>
<!-- ... -->
<headers>
<referrer-policy policy="same-origin" />
</headers>
</http>
类似地,您可以使用Java配置启用引用策略头,如下所示:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.headers()
.referrerPolicy(ReferrerPolicy.SAME_ORIGIN);
}
}
Feature Policy
功能策略是一种机制,允许Web开发人员有选择地启用、禁用和修改浏览器中某些API和Web功能的行为。
Feature-Policy: geolocation 'self'
使用 Feature Policy,开发人员可以选择一组“策略”,让浏览器对整个站点中使用的特定功能进行强制执行。这些策略限制站点可以访问或修改特定功能的浏览器默认行为的API。
Configuring Feature Policy
默认情况下,Spring Security 不会添加功能策略头。
您可以使用带有<feature policy>元素的XML配置启用Feature-Policy头,如下所示:
<http>
<!-- ... -->
<headers>
<feature-policy policy-directives="geolocation 'self'" />
</headers>
</http>
类似地,您可以使用Java配置启用特性策略标题,如下所示:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.headers()
.featurePolicy("geolocation 'self'");
}
}
Headers Writer
当命名空间或Java配置不支持所需的头文件时,您可以创建自定义 HeadersWriter 实例,甚至可以提供 HeadersWriter 的自定义实现。
让我们来看一个使用 XFrameOptionsHeaderWriter 自定义实例的示例。也许您希望允许为同一个来源设置内容框架。通过将policy属性设置为“sameorigin”,这很容易得到支持,但是让我们来看一个使用ref属性的更明确的示例。
<http>
<!-- ... -->
<headers>
<header ref="frameOptionsWriter"/>
</headers>
</http>
<!-- Requires the c-namespace.
See 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
有时,您可能只想为某些请求编写一个头部。例如,也许您只想保护登录页面不被框架化。您可以使用 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配置将内容框架化到登录页面:
@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);
}
}