# Servlet 环境中的跨站点请求伪造

本节讨论 Spring Security对 Servlet 环境的[跨站点请求伪造](../../features/exploits/csrf.html#csrf)支持。

## 使用 Spring 安全CSRF保护

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

* [使用适当的HTTP动词](#servlet-csrf-idempotent)

* [配置CSRF保护](#servlet-csrf-configure)

* [包括CSRF令牌](#servlet-csrf-include)

### 使用适当的HTTP动词

防止CSRF攻击的第一步是确保你的网站使用正确的HTTP动词。这在[安全的方法必须是幂等的。](../../features/exploits/csrf.html#csrf-protection-idempotent)中有详细介绍。

### 配置CSRF保护

下一步是在应用程序中配置 Spring Security的CSRF保护。 Spring 默认情况下，Security的CSRF保护是启用的，但你可能需要定制配置。下面是一些常见的定制。

#### 自定义CSRFTokenRepository

Spring 默认情况下，Security使用`HttpSessionCsrfTokenRepository`将预期的CSRF令牌存储在`HttpSession`中。在某些情况下，用户可能希望配置自定义`CsrfTokenRepository`。例如，可能希望将cookie中的`CsrfToken`持久化到[支持基于JavaScript的应用程序](#servlet-csrf-include-ajax-auto)。

默认情况下，`CookieCsrfTokenRepository`将写到一个名为`XSRF-TOKEN`的cookie，并从一个名为`X-XSRF-TOKEN`的头部或HTTP参数`_csrf`读取它。这些默认值来自[AngularJS](https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection)

可以使用以下方法在XML中配置`CookieCsrfTokenRepository`:

例1.使用XML配置在cookie中存储CSRF令牌

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

|   |示例显式设置`cookieHttpOnly=false`.<br/>这是允许JavaScript（即Angularjs）读取它所必需的。<br/>如果不需要直接使用JavaScript读取cookie的能力，建议省略`cookieHttpOnly=false`以提高安全性。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

你可以在 Java 配置中使用以下方法配置`CookieCsrfTokenRepository`:

例2.在cookie中存储CSRF令牌

Java

```
@EnableWebSecurity
public class WebSecurityConfig extends
		WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) {
		http
			.csrf(csrf -> csrf
				.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
			);
	}
}
```

Kotlin

```
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
       http {
            csrf {
                csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
            }
        }
    }
}
```

|   |示例显式设置`cookieHttpOnly=false`.<br/>这是允许JavaScript（即Angularjs）读取它所必需的。<br/>如果不需要直接使用JavaScript读取cookie的能力，建议省略`cookieHttpOnly=false`（通过使用`new CookieCsrfTokenRepository()`代替）以提高安全性。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 禁用CSRF保护

默认情况下启用了CSRF保护。但是，如果CSRF保护[对你的应用程序来说是有意义的](../../features/exploits/csrf.html#csrf-when)，则禁用CSRF保护非常简单。

下面的XML配置将禁用CSRF保护。

例3.禁用CSRF XML配置

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

下面的 Java 配置将禁用CSRF保护。

例4.禁用CSRF

Java

```
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends
		WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) {
		http
			.csrf(csrf -> csrf.disable());
	}
}
```

Kotlin

```
@Configuration
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
       http {
            csrf {
                disable()
            }
        }
    }
}
```

### 包括CSRF令牌

为了使[同步器令牌模式](../../features/exploits/csrf.html#csrf-protection-stp)能够抵御CSRF攻击，我们必须在HTTP请求中包含实际的CSRF令牌。这必须包含在请求的一部分（即表单参数、HTTP头等）中，而该部分不是由浏览器自动包含在HTTP请求中的。

Spring Security的[CsrfFilter](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfFilter.html)将[CsrfToken](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfToken.html)公开为名为`HttpServletRequest`的属性。这意味着，任何视图技术都可以访问`CsrfToken`以将预期的令牌公开为[form](#servlet-csrf-include-form-attr)或[meta tag](#servlet-csrf-include-ajax-meta-attr)。幸运的是，下面列出的集成使得在[form](#servlet-csrf-include-form)和[ajax](#servlet-csrf-include-ajax)请求中包含令牌变得更加容易。

#### 表单URL编码

为了发布HTML表单，CSRF令牌必须作为隐藏输入包含在表单中。例如，呈现的HTML可能看起来像：

例5. CSRF令牌HTML

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

接下来，我们将讨论将CSRF令牌以一种形式包含为隐藏输入的各种方法。

##### CSRF令牌自动包含

Spring Security的CSRF支持通过其[CsrfrequestDataValueProcessor](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.html)提供与 Spring 的[RequestDataValueProcessor](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/support/RequestDataValueProcessor.html)的集成。这意味着，如果你利用[Spring’s form tag library](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib)、[Thymeleaf](https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor)或与`RequestDataValueProcessor`集成的任何其他视图技术，那么具有不安全的HTTP方法（即POST）的窗体将自动包括实际的CSRF令牌。

##### csrfinput标记

如果你正在使用JSP，那么你可以使用[Spring’s form tag library](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib)。但是，如果这不是一个选项，你也可以很容易地将令牌包含在[csrfInput](../integrations/jsp-taglibs.html#taglibs-csrfinput)标记中。

##### CSRFToken请求属性

如果用于在请求中包含实际CSRF令牌的[其他选择](#servlet-csrf-include)不起作用，则可以利用以下事实：`CsrfToken`[is exposed](#servlet-csrf-include)作为`HttpServletRequest`属性，该属性名为`_csrf`。

使用JSP执行此操作的示例如下所示：

例6.具有请求属性的表单中的CSRF令牌

```
<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 安全性可以很容易地[configured](#servlet-csrf-configure-custom-repository)将预期的CSRF令牌存储在cookie中。通过将预期的CSRF存储在Cookie中，像[AngularJS](https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection)这样的JavaScript框架将自动在HTTP请求头中包含实际的CSRF令牌。

##### 元标签

[在cookie中暴露CSRF](#servlet-csrf-include-form-auto)的另一种模式是在`meta`标记中包含CSRF标记。HTML可能看起来是这样的：

例7. 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，可以通过以下方式完成此操作：

例8. Ajax发送CSRF令牌

```
$(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令牌写入`meta`标记的一种简单方法是利用[csrfMeta](../integrations/jsp-taglibs.html#taglibs-csrfmeta)标记。

###### CSRFToken请求属性

如果用于在请求中包含实际CSRF令牌的[其他选择](#servlet-csrf-include)不起作用，则可以利用以下事实：`CsrfToken`[is exposed](#servlet-csrf-include)作为`HttpServletRequest`属性，该属性名为`_csrf`。使用JSP执行此操作的示例如下所示：

例9. CSRF元标记JSP

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

## CSRF考虑因素

在实施针对CSRF攻击的保护时，有几个特殊的考虑因素需要考虑。本节讨论与 Servlet 环境相关的那些考虑因素。有关更一般性的讨论，请参见[CSRF考虑因素](../../features/exploits/csrf.html#csrf-considerations)。

### 登录

这是重要的[需要CSRF才能登录](../../features/exploits/csrf.html#csrf-considerations-login)请求，以防止伪造日志的企图。 Spring Security的 Servlet 支持是开箱即用的。

### 注销

重要的是[需要CSRF才能注销](../../features/exploits/csrf.html#csrf-considerations-logout)请求，以防止伪造注销尝试。如果启用了CSRF保护（默认）， Spring Security的`LogoutFilter`将仅处理HTTP POST。这确保了注销需要CSRF令牌，并且恶意用户不能强制注销你的用户。

最简单的方法是使用表单注销。如果你真的想要一个链接，可以使用JavaScript让该链接执行一个POST（例如，可能在一个隐藏的表单上）。对于禁用了JavaScript的浏览器，你可以选择让链接将用户带到将执行POST的注销确认页面。

如果你真的想使用HTTP GET与注销，你可以这样做，但请记住，这通常是不推荐的。例如，以下 Java 配置将使用任何HTTP方法请求的URL执行注销：

例10.用HTTP GET登出

Java

```
@EnableWebSecurity
public class WebSecurityConfig extends
		WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) {
		http
			.logout(logout -> logout
				.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
			);
	}
}
```

Kotlin

```
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
       http {
            logout {
                logoutRequestMatcher = AntPathRequestMatcher("/logout")
            }
        }
    }
}
```

### CSRF和会话暂停

默认情况下， Spring Security将CSRF令牌存储在`HttpSession`中。这可能导致会话过期的情况，这意味着没有预期的CSRF令牌来验证。

我们已经讨论了[一般解决方案](../../features/exploits/csrf.html#csrf-considerations-login)到会话的超时。本节讨论CSRF超时的细节，因为它与 Servlet 支持有关。

将预期的CSRF令牌的存储更改为cookie中的存储是很简单的。有关详细信息，请参阅[自定义CSRFTokenRepository](#servlet-csrf-configure-custom-repository)部分。

如果一个令牌确实过期了，你可能希望通过指定一个自定义`AccessDeniedHandler`来定制它的处理方式。自定义`AccessDeniedHandler`可以以任何方式处理`InvalidCsrfTokenException`。对于如何自定义`AccessDeniedHandler`的示例，请参阅为[xml](../appendix/namespace/http.html#nsa-access-denied-handler)和[Java configuration](https://github.com/spring-projects/spring-security/tree/5.6.2/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java#L64)提供的链接。

### 

我们有[已经讨论过了](../../features/exploits/csrf.html#csrf-considerations-multipart)如何保护多部分请求（文件上传）不受CSRF攻击导致[鸡和蛋](https://en.wikipedia.org/wiki/Chicken_or_the_egg)问题。本节讨论如何在 Servlet 应用程序中实现将CSRF令牌放置在[body](#servlet-csrf-considerations-multipart-body)和[url](#servlet-csrf-considerations-multipart-url)中。

|   |关于使用具有 Spring 的多部分表单的更多信息，可以在 Spring 引用的[1.1.11. 多部分旋转变压器](https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web.html#mvc-multipart)部分和[MultipartFilter Javadoc](https://docs.spring.io/spring/docs/5.2.x/javadoc-api/org/springframework/web/multipart/support/MultipartFilter.html)中找到。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 将CSRF标记放入体内

我们有[已经讨论过了](../../features/exploits/csrf.html#csrf-considerations-multipart-body)在主体中放置CSRF标记的权衡。在本节中，我们将讨论如何配置 Spring 安全性，以便从主体读取CSRF。

为了从主体中读取CSRF令牌，在 Spring 安全过滤器之前指定了`MultipartFilter`。在 Spring 安全过滤器之前指定`MultipartFilter`意味着没有调用`MultipartFilter`的授权，这意味着任何人都可以在你的服务器上放置临时文件。但是，只有经过授权的用户才能提交由你的应用程序处理的文件。通常，这是推荐的方法，因为临时文件上传对大多数服务器的影响可以忽略不计。

为了确保`MultipartFilter`是在 Spring 安全过滤器 Java 配置之前指定的，用户可以在SpringSecurityFilterchain之前覆盖，如下所示：

例11.初始化器MultipartFilter

Java

```
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

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

Kotlin

```
class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer() {
    override fun beforeSpringSecurityFilterChain(servletContext: ServletContext?) {
        insertFilters(servletContext, MultipartFilter())
    }
}
```

为了确保在使用XML配置的 Spring 安全过滤器之前指定`MultipartFilter`，用户可以确保将`MultipartFilter`元素的\<filter-mapping\>元素放置在web.xml中的SpringSecurityFilterchain之前，如下所示：

示例12.web.xml-multipartfilter

```
<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 安全过滤器之后，并将CSRF作为查询参数包含在表单的动作属性中。由于`CsrfToken`被公开为`HttpServletRequest`[请求属性](#servlet-csrf-include)，因此我们可以使用它来创建带有CSRF令牌的`action`。下面显示了一个带有JSP的示例。

例13. CSRF令牌正在运行

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

### HiddenHttpMethodFilter

我们有[已经讨论过了](../../features/exploits/csrf.html#csrf-considerations-multipart-body)在主体中放置CSRF标记的权衡。

在 Spring 的 Servlet 支持中，覆盖HTTP方法是使用[HiddenHttpMethodFilter](https://docs.spring.io/spring-framework/docs/5.2.x/javadoc-api/org/springframework/web/filter/reactive/HiddenHttpMethodFilter.html)完成的。更多信息可以在参考文档的[HTTP方法转换](https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web.html#mvc-rest-method-conversion)部分中找到。

[保护免受剥削](index.html)[安全HTTP响应标头](headers.html)

