本节从以下四点讨论 Servlet 对 Spring针对安全性常见攻击的保护的特定支持:

  • Servlet环境下的跨站点请求伪造(CSRF)
  • Security HTTP Response Headers
  • HTTP
  • HttpFirewall

一、Servlet环境下的跨站点请求伪造(CSRF)

1、使用Spring Security CSRF保护

使用Spring Security的CSRF保护的步骤概述如下:

  • Use proper HTTP verbs
  • Configure CSRF Protection
  • Include the CSRF Token

1.1 使用正确的HTTP谓词

防止CSRF攻击的第一步是确保您的网站使用正确的HTTP谓词。为了使针对CSRF的任何一种保护都能工作,应用程序必须确保“安全”的HTTP方法是幂等的。这意味着使用HTTP GET、HEAD、OPTIONS和TRACE方法的请求不应更改应用程序的状态。

1.2 配置 CSRF 保护

下一步是在应用程序中配置 Spring Security 的 CSRF 保护。 默认情况下,Spring 安全性的 CSRF 保护处于启用状态,但您可能需要自定义配置。 接下来的几节将介绍一些常见的自定义项。

自定义CsrfTokenRepository
默认情况下,Spring Security通过使用HttpSessionCsrfTokenRepository将预期的CSRF令牌存储在HttpSession中。在某些情况下,用户可能希望配置自定义CsrfTokenRepository。例如,可能需要将CsrfToken保存在cookie中,以支持基于JavaScript的应用程序。
默认情况下,CookieCsrfTokenRepository写入名为XSRF-TOKEN的cookie,并从名为X-XSRF-TOKEN的标头或HTTP参数_csrf读取该cookie。这些默认值来自AngularJS。
您可以使用以下方法在XML中配置CookieCsrfTokenRepository:

<http>
	<!-- ... -->
	<csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
	class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
	p:cookieHttpOnly="false"/>

该示例显式设置cookieHttpOnly=false。这是允许JavaScript(如AngularJS)读取cookie所必需的。如果您不需要使用JavaScript直接读取cookie,我们建议省略cookieHttpOnly=false以提高安全性。

 您可以使用以下方法在Java或Kotlin配置中配置CookieCsrfTokenRepository:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			.csrf(csrf -> csrf
				.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
			);
		return http.build();
	}
}

该示例显式设置cookieHttpOnly=false。这是让JavaScript(如AngularJS)读取cookie所必需的。如果您不需要使用JavaScript直接读取cookie,我们建议省略cookieHttpOnly=false(使用新的CookieCsrfTokenRepository())以提高安全性。

禁用CSRF保护
默认情况下,启用CSRF保护。但是,如果CSRF保护对您的应用程序有意义,您可以禁用它。
以下XML配置禁用CSRF保护:

<http>
	<!-- ... -->
	<csrf disabled="true"/>
</http>

以下Java或Kotlin配置禁用CSRF保护:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			.csrf(csrf -> csrf.disable());
		return http.build();
	}
}

配置CsrfTokenRequestHandler
Spring Security的CsrfFilter在CsrfTokenRequestHandler的帮助下,将CsrfToken公开为名为_csrf的HttpServletRequest属性。在5.8中,默认实现是CsrfTokenRequestAttributeHandler,它只是使_csrf属性可用作请求属性。
从6.0开始,默认实现是XorCsrfTokenRequestAttributeHandler,它为BRACH提供保护(请参阅gh-4001)。
如果您希望禁用CsrfToken的BREACH保护并恢复到5.8默认值,您可以使用以下方法在XML中配置CsrfTokenRequestAttributeHandler:

<http>
	<!-- ... -->
	<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
	class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"/>

您可以使用以下方法在Java配置中配置CsrfTokenRequestAttributeHandler:

@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			.csrf(csrf -> csrf
				.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
			);
		return http.build();
	}
}

包含CSRF令牌

为了保护同步器令牌模式免受CSRF攻击,我们必须在HTTP请求中包含实际的CSRF令牌。这必须包含在请求的一部分(表单参数、HTTP标头或其他部分)中,而浏览器不会自动将其包含在HTTP请求中。

我们已经看到CsrfToken被公开为一个请求属性。这意味着任何视图技术都可以访问CsrfToken,以将预期的令牌公开为表单或元标记。幸运的是,本章后面列出了一些集成,这些集成使得在表单和ajax请求中包含令牌变得更加容易。

表单URL编码
要发布HTML表单,CSRF令牌必须作为隐藏输入包含在表单中。例如,呈现的HTML可能看起来像:

<input type="hidden"
	name="_csrf"
	value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>

下来,我们将讨论将 CSRF 令牌作为隐藏输入包含在表单中的各种方法。

自动包含CSRF令牌

Spring Security的CSRF支持通过其CsrfRequestDataValueProcessor提供与Spring的RequestDataValue处理器的集成。这意味着,如果您使用Spring的表单标记库Thymelaf或任何其他与RequestDataValueProcessor集成的视图技术,那么具有不安全HTTP方法(如post)的表单会自动包含实际的CSRF令牌。

csrfInput标记

如果您使用JSP,那么您可以使用Spring的表单标记库。但是,如果这不是一个选项,您也可以使用csrfInput标记包含该令牌。

CsrfToken请求属性

如果在请求中包含实际CSRF令牌的其他选项不起作用,则可以利用CsrfToken作为名为_CSRF的HttpServletRequest属性公开的事实。
以下示例使用JSP来实现这一点:

<c:url var="logoutUrl" value="/logout"/>
<form action="${logoutUrl}"
	method="post">
<input type="submit"
	value="Log out" />
<input type="hidden"
	name="${_csrf.parameterName}"
	value="${_csrf.token}"/>
</form>

Ajax 和 JSON 请求
如果使用 JSON,则无法在 HTTP 参数中提交 CSRF 令牌。 相反,您可以在 HTTP 标头中提交令牌。
以下各节讨论了在基于 JavaScript 的应用程序中将 CSRF 令牌作为 HTTP 请求标头包含的各种方法。

自动收录
您可以配置 Spring 安全性以将预期的 CSRF 令牌存储在 cookie 中。 通过将预期的CSRF存储在cookie中,JavaScript框架(如AngularJS)会自动在HTTP请求标头中包含实际的CSRF令牌。

元标签
在cookie中暴露CSRF的另一种模式是在元标签中包含CSRF令牌。HTML可能看起来像这样:

<html>
<head>
	<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
	<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
	<!-- ... -->
</head>
<!-- ... -->

一旦元标签包含CSRF令牌,JavaScript代码就可以读取元标签并将CSRF令牌包括为报头。如果您使用jQuery,您可以使用以下代码来完成此操作:

$(function () {
	var token = $("meta[name='_csrf']").attr("content");
	var header = $("meta[name='_csrf_header']").attr("content");
	$(document).ajaxSend(function(e, xhr, options) {
		xhr.setRequestHeader(header, token);
	});
});

csrfMeta标记
如果使用JSP,将CSRF令牌写入元标记的一种方法是使用csrfMeta标记。

CsrfToken请求属性
如果在请求中包含实际CSRF令牌的其他选项不起作用,则可以利用CsrfToken作为名为_CSRF的HttpServletRequest属性公开的事实。以下示例使用JSP来实现这一点:

<html>
<head>
	<meta name="_csrf" content="${_csrf.token}"/>
	<!-- default header name is X-CSRF-TOKEN -->
	<meta name="_csrf_header" content="${_csrf.headerName}"/>
	<!-- ... -->
</head>
<!-- ... -->

2、CSRF注意事项

在实施针对CSRF攻击的保护时,需要考虑一些特殊因素。本节讨论了与servlet环境相关的这些注意事项。有关更一般性的讨论,请参阅CSRF注意事项。

2.1 登录

对于登录请求,要求CSRF以防止伪造登录尝试,这一点很重要。Spring Security的servlet支持开箱即用。

2.2 注销

对于注销请求,要求CSRF以防止伪造的注销尝试,这一点很重要。如果启用了CSRF保护(默认设置),则Spring Security的LogoutFilter将仅处理HTTP POST。这样可以确保注销需要CSRF令牌,并且恶意用户不能强制注销您的用户。
最简单的方法是使用表单注销。如果你真的想要一个链接,你可以使用JavaScript让链接执行POST(可能是在隐藏的表单上)。对于禁用了JavaScript的浏览器,您可以选择让链接将用户带到执行POST的注销确认页面。
如果你真的想在注销时使用HTTPGET,你可以这样做。但是,请记住,通常不建议这样做。例如,当使用任何HTTP方法请求/logoout URL时,以下Java配置将注销:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			.logout(logout -> logout
				.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
			);
		return http.build();
	}
}

2.3 CSRF和会话超时

默认情况下,Spring Security将CSRF令牌存储在HttpSession中。这可能会导致会话过期,从而没有CSRF令牌可供验证。
我们已经讨论了会话超时的一般解决方案。本节讨论CSRF超时的细节,因为它与servlet支持有关。
您可以将CSRF令牌的存储更改为cookie中的存储。有关详细信息,请参阅Custom CsrfTokenRepository部分。
如果令牌确实过期,您可能希望通过指定自定义AccessDeniedHandler来自定义处理方式。自定义AccessDeniedHandler可以以任何您喜欢的方式处理InvalidCsrfTokenException。有关如何自定义AccessDeniedHandler的示例,请参阅提供的xml和Java配置链接。

2.4 Multipart (文件上传)

我们已经讨论了保护多部分请求(文件上传)免受CSRF攻击是如何导致鸡和蛋问题的。本节讨论如何实现将CSRF令牌放置在servlet应用程序的主体和url中。

将CSRF令牌放入主体
我们已经讨论了在主体中放置CSRF令牌的权衡。在本节中,我们将讨论如何配置Spring Security以从主体读取CSRF。
要从主体读取CSRF令牌,在Spring Security筛选器之前指定MultipartFilter。在Spring Security筛选器之前指定MultipartFilter意味着没有调用MultipartFilter的授权,这意味着任何人都可以在服务器上放置临时文件。但是,只有经过授权的用户才能提交由您的应用程序处理的文件。通常,这是推荐的方法,因为临时文件上传对大多数服务器的影响应该可以忽略不计。
为了确保在使用XML配置的Spring Security筛选器之前指定MultipartFilter,可以确保将MultipartFilter的<filter mapping>元素放置在web.XML文件中的springSecurityFilterChain之前:

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

	@Override
	protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
		insertFilters(servletContext, new MultipartFilter());
	}
}

为了确保在使用XML配置的Spring Security筛选器之前指定MultipartFilter,用户可以确保将MultipartFilter的<filter mapping>元素放置在web.XML中的springSecurityFilterChain之前,如下所示:

<filter>
	<filter-name>MultipartFilter</filter-name>
	<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>MultipartFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

在URL中包含CSRF令牌
如果允许未经授权的用户上传临时文件是不可接受的,那么另一种选择是将MultipartFilter放在Spring Security筛选器之后,并将CSRF作为查询参数包含在表单的action属性中。由于CsrfToken公开为HttpServletRequest请求属性,因此我们可以使用它来创建包含CSRF令牌的action。以下示例使用JSP来实现这一点:

<form method="post"
	action="./upload?${_csrf.parameterName}=${_csrf.token}"
	enctype="multipart/form-data">

HiddenHttpMethodFilter

我们已经讨论了在主体中放置CSRF令牌的权衡。
在Spring的Servlet支持中,通过使用HiddenHttpMethodFilter来覆盖HTTP方法。您可以在参考文档的HTTP方法转换部分找到更多信息。