提交 bb2e5acd 编写于 作者: dallascao's avatar dallascao

white MT translated

上级 3ceb52f9
此差异已折叠。
# Spring 安全共同体
欢迎来到 Spring 安全社区!本节讨论如何充分利用我们庞大的社区。
## 获得帮助
如果你在安全方面需要帮助,我们会提供帮助。以下是获得帮助的一些最佳方式:
* 通读这份文件。
* 试试我们的许多[示例应用程序](samples.html#samples)中的一个。
*`spring-security`标记在[https://stackoverflow.com](https://stackoverflow.com/questions/tagged/spring-security)上提问。
*[https://github.com/spring-projects/spring-security/issues](https://github.com/spring-projects/spring-security/issues)上报告错误和增强请求
## 参与
我们欢迎你参与 Spring 安全项目。有很多方法可以帮助你,包括回答有关堆栈溢出的问题,编写新代码,改进现有代码,协助编写文档,开发示例或教程,报告错误,或者只是提出建议。有关更多信息,请参见我们的[贡献](https://github.com/spring-projects/spring-security/blob/main/CONTRIBUTING.adoc)文档。
## 源代码
你可以在 GitHub 上找到 Spring Security 的源代码,网址为[https://github.com/spring-projects/spring-security/](https://github.com/spring-projects/spring-security/)
## Apache 2 许可证
Spring 安全性是在[Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.html)下发布的开源软件。
## 社交媒体
你可以在 Twitter 上关注[@SpringSecurity](https://twitter.com/SpringSecurity)[Spring Security team](https://twitter.com/SpringSecurity/lists/team),了解最新消息。你还可以跟踪[@SpringCentral](https://twitter.com/SpringCentral),以了解整个 Spring 投资组合的最新情况。
\ No newline at end of file
# 认证
Spring 安全性为[认证](https://en.wikipedia.org/wiki/Authentication)提供了全面的支持。身份验证是指我们如何验证试图访问特定资源的人的身份。验证用户身份的一种常见方法是要求用户输入用户名和密码。一旦进行了身份验证,我们就知道了身份并可以执行授权。
Spring 安全性为用户身份验证提供了内置支持。本节专门介绍在 Servlet 和 WebFlux 环境中都适用的通用身份验证支持。有关每个堆栈支持什么的详细信息,请参阅[Servlet](../../servlet/authentication/index.html#servlet-authentication)和 WebFlux 的身份验证部分。
\ No newline at end of file
# 跨站点请求伪造
Spring 为防范[跨站点请求伪造](https://en.wikipedia.org/wiki/Cross-site_request_forgery)攻击提供了全面的支持。在以下几节中,我们将探讨:
* [什么是 CSRF 攻击?](#csrf-explained)
* [防范 CSRF 攻击](#csrf-protection)
* [CSRF 考虑因素](#csrf-considerations)
| |这部分的文档讨论了 CSRF 保护的一般主题。<br/>关于基于[servlet](../../servlet/exploits/csrf.html#servlet-csrf)[WebFlux](../../reactive/exploits/csrf.html#webflux-csrf)的应用程序的 CSRF 保护的具体信息,请参阅相关章节。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
## 什么是 CSRF 攻击?
理解 CSRF 攻击的最好方法是看一个具体的例子。
假设你的银行的网站提供了一种表单,允许将当前登录的用户的资金转移到另一个银行帐户。例如,传输表单可能看起来像:
例 1。转移形式
```
<form method="post"
action="/transfer">
<input type="text"
name="amount"/>
<input type="text"
name="routingNumber"/>
<input type="text"
name="account"/>
<input type="submit"
value="Transfer"/>
</form>
```
相应的 HTTP 请求可能看起来像:
例 2。传输 HTTP 请求
```
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876
```
现在,假装你对银行的网站进行了认证,然后在不登出的情况下,访问一个邪恶的网站。邪恶网站包含一个 HTML 页面,其形式如下:
例 3。邪恶转移形式
```
<form method="post"
action="https://bank.example.com/transfer">
<input type="hidden"
name="amount"
value="100.00"/>
<input type="hidden"
name="routingNumber"
value="evilsRoutingNumber"/>
<input type="hidden"
name="account"
value="evilsAccountNumber"/>
<input type="submit"
value="Win Money!"/>
</form>
```
你想赢钱,所以你点击提交按钮。在此过程中,你无意中向恶意用户转移了 100 美元。发生这种情况的原因是,虽然邪恶的网站无法看到你的 cookie,但与你的银行相关的 cookie 仍会随请求一起发送。
最糟糕的是,整个过程都可以使用 JavaScript 实现自动化。这意味着你甚至不需要点击这个按钮。此外,当访问一个受[XSS attack](https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)影响的诚实网站时,也很容易发生这种情况。那么,我们如何保护我们的用户免受此类攻击呢?
## 防范 CSRF 攻击
可能发生 CSRF 攻击的原因是,来自受害者网站的 HTTP 请求和来自攻击者网站的请求完全相同。这意味着,无法拒绝来自邪恶网站的请求,也无法允许来自该行网站的请求。为了防止 CSRF 攻击,我们需要确保请求中有邪恶站点无法提供的内容,因此我们可以区分这两个请求。
Spring 提供了两种机制来防止 CSRF 攻击:
* the[同步器令牌模式](#csrf-protection-stp)
* 在会话cookie 上指定[Samesite 属性](#csrf-protection-ssa)
| |这两种保护都要求[安全的方法必须是幂等的。](#csrf-protection-idempotent)|
|---|--------------------------------------------------------------------------------------------|
### 安全的方法必须是幂等的。
为了使针对 CSRF 的[要么保护](#csrf-protection)工作,应用程序必须确保[“安全的”HTTP 方法是幂等的](https://tools.ietf.org/html/rfc7231#section-4.2.1)。这意味着使用 http 方法`GET``HEAD``OPTIONS``TRACE`的请求不应改变应用程序的状态。
### 同步器令牌模式
防止 CSRF 攻击的主要和最全面的方法是使用[同步器令牌模式](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern)。这种解决方案是为了确保每个 HTTP 请求,除了我们的会话cookie 之外,还必须在 HTTP 请求中存在一个安全的随机生成值,称为 CSRF 令牌。
当提交 HTTP 请求时,服务器必须查找预期的 CSRF 令牌,并将其与 HTTP 请求中的实际 CSRF 令牌进行比较。如果值不匹配,则应拒绝 HTTP 请求。
这一工作的关键是,实际的 CSRF 令牌应该位于 HTTP 请求的一部分,该部分不会被浏览器自动包含。例如,在 HTTP 参数或 HTTP 报头中要求实际的 CSRF 令牌,可以防止 CSRF 攻击。在 Cookie 中要求实际的 CSRF 令牌是不起作用的,因为浏览器会自动将 Cookie 包含在 HTTP 请求中。
我们可以放松预期,只需要每个更新应用程序状态的 HTTP 请求的实际 CSRF 令牌。要使其工作,我们的应用程序必须确保[安全的 HTTP 方法是幂等的](#csrf-protection-idempotent)。这提高了可用性,因为我们希望允许使用外部站点的链接来链接到我们的网站。此外,我们不想在 HTTP GET 中包含随机令牌,因为这可能导致令牌泄漏。
让我们来看看使用 Synchronizer 令牌模式时[我们的例子](#csrf-explained)将如何更改。假设实际的 CSRF 令牌需要位于一个名为`_csrf`的 HTTP 参数中。我们的申请的转移表看起来是这样的:
例 4。同步器令牌形式
```
<form method="post"
action="/transfer">
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="text"
name="amount"/>
<input type="text"
name="routingNumber"/>
<input type="hidden"
name="account"/>
<input type="submit"
value="Transfer"/>
</form>
```
表单现在包含一个隐藏的输入,其值为 CSRF 令牌。外部站点无法读取 CSRF 令牌,因为相同的源策略确保邪恶站点无法读取响应。
相应的转移资金的 HTTP 请求将如下所示:
例 5。同步器令牌请求
```
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
```
你将注意到,HTTP 请求现在包含带有安全随机值的`_csrf`参数。当服务器将实际的 CSRF 令牌与预期的 CSRF 令牌进行比较时,Evil 网站将无法为`_csrf`参数(必须在 Evil 网站上显式提供)提供正确的值,并且传输将失败。
### Samesite 属性
防止[CSRF 攻击](#csrf)的一种新兴方法是在 cookie 上指定[Samesite 属性](https://tools.ietf.org/html/draft-west-first-party-cookies)。服务器在设置 cookie 时可以指定`SameSite`属性,以指示当来自外部站点时不应发送 cookie。
| |Spring 安全性并不直接控制会话cookie 的创建,因此它不提供对 Samesite 属性的支持。[Spring Session](https://spring.io/projects/spring-session)在基于 Servlet 的应用程序中提供了对`SameSite`属性的支持。<br/> Spring 框架的[CookiewebessionDresolver](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/server/session/CookieWebSessionIdResolver.html)在基于 WebFlux 的应用程序中提供了对`SameSite`属性的开箱即用支持。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
例如,具有`SameSite`属性的 HTTP 响应头可能看起来如下所示:
例 6。Samesite HTTP 响应
```
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax
```
`SameSite`属性的有效值是:
* `Strict`-当指定时,来自[same-site](https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-2.1)的任何请求都将包括 cookie。否则,Cookie 将不会包含在 HTTP 请求中。
* `Lax`-当指定的 cookie 来自[same-site](https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-2.1)或当请求来自顶级导航和[方法是幂等的](#csrf-protection-idempotent)时将被发送。否则,Cookie 将不会包含在 HTTP 请求中。
让我们来看看如何使用[我们的例子](#csrf-explained)属性来保护`SameSite`。银行应用程序可以通过在会话cookie 上指定`SameSite`属性来防止 CSRF。
通过在我们的会话cookie 上设置`SameSite`属性,浏览器将继续发送来自银行网站的请求的`JSESSIONID`cookie。但是,浏览器将不再发送带有来自 Evil 网站的传输请求的`JSESSIONID`cookie。由于会话在来自邪恶网站的传输请求中不再存在,因此应用程序受到 CSRF 攻击的保护。
在使用`SameSite`属性来防止 CSRF 攻击时,应该注意一些重要的[注意事项](https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-5)属性。
`SameSite`属性设置为`Strict`提供了更强的防御能力,但可能会使用户感到困惑。考虑一个用户保持登录到托管在[https://social.example.com](https://social.example.com)的社交媒体网站。用户在[https://email.example.org](https://email.example.org)处收到一封电子邮件,其中包括一个到该社交媒体网站的链接。如果用户点击了该链接,他们理所当然地希望得到该社交媒体网站的认证。但是,如果`SameSite`属性是`Strict`,则不会发送 cookie,因此不会对用户进行身份验证。
| |通过实现[gh-7537](https://github.com/spring-projects/spring-security/issues/7537),可以提高`SameSite`对 CSRF 攻击的防护能力和可用性。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
另一个明显的考虑因素是,为了让`SameSite`属性保护用户,浏览器必须支持`SameSite`属性。大多数现代浏览器都[支持 Samesite 属性](https://developer.mozilla.org/en-US/docs/Web/HTTP/headers/Set-Cookie#Browser_compatibility)。然而,仍在使用的旧浏览器可能不会。
出于这个原因,通常建议使用`SameSite`属性作为深度防御,而不是针对 CSRF 攻击的唯一保护。
## 何时使用 CSRF 保护
你什么时候应该使用 CSRF 保护?我们的建议是,对于普通用户可能通过浏览器处理的任何请求,使用 CSRF 保护。如果你只是创建一个由非浏览器客户端使用的服务,那么你可能希望禁用 CSRF 保护。
### CSRF 保护和 JSON
一个常见的问题是“我需要保护 JavaScript 发出的 JSON 请求吗?”简短的回答是,这要看情况而定。但是,你必须非常小心,因为有 CSRF 漏洞可能会影响 JSON 请求。例如,恶意用户可以创建[使用以下表单使用 JSON 的 CSRF](http://blog.opensecurityresearch.com/2012/02/json-csrf-with-parameter-padding.html):
例 7。使用 JSON 表单的 CSRF
```
<form action="https://bank.example.com/transfer" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
```
这将产生以下 JSON 结构
例 8。CSRF 与 JSON 请求
```
{ "amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}
```
如果应用程序没有验证 Content-type,那么它就会受到此攻击。根据设置,验证 Content-type 的 Spring MVC 应用程序仍然可以通过将 URL 后缀更新为`.json`来利用该漏洞,如下所示:
例 9。使用 JSON Spring MVC 表单的 CSRF
```
<form action="https://bank.example.com/transfer.json" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
```
### CSRF 和无状态浏览器应用程序
如果我的应用程序是无状态的,该怎么办?这并不一定意味着你受到了保护。实际上,如果用户不需要在 Web 浏览器中为给定的请求执行任何操作,那么他们很可能仍然容易受到 CSRF 攻击。
例如,考虑一个应用程序,它使用一个自定义 Cookie 来进行身份验证,而不是使用 JSessionID。当发生 CSRF 攻击时,将以与我们上一个示例中发送 JSessionID Cookie 相同的方式发送自定义 Cookie 和请求。此应用程序易受 CSRF 攻击。
使用基本身份验证的应用程序也容易受到 CSRF 攻击。该应用程序很容易受到攻击,因为浏览器将在任何请求中自动包括用户名和密码,其方式与我们上一个示例中发送的 JSessionID cookie 的方式相同。
## CSRF 考虑因素
在实施针对 CSRF 攻击的保护时,有几个特殊的考虑因素需要考虑。
### 登录
为了防止[请求中的伪造日志](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Forging_login_requests),应该保护 HTTP 请求中的日志不受 CSRF 攻击。防止在请求中伪造日志是必要的,这样恶意用户就无法读取受害者的敏感信息。攻击的执行方式如下:
* 恶意用户使用恶意用户的凭据执行 CSRF 登录。受害者现在被认证为恶意用户。
* 然后,恶意用户诱使受害者访问受损网站并输入敏感信息。
* 该信息与恶意用户的帐户相关联,因此恶意用户可以使用自己的凭据登录并查看 Vicitim 的敏感信息。
确保登录 HTTP 请求不受 CSRF 攻击的一个可能的复杂情况是,用户可能会经历会话超时,这会导致请求被拒绝。会话超时对于那些预计不需要会话才能登录的用户来说是令人惊讶的。有关更多信息,请参见[CSRF 和会话暂停](#csrf-considerations-timeouts)
### 注销
为了防止伪造注销请求,应该保护注销 HTTP 请求不受 CSRF 攻击。防止伪造注销请求是必要的,这样恶意用户就无法读取受害者的敏感信息。有关攻击的详细信息,请参阅[这篇博文](https://labs.detectify.com/2017/03/15/loginlogout-csrf-time-to-reconsider/)
确保退出 HTTP 请求不受 CSRF 攻击的一个可能的复杂情况是,用户可能会经历会话超时,这会导致请求被拒绝。会话超时对于那些不期望为了注销而需要会话超时的用户来说是令人惊讶的。有关更多信息,请参见[CSRF and Session Timeouts](#csrf-considerations-timeouts)
### CSRF and Session Timeouts
通常,期望的 CSRF 令牌存储在会话中。这意味着,一旦会话过期,服务器将找不到预期的 CSRF 令牌并拒绝 HTTP 请求。解决超时问题有多种选择,每种选择都需要权衡利弊。
* 减少超时的最佳方法是使用 JavaScript 在表单提交时请求 CSRF 令牌。然后用 CSRF 令牌更新表单并提交表单。
* 另一种选择是使用一些 JavaScript,让用户知道他们的会话即将到期。用户可以单击按钮继续并刷新会话。
* 最后,预期的 CSRF 令牌可以存储在 Cookie 中。这允许预期的 CSRF 令牌比会话有效。
有人可能会问,为什么预期的 CSRF 令牌在默认情况下没有存储在 Cookie 中。这是因为存在已知的利用漏洞攻击,其中的头(例如,指定 cookie)可以由另一个域设置。这也是 Ruby on Rails[当标题 x-request-with 出现时,不再跳过 CSRF 检查](https://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails/)的原因。有关如何执行此漏洞利用的详细信息,请参见[这个 webappsec.org 线程](https://web.archive.org/web/20210221120355/https://lists.webappsec.org/pipermail/websecurity_lists.webappsec.org/2011-February/007533.html)。另一个缺点是,通过删除状态(即超时),如果令牌受到损害,你将失去强制使其无效的能力。
###
保护多部分请求(文件上传)不受 CSRF 攻击会导致[鸡和蛋](https://en.wikipedia.org/wiki/Chicken_or_the_egg)问题。为了防止 CSRF 攻击的发生,必须读取 HTTP 请求的主体以获得实际的 CSRF 令牌。然而,读取主体意味着文件将被上传,这意味着外部站点可以上传文件。
有两个选择使用 CSRF 保护与多部分/形式数据。每一种选择都有其利弊得失。
* [将 CSRF 标记放入体内](#csrf-considerations-multipart-body)
* [在 URL 中放置 CSRF 令牌](#csrf-considerations-multipart-url)
| |在将 Spring Security 的 CSRF 保护与多部分文件上载集成在一起之前,请确保首先可以在没有 CSRF 保护的情况下进行上传。<br/>关于使用 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 标记放入体内
第一个选项是在请求主体中包含实际的 CSRF 令牌。通过将 CSRF 令牌放置在主体中,主体将在执行授权之前被读取。这意味着任何人都可以在你的服务器上放置临时文件。但是,只有经过授权的用户才能提交由你的应用程序处理的文件。通常,这是推荐的方法,因为临时文件上传对大多数服务器的影响可以忽略不计。
#### 在 URL 中包含 CSRF 令牌
如果允许未经授权的用户上传临时文件是不可接受的,那么另一种选择是在表单的 Action 属性中包含预期的 CSRF 令牌作为查询参数。这种方法的缺点是查询参数可能会泄露。更普遍地说,将敏感数据放置在主体或标头内以确保其不会泄漏被认为是最佳实践。其他信息可以在[RFC2616 第 15.1.3 节在 URI 中对敏感信息进行编码](https://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3)中找到。
#### HiddenHttpMethodFilter
在某些应用程序中,表单参数可用于覆盖 HTTP 方法。例如,下面的表单可以用来将 HTTP 方法视为`delete`,而不是`post`
例 10。CSRF 隐藏 HTTP 方法表单
```
<form action="/process"
method="post">
<!-- ... -->
<input type="hidden"
name="_method"
value="delete"/>
</form>
```
在筛选器中重写 HTTP 方法。这个过滤器必须放在安全部门的支持之前。请注意,覆盖只发生在`post`上,因此这实际上不太可能导致任何实际问题。然而,最好的做法仍然是确保将其置于 Spring Security 的过滤器之前。
\ No newline at end of file
此差异已折叠。
# HTTP
所有基于 HTTP 的通信,包括[静态资源](https://www.troyhunt.com/heres-why-your-static-website-needs-https/),都应该受到[using TLS](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html)的保护。
作为一个框架, Spring 安全性不处理 HTTP 连接,因此不直接提供对 HTTPS 的支持。然而,它确实提供了许多有助于 HTTPS 使用的功能。
## 重定向到 HTTPS
当客户端使用 HTTP 时, Spring 安全性可以被配置为将[Servlet](../../servlet/exploits/http.html#servlet-http-redirect)[WebFlux](../../reactive/exploits/http.html#webflux-http-redirect)环境重定向到 HTTPS。
## 严格的运输安全
Spring 安全性为[严格的运输安全](headers.html#headers-hsts)提供支持,并在默认情况下启用它。
## 代理服务器配置
在使用代理服务器时,重要的是要确保你已经正确地配置了应用程序。例如,许多应用程序将有一个负载均衡器,该负载均衡器通过在[https://192.168.1:8080](https://192.168.1:8080)处将请求转发到应用程序服务器来响应[https://example.com/](https://example.com/)的请求。如果没有适当的配置,应用程序服务器将不知道负载均衡器的存在,并将请求视为[https://192.168.1:8080](https://192.168.1:8080)是由客户机请求的。
要解决这个问题,你可以使用[RFC 7239](https://tools.ietf.org/html/rfc7239)来指定正在使用负载均衡器。要使应用程序意识到这一点,你需要配置你的应用程序服务器来了解 X 转发头。例如, Tomcat 使用[Remoteipvalve](https://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html), Jetty 使用[ForwardeDrequestCustomizer](https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/server/ForwardedRequestCustomizer.html)。或者, Spring 用户可以利用[ForwardedHeaderFilter](https://github.com/spring-projects/spring-framework/blob/v4.3.3.RELEASE/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java)
Spring 引导用户可以使用`server.use-forward-headers`属性来配置应用程序。有关更多详细信息,请参见[Spring Boot documentation](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-use-tomcat-behind-a-proxy-server)
\ No newline at end of file
# 保护免受剥削
Spring 安全性提供了针对常见攻击的保护。只要有可能,默认情况下都会启用该保护。下面,你将看到安全防范的各种攻击的高级描述。
## 章节摘要
* [CSRF](csrf.html)
* [HTTP 头](headers.html)
* [HTTP 请求](http.html)
\ No newline at end of file
# 并发支持
在大多数环境中,安全性是以 per`Thread`为基础存储的。这意味着,当在新的`Thread`上完成工作时,`SecurityContext`将丢失。 Spring 安全性提供了一些基础设施,以帮助用户更容易地实现这一点。 Spring 安全性为在多线程环境中使用 Spring 安全性提供了低层次的抽象。事实上,这就是 Spring 安全性构建到与[AsyncContext.start(可运行)](../../servlet/integrations/servlet-api.html#servletapi-start-runnable)[Spring MVC Async Integration](../../servlet/integrations/mvc.html#mvc-async)集成的基础。
## 在可撤销的情况下将证券转让
Spring Security 的并发支持中最基本的构建块之一是`DelegatingSecurityContextRunnable`。它包装了一个委托`Runnable`,以便用指定的`SecurityContext`为委托初始化`SecurityContextHolder`。然后,它调用委托 Runnable 确保在之后清除`SecurityContextHolder``DelegatingSecurityContextRunnable`看起来是这样的:
爪哇
```
public void run() {
try {
SecurityContextHolder.setContext(securityContext);
delegate.run();
} finally {
SecurityContextHolder.clearContext();
}
}
```
Kotlin
```
fun run() {
try {
SecurityContextHolder.setContext(securityContext)
delegate.run()
} finally {
SecurityContextHolder.clearContext()
}
}
```
虽然非常简单,但它可以无缝地将 SecurityContext 从一个线程转移到另一个线程。这一点很重要,因为在大多数情况下,SecurityContextholder 是以每个线程为基础的。例如,你可能使用了 Spring Security 的[\<global-method-security\>](../../servlet/appendix/namespace/method-security.html#nsa-global-method-security)支持来保护你的某个服务。现在,你可以轻松地将当前`Thread``SecurityContext`传输到调用安全服务的`Thread`。下面是你如何做到这一点的一个示例:
爪哇
```
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable, context);
new Thread(wrappedRunnable).start();
```
Kotlin
```
val originalRunnable = Runnable {
// invoke secured service
}
val context: SecurityContext = SecurityContextHolder.getContext()
val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable, context)
Thread(wrappedRunnable).start()
```
上面的代码执行以下步骤:
* 创建将调用我们的安全服务的`Runnable`。请注意,它并不了解 Spring 安全性
*`SecurityContextHolder`获取我们希望使用的`SecurityContext`,并初始化`DelegatingSecurityContextRunnable`
* 使用`DelegatingSecurityContextRunnable`创建线程
* 启动我们创建的线程
由于从`SecurityContextHolder`中使用`SecurityContext`创建`DelegatingSecurityContextRunnable`是很常见的,因此它有一个快捷构造函数。以下代码与上述代码相同:
爪哇
```
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable);
new Thread(wrappedRunnable).start();
```
Kotlin
```
val originalRunnable = Runnable {
// invoke secured service
}
val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable)
Thread(wrappedRunnable).start()
```
我们拥有的代码使用起来很简单,但仍然需要了解我们正在使用 Spring 安全性。在下一节中,我们将研究如何利用`委派安全环境专家`来隐藏我们正在使用 Spring 安全性的事实。
## DelegatingSecurityContextExecutor
在上一节中,我们发现使用`DelegatingSecurityContextRunnable`很容易,但并不理想,因为我们必须意识到 Spring 安全性才能使用它。让我们来看看`DelegatingSecurityContextExecutor`如何保护我们的代码不受我们正在使用 Spring 安全性的任何知识的影响。
`DelegatingSecurityContextExecutor`的设计与`DelegatingSecurityContextRunnable`的设计非常相似,只是它接受一个委托`Executor`而不是一个委托`Runnable`。你可以在下面看到一个如何使用它的示例:
爪哇
```
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new UsernamePasswordAuthenticationToken("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"));
context.setAuthentication(authentication);
SimpleAsyncTaskExecutor delegateExecutor =
new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor, context);
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
executor.execute(originalRunnable);
```
Kotlin
```
val context: SecurityContext = SecurityContextHolder.createEmptyContext()
val authentication: Authentication =
UsernamePasswordAuthenticationToken("user", "doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"))
context.authentication = authentication
val delegateExecutor = SimpleAsyncTaskExecutor()
val executor = DelegatingSecurityContextExecutor(delegateExecutor, context)
val originalRunnable = Runnable {
// invoke secured service
}
executor.execute(originalRunnable)
```
代码执行以下步骤:
* 创建用于我们的`DelegatingSecurityContextExecutor``SecurityContext`。注意,在这个示例中,我们只需手工创建`SecurityContext`。然而,无论我们在哪里或如何获得`SecurityContext`都不重要(也就是说,如果我们愿意,我们可以从`SecurityContextHolder`获得它)。
* 创建一个 DelegateExecutor,它负责执行提交的`Runnable`s
* 最后,我们创建一个`DelegatingSecurityContextExecutor`,它负责用`DelegatingSecurityContextRunnable`包装传递到 Execute 方法中的任何 runnable。然后,它将包装好的 Runnable 传递给 DelegateExecutor。在此实例中,对于提交到我们的`DelegatingSecurityContextExecutor`的每个 runnable,将使用相同的`SecurityContext`。如果我们运行的是需要由具有提升权限的用户运行的后台任务,那么这很好。
* 此时,你可能会问自己:“这是如何屏蔽我的代码中的任何安全知识的?”我们不需要在自己的代码中创建`SecurityContext``DelegatingSecurityContextExecutor`,而是可以插入一个已经初始化的`DelegatingSecurityContextExecutor`实例。
爪哇
```
@Autowired
private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor
public void submitRunnable() {
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
executor.execute(originalRunnable);
}
```
Kotlin
```
@Autowired
lateinit var executor: Executor // becomes an instance of our DelegatingSecurityContextExecutor
fun submitRunnable() {
val originalRunnable = Runnable {
// invoke secured service
}
executor.execute(originalRunnable)
}
```
现在我们的代码不知道`SecurityContext`正在传播到`Thread`,然后运行`originalRunnable`,然后清除`SecurityContextHolder`。在本例中,使用相同的用户运行每个线程。如果我们希望在调用`executor.execute(Runnable)`时使用来自`SecurityContextHolder`的用户(即当前登录的用户)来处理`originalRunnable`,该怎么办?这可以通过从我们的`DelegatingSecurityContextExecutor`构造函数中删除`SecurityContext`参数来完成。例如:
爪哇
```
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
```
Kotlin
```
val delegateExecutor = SimpleAsyncTaskExecutor()
val executor = DelegatingSecurityContextExecutor(delegateExecutor)
```
现在,每当执行`executor.execute(Runnable)`时,`SecurityContext`首先由`SecurityContextHolder`获得,然后`SecurityContext`用于创建我们的`DelegatingSecurityContextRunnable`。这意味着我们运行`Runnable`的用户与调用`executor.execute(Runnable)`代码的用户相同。
## Spring 安全并发类
有关 Java Concurrent API 和 Spring 任务抽象的附加集成,请参考 Javadoc。一旦你理解了前面的代码,它们就非常不言自明了。
* `DelegatingSecurityContextCallable`
* `DelegatingSecurityContextExecutor`
* `DelegatingSecurityContextExecutorService`
* `DelegatingSecurityContextRunnable`
* `DelegatingSecurityContextScheduledExecutorService`
* `DelegatingSecurityContextSchedulingTaskExecutor`
* `DelegatingSecurityContextAsyncTaskExecutor`
* `DelegatingSecurityContextTaskExecutor`
* `DelegatingSecurityContextTaskScheduler`
\ No newline at end of file
# Spring 安全加密模块
## 导言
Spring 安全加密模块提供对对称加密、密钥生成和密码编码的支持。该代码作为核心模块的一部分进行分发,但不依赖于任何其他 Spring 安全性(或 Spring)代码。
## 加密者
Encryptors 类提供了用于构造对称加密器的工厂方法。使用这个类,你可以创建 ByteenCryptors 来加密原始字节[]形式的数据。你还可以构造文本加密器来加密文本字符串。加密器是线程安全的。
### BytesenCryptor
使用`Encryptors.stronger`工厂方法来构造一个 ByteSenCryptor:
例 1。BytesenCryptor
爪哇
```
Encryptors.stronger("password", "salt");
```
Kotlin
```
Encryptors.stronger("password", "salt")
```
“更强”的加密方法使用 256 位 AES 加密和伽罗瓦计数器模式创建一个加密器。它使用 PKCS#5 的 PBKDF2(基于密码的密钥派生函数 #2)来派生密钥。这个方法需要 爪哇6。用于生成秘密密钥的密码应保存在安全的地方,不得共享。SALT 用于防止在你的加密数据遭到破坏时对密钥发起字典攻击。还应用了 16 字节的随机初始化向量,因此每个加密消息都是唯一的。
所提供的 SALT 应该是十六进制编码的字符串形式,是随机的,并且至少有 8 个字节的长度。可以使用键盘生成器生成这样的 salt:
例 2。生成密钥
爪哇
```
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
```
Kotlin
```
val salt = KeyGenerators.string().generateKey() // generates a random 8-byte salt that is then hex-encoded
```
用户也可以使用`standard`加密方法,这是在密码块链接模式下的 256 位 AES。此模式不是[已认证](https://en.wikipedia.org/wiki/Authenticated_encryption),并且不提供有关数据真实性的任何保证。对于更安全的选择,用户应该更喜欢`Encryptors.stronger`
### TextEncryptor
使用 Encryptors.text Factory 方法构建一个标准的 TextEncryptor:
例 3。TextEncryptor
爪哇
```
Encryptors.text("password", "salt");
```
Kotlin
```
Encryptors.text("password", "salt")
```
TextEncryptor 使用标准的 BytesEncryptor 加密文本数据。加密的结果以十六进制编码字符串的形式返回,以便于存储在文件系统或数据库中。
使用 Encryptors.queryableText Factory 方法构造一个“可查询的”文本加密程序:
例 4。可查询文本加密器
爪哇
```
Encryptors.queryableText("password", "salt");
```
Kotlin
```
Encryptors.queryableText("password", "salt")
```
可查询文本加密器和标准文本加密器之间的区别与初始化向量处理有关。在可查询的 TextEncryptor#Encrypt 操作中使用的 IV 是共享的或常量的,并且不是随机生成的。这意味着多次加密的相同文本将始终产生相同的加密结果。这不太安全,但对于需要查询的加密数据来说是必要的。可查询加密文本的一个例子是 OAuth Apikey。
## 关键生成器
keygenerators 类为构造不同类型的密钥生成器提供了许多方便的工厂方法。使用这个类,你可以创建一个 byteskeygenerator 来生成 byte[]键。你还可以构造一个 串键生成器 来生成 String 键。键盘生成器是线程安全的。
### Byteskeygenerator
使用 keygenerators.secureRandom Factory 方法生成由 secureRandom 实例支持的 byteskeygenerator:
例 5。Byteskeygenerator
爪哇
```
BytesKeyGenerator generator = KeyGenerators.secureRandom();
byte[] key = generator.generateKey();
```
Kotlin
```
val generator = KeyGenerators.secureRandom()
val key = generator.generateKey()
```
默认的密钥长度是 8 个字节。还有一个 keygenerators.secureRandom 变体,它提供对密钥长度的控制:
例 6。keygenerators.secureRandom
爪哇
```
KeyGenerators.secureRandom(16);
```
Kotlin
```
KeyGenerators.secureRandom(16)
```
使用 keygenerators.shared Factory 方法构造一个 byteskeygenerator,它总是在每次调用时返回相同的键:
例 7。keygenerators.shared
爪哇
```
KeyGenerators.shared(16);
```
Kotlin
```
KeyGenerators.shared(16)
```
### StringKeyGenerator
使用 keygenerators.string Factory 方法构造一个 8 字节的安全随机密钥生成器,将每个密钥作为字符串进行十六进制编码:
例 8。串键生成器
爪哇
```
KeyGenerators.string();
```
Kotlin
```
KeyGenerators.string()
```
## 密码编码
Spring-security-crypto 模块的密码包提供了对密码编码的支持。`PasswordEncoder`是中心服务接口,具有以下签名:
```
public interface PasswordEncoder {
String encode(String rawPassword);
boolean matches(String rawPassword, String encodedPassword);
}
```
如果编码后的 RAWPassword 等于 EncodedPassword,则 Matches 方法返回 true。该方法旨在支持基于密码的身份验证方案。
`BCryptPasswordEncoder`实现使用广泛支持的“bcrypt”算法来散列密码。Bcrypt 使用一个随机的 16 字节的盐值,并且是一个故意缓慢的算法,以阻止密码破解器。它所做的工作量可以使用“强度”参数进行调整,该参数的取值范围为 4 到 31。值越高,计算散列所需做的工作就越多。默认值是 10。你可以在部署的系统中更改该值,而不会影响现有的密码,因为该值也存储在编码的散列中。
例 9。bcryptpasswordencoder
爪哇
```
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
```
Kotlin
```
// Create an encoder with strength 16
val encoder = BCryptPasswordEncoder(16)
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
```
`Pbkdf2PasswordEncoder`实现使用 PBKDF2 算法来散列密码。为了击败密码破解,PBKDF2 是一个故意缓慢的算法,应该调整为大约 0.5 秒来验证你的系统上的密码。
例 10。PBKDF2PASSWORDENCODER
Java
```
// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
```
Kotlin
```
// Create an encoder with all the defaults
val encoder = Pbkdf2PasswordEncoder()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
```
\ No newline at end of file
# Spring 数据集成
Spring 安全性提供了 Spring 数据集成,允许在查询中引用当前用户。将用户包括在查询中以支持分页结果不仅是有用的,而且是必要的,因为在此之后对结果进行过滤将不会扩展。
## Spring 数据和 Spring 安全配置
要使用此支持,请添加`org.springframework.security:spring-security-data`依赖项,并提供类型`SecurityEvaluationContextExtension`的 Bean。在 爪哇 配置中,这看起来像是:
爪哇
```
@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
```
Kotlin
```
@Bean
fun securityEvaluationContextExtension(): SecurityEvaluationContextExtension {
return SecurityEvaluationContextExtension()
}
```
在 XML 配置中,这看起来像是:
```
<bean class="org.springframework.security.data.repository.query.SecurityEvaluationContextExtension"/>
```
## @query 中的安全表达式
现在 Spring 可以在查询中使用安全性。例如:
Java
```
@Repository
public interface MessageRepository extends PagingAndSortingRepository<Message,Long> {
@Query("select m from Message m where m.to.id = ?#{ principal?.id }")
Page<Message> findInbox(Pageable pageable);
}
```
Kotlin
```
@Repository
interface MessageRepository : PagingAndSortingRepository<Message?, Long?> {
@Query("select m from Message m where m.to.id = ?#{ principal?.id }")
fun findInbox(pageable: Pageable?): Page<Message?>?
}
```
这将检查`Authentication.getPrincipal().getId()`是否等于`Message`的接收者。请注意,本例假定你已将主体自定义为具有 ID 属性的对象。通过公开`SecurityEvaluationContextExtension` Bean,查询中的所有[常见的安全表达式](../../servlet/authorization/expression-based.html#common-expressions)都是可用的。
\ No newline at end of file
# Jackson 支助
Spring 安全性为持久化 Spring 与安全性相关的类提供了 Jackson 支持。这可以在使用分布式会话(即会话复制、 Spring 会话等)时提高序列化 Spring 安全相关类的性能。
要使用它,将`SecurityJackson2Modules.getModules(ClassLoader)`注册为`ObjectMapper`[Jackson-数据库](https://github.com/FasterXML/jackson-databind)):
爪哇
```
ObjectMapper mapper = new ObjectMapper();
ClassLoader loader = getClass().getClassLoader();
List<Module> modules = SecurityJackson2Modules.getModules(loader);
mapper.registerModules(modules);
// ... use ObjectMapper as normally ...
SecurityContext context = new SecurityContextImpl();
// ...
String json = mapper.writeValueAsString(context);
```
Kotlin
```
val mapper = ObjectMapper()
val loader = javaClass.classLoader
val modules: MutableList<Module> = SecurityJackson2Modules.getModules(loader)
mapper.registerModules(modules)
// ... use ObjectMapper as normally ...
val context: SecurityContext = SecurityContextImpl()
// ...
val json: String = mapper.writeValueAsString(context)
```
| |下面的 Spring 安全模块提供了 Jackson 支持:<br/><br/>* Spring-security-core(`CoreJackson2Module`)<br/><br/>* Spring-security-web(`WebJackson2Module``WebServletJackson2Module``WebServerJackson2Module`<br/><12"gt=">>(<10"/>r=“r=”10“>)<gt="/>r=“20”/>(<<<gt="r="r="11">>|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
\ No newline at end of file
# 本地化
如果你需要支持其他语言环境,那么你需要了解的所有内容都包含在本节中。
所有异常消息都可以本地化,包括与身份验证失败和访问被拒绝(授权失败)相关的消息。针对开发人员或系统部署人员的异常和日志消息(包括不正确的属性、违反接口契约、使用不正确的构造函数、启动时间验证、调试级别的日志记录)没有本地化,而是在 Spring Security 的代码中用英文进行了硬编码。
`spring-security-core-xx.jar`中,你将发现一个`org.springframework.security`包,该包依次包含一个`messages.properties`文件,以及一些常见语言的本地化版本。这应该由你的`ApplicationContext`来引用,因为 Spring 安全类实现了 Spring 的`MessageSourceAware`接口,并且期望消息解析程序是在应用程序上下文启动时注入的依赖项。通常,你所需要做的就是在应用程序上下文中注册一个 Bean 来引用消息。下面是一个例子:
```
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:org/springframework/security/messages"/>
</bean>
```
`messages.properties`是根据标准资源包命名的,表示 Spring 安全消息支持的默认语言。这个默认文件是英文的。
如果你希望自定义`messages.properties`文件,或者支持其他语言,那么你应该复制该文件,对其进行相应的重命名,并将其注册到上述 Bean 定义中。在这个文件中没有大量的消息键,因此本地化不应该被认为是一项主要的举措。如果你确实执行了此文件的本地化,请考虑通过记录 JIRA 任务并附加适当命名的本地化版本`messages.properties`来与社区共享你的工作。
Spring 安全性依赖于 Spring 的本地化支持,以便实际查找适当的消息。为了实现这一点,你必须确保来自传入请求的区域设置存储在 Spring 的`org.springframework.context.i18n.LocaleContextHolder`中。 Spring MVC 的`DispatcherServlet`自动为你的应用程序执行此操作,但是由于 Spring Security 的过滤器是在此之前调用的,因此在调用过滤器之前,需要设置`LocaleContextHolder`以包含正确的`Locale`。你可以自己在过滤器中执行此操作(它必须在`web.xml`中的 Spring 安全过滤器之前),也可以使用 Spring 的`RequestContextFilter`。请参阅 Spring 框架文档,以获取关于使用 Spring 本地化的更多详细信息。
将“联系人”示例应用程序设置为使用本地化消息。
\ No newline at end of file
# 整合
Spring 安全性提供了与众多框架和 API 的集成。在这一节中,我们将讨论不特定于 Servlet 或反应性环境的通用集成。要查看特定的集成,请参阅[Servlet](../../servlet/integrations/index.html)[Reactive](../../servlet/integrations/index.html)集成部分。
## 章节摘要
* [密码学](cryptography.html)
* [Spring Data](data.html)
* [Java 的并发 API](concurrency.html)
* [Jackson](jackson.html)
* [本地化](localization.html)
\ No newline at end of file
# 特征
Spring 安全性为[认证](authentication/index.html)[授权](authorization/index.html)[共同的功绩](exploits/index.html#exploits)提供了全面的支持。它还提供了与其他库的集成,以简化其使用。
## 章节摘要
* [认证](authentication/index.html)
* [保护免受剥削](exploits/index.html)
* [整合](integrations/index.html)
[Getting Spring Security](../getting-spring-security.html)[认证](authentication/index.html)
\ No newline at end of file
# 获得 Spring 安全性
本节讨论了有关获得 Spring 安全性二进制文件所需了解的所有信息。有关如何获得源代码,请参见[源代码](community.html#community-source)
## 发行版本编号
Spring 安全版本的格式为 major.minor.patch,例如:
* 主要的版本可能包含断开的更改。通常,这样做是为了提供与现代安全实践相匹配的改进的安全性。
* 小版本包含增强功能,但被视为被动更新。
* 补丁级别应该是完全兼容的,向前和向后,可能的例外情况是修复错误的更改。
## Maven 的用法
与大多数开源项目一样, Spring Security 将其依赖关系部署为 Maven 工件。本节中的主题提供了有关在使用 Maven 时如何使用 Spring 安全性的详细信息。
### Spring 用 Maven 引导
Spring Boot 提供了一个`spring-boot-starter-security`启动器,该启动器将 Spring 与安全相关的依赖关系聚合在一起。使用启动器的最简单和首选的方法是通过使用 IDE 集成([Eclipse](https://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html)[IntelliJ](https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2)[NetBeans](https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour))或通过[https://start.spring.io](https://start.spring.io)使用[Spring Initializr](https://docs.spring.io/initializr/docs/current/reference/html/)
或者,你也可以手动添加启动器,如下例所示:
例 1。 POM.xml
```
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
```
由于 Spring 启动提供了一个 Maven BOM 来管理依赖版本,因此你不需要指定版本。如果你希望重写 Spring 安全版本,那么可以通过提供 Maven 属性来实现,如下例所示:
例 2。 POM.xml
```
<properties>
<!-- ... -->
<spring-security.version>5.6.2</spring-security.version>
</properties>
```
由于 Spring 安全性仅在主要版本中进行破坏更改,因此使用具有 Spring 启动的 Spring 安全性的较新版本是安全的。然而,有时你可能还需要更新 Spring Framework 的版本。你可以通过添加一个 Maven 属性来做到这一点,如下例所示:
例 3。 POM.xml
```
<properties>
<!-- ... -->
<spring.version>5.3.16</spring.version>
</properties>
```
如果使用额外的功能(例如 LDAP、OpenID 和其他功能),还需要包括适当的[项目模块和依赖项](modules.html#modules)
### Maven 没有靴子
当使用 Spring 安全性而不启动 Spring 时,首选的方法是使用 Spring 安全性的 BOM,以确保在整个项目中使用 Spring 安全性的一致版本。下面的示例展示了如何做到这一点:
例 4。 POM.xml
```
<dependencyManagement>
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>{spring-security-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
```
一组最小的 Spring 安全性 Maven 依赖关系通常如下所示:
例 5。 POM.xml
```
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
</dependencies>
```
如果你使用了额外的功能(例如 LDAP、OpenID 和其他功能),则还需要包括适当的[项目模块和依赖项](modules.html#modules)
Spring 安全性是根据 Spring Framework5.3.16 构建的,但通常应该与 Spring Framework5.x 的任何较新版本一起工作。 Spring 安全性的传递依赖关系解决了 Spring Framework5.3.16,这可能会导致奇怪的 Classpath 问题,许多用户可能会对此感到不满。解决此问题的最简单方法是使用`spring-framework-bom``<dependencyManagement>`部分中的`pom.xml`,如下例所示:
例 6。 POM.xml
```
<dependencyManagement>
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.16</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
```
前面的示例确保 Spring 安全性的所有传递依赖使用 Spring 5.3.16 模块。
| |这种方法使用 Maven 的“物料清单”概念,并且仅在 Maven 2.0.9+ 中可用。<br/>有关如何解决依赖关系的更多详细信息,请参见[Maven’s Introduction to the Dependency Mechanism documentation](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### Maven 存储库
所有 GA 版本(即以.release 结尾的版本)都部署到 Maven Central,因此不需要在 POM 中声明额外的 Maven 存储库。
如果使用快照版本,则需要确保定义了 Spring 快照存储库,如下例所示:
例 7。 POM.xml
```
<repositories>
<!-- ... possibly other repository elements ... -->
<repository>
<id>spring-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/snapshot</url>
</repository>
</repositories>
```
如果使用里程碑或发布候选版本,则需要确保定义了 Spring 里程碑存储库,如下例所示:
例 8。 POM.xml
```
<repositories>
<!-- ... possibly other repository elements ... -->
<repository>
<id>spring-milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
```
## Gradle
与大多数开源项目一样, Spring Security 将其依赖关系部署为 Maven 工件,这允许一流的 Gradle 支持。以下主题提供了在使用 Gradle 时如何使用 Spring 安全性的详细信息。
### Spring 用 Gradle 引导
Spring 引导提供了一个`spring-boot-starter-security`启动器,该启动器将 Spring 安全相关的依赖关系聚合在一起。使用启动器的最简单和首选方法是通过使用 IDE 集成([Eclipse](https://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html)[IntelliJ](https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2)[NetBeans](https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour))或通过[https://start.spring.io](https://start.spring.io)使用[Spring Initializr](https://docs.spring.io/initializr/docs/current/reference/html/)
或者,你也可以手动添加启动器,如下例所示:
示例 9.build. Gradle
```
dependencies {
compile "org.springframework.boot:spring-boot-starter-security"
}
```
由于 Spring 启动提供了一个 Maven BOM 来管理依赖版本,所以不需要指定版本。如果你希望重写 Spring 安全版本,那么可以通过提供 Gradle 属性来实现,如下例所示:
示例 10.构建。 Gradle
```
ext['spring-security.version']='5.6.2'
```
由于 Spring 安全性仅在主要版本中进行破坏更改,因此使用具有 Spring 启动的 Spring 安全性的较新版本是安全的。然而,有时你可能还需要更新 Spring 框架的版本。你可以通过添加一个 Gradle 属性来做到这一点,如下例所示:
示例 11.build. Gradle
```
ext['spring.version']='5.3.16'
```
如果你使用了额外的功能(例如 LDAP、OpenID 和其他功能),则还需要包括适当的[项目模块和依赖项](modules.html#modules)
### Gradle 没有靴子
当使用 Spring 安全性而不启动 Spring 时,首选的方法是使用 Spring 安全性的 BOM,以确保在整个项目中使用 Spring 安全性的一致版本。你可以通过使用[依赖管理插件 Name](https://github.com/spring-gradle-plugins/dependency-management-plugin)来实现这一点,如下例所示:
例 12.build. Gradle
```
plugins {
id "io.spring.dependency-management" version "1.0.6.RELEASE"
}
dependencyManagement {
imports {
mavenBom 'org.springframework.security:spring-security-bom:5.6.2'
}
}
```
Spring 安全性 Maven 依赖性的最小集合通常如下所示:
例 13.build. Gradle
```
dependencies {
compile "org.springframework.security:spring-security-web"
compile "org.springframework.security:spring-security-config"
}
```
如果你使用了额外的功能(例如 LDAP、OpenID 和其他功能),则还需要包括适当的[项目模块和依赖项](modules.html#modules)
Spring 安全性是根据 Spring Framework5.3.16 构建的,但通常应该与 Spring Framework5.x 的任何较新版本一起工作。 Spring 安全性的传递依赖关系解决了 Spring Framework5.3.16,这可能会导致奇怪的 Classpath 问题,许多用户可能会对此感到不满。解决此问题的最简单方法是在`pom.xml``<dependencyManagement>`部分中使用`spring-framework-bom`。你可以通过使用[依赖管理插件 Name](https://github.com/spring-gradle-plugins/dependency-management-plugin)来实现这一点,如下例所示:
示例 14.build. Gradle
```
plugins {
id "io.spring.dependency-management" version "1.0.6.RELEASE"
}
dependencyManagement {
imports {
mavenBom 'org.springframework:spring-framework-bom:5.3.16'
}
}
```
前面的示例确保 Spring 安全性的所有传递依赖使用 Spring 5.3.16 模块。
### Gradle 存储库
所有 GA 版本(即以.release 结尾的版本)都部署到 Maven Central,因此对于 GA 版本,使用 MavenCentral()存储库就足够了。下面的示例展示了如何做到这一点:
例 15.build. Gradle
```
repositories {
mavenCentral()
}
```
如果使用快照版本,则需要确保定义了 Spring 快照存储库,如下例所示:
示例 16.build. Gradle
```
repositories {
maven { url 'https://repo.spring.io/snapshot' }
}
```
如果使用里程碑或发布候选版本,则需要确保定义了 Spring 里程碑存储库,如下例所示:
示例 17.build. Gradle
```
repositories {
maven { url 'https://repo.spring.io/milestone' }
}
```
\ No newline at end of file
# 项目模块和依赖项
即使你不使用 Maven,我们也建议你查阅`pom.xml`文件,以了解第三方的依赖关系和版本。另一个好主意是检查示例应用程序中包含的库。
本节提供了 Spring Security 中的模块的参考,以及它们在运行中的应用程序中起作用所需的附加依赖关系。我们不包括仅在构建或测试 Spring 安全性本身时使用的依赖关系。我们也不包括外部依赖项所要求的传递依赖项。
项目网站上列出了 Spring Required 的版本,因此在下面的 Spring 依赖项中省略了具体的版本。请注意,在 Spring 应用程序中的其他非安全功能可能仍然需要下面列出的一些“可选”依赖项。此外,如果在大多数应用程序中使用了被列为“可选”的依赖项,那么它们在项目的 Maven POM 文件中实际上可能不会被标记为可选的依赖项。它们是“可选的”,只是因为除非你使用指定的功能,否则你不需要它们。
当一个模块依赖于另一个 Spring 安全模块时,它所依赖的模块的非可选依赖也被认为是必需的,并且不会单独列出。
## 核心—`spring-security-core.jar`
该模块包含核心身份验证和访问控制类和接口、远程支持和基本供应 API。任何使用 Spring 安全性的应用程序都需要它。它支持独立的应用程序、远程客户机、方法(服务层)安全性和 JDBC 用户配置。它包含以下顶级包:
* `org.springframework.security.core`
* `org.springframework.security.access`
* `org.springframework.security.authentication`
* `org.springframework.security.provisioning`
| Dependency |Version|说明|
|-----------------|-------|---------------------------------------------------------------------------|
| ehcache | 1.6.2 |如果使用基于 EHCache 的用户缓存实现,则需要(可选)。|
| spring-aop | |方法安全性基于 Spring AOP|
| spring-beans | |Spring 配置所需|
|spring-expression| |基于表达式的方法安全性所需(可选)|
| spring-jdbc | |如果使用数据库存储用户数据(可选),则需要.|
| spring-tx | |如果使用数据库存储用户数据(可选),则需要.|
| aspectjrt |1.6.10 |如果使用 AspectJ 支持(可选),则是必需的。|
| jsr250-api | 1.0 |如果你使用的是 JSR-250 方法-安全注释(可选),则需要这样做。|
## remoting—`spring-security-remoting.jar`
该模块提供了与 Spring 远程的集成。你不需要这样做,除非你正在编写使用远程处理的远程客户机。主包是`org.springframework.security.remoting`
| Dependency |Version|说明|
|--------------------|-------|-----------------------------------------------------|
|spring-security-core| | |
| spring-web | |对于使用 HTTP 远程支持的客户端来说是必需的。|
## web—`spring-security-web.jar`
这个模块包含过滤器和相关的 Web 安全基础设施代码。它包含任何具有 Servlet API 依赖关系的内容。如果你需要 Spring 安全性 Web 身份验证服务和基于 URL 的访问控制,那么你就需要它。主包是`org.springframework.security.web`
| Dependency |Version|说明|
|--------------------|-------|-------------------------------------------------------------------------------|
|spring-security-core| | |
| spring-web | |Spring Web 支持类被广泛使用。|
| spring-jdbc | |基于 JDBC 的持久 Rememe-Me 令牌存储库所需(可选).|
| spring-tx | |Rememe-Me 持久令牌存储库实现所需(可选).|
## 配置—`spring-security-config.jar`
这个模块包含安全名称空间解析代码和 Java 配置代码。如果你使用 Spring Security XML 名称空间进行配置或 Spring Security 的 Java 配置支持,那么你就需要它。主包是`org.springframework.security.config`。这些类都不打算在应用程序中直接使用。
| Dependency |Version|说明|
|----------------------|-------|-----------------------------------------------------------------------------|
| spring-security-core | | |
| spring-security-web | |如果你正在使用任何与 Web 相关的名称空间配置(可选),则需要这样做。|
| spring-security-ldap | |如果你正在使用 LDAP 命名空间选项(可选),则是必需的。|
|spring-security-openid| |如果你使用的是 OpenID 身份验证(可选的),则需要这样做。|
| aspectjweaver |1.6.10 |如果使用 protect-pointcut 命名空间语法(可选),则需要.|
## LDAP—`spring-security-ldap.jar`
该模块提供 LDAP 身份验证和配置代码。如果你需要使用 LDAP 身份验证或管理 LDAP 用户条目,则需要使用它。顶级包是`org.springframework.security.ldap`
| Dependency |Version|说明|
|-----------------------------------------------------------------------------------------------------------------------------------|-------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| spring-security-core | | |
| spring-ldap-core | 1.3.0 |LDAP 支持基于 Spring LDAP。|
| spring-tx | |数据异常类是必需的。|
|apache-ds <sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>| 1.5.5 |如果你使用的是嵌入式 LDAP 服务器(可选),则需要这样做。|
| shared-ldap |0.9.15 |如果你使用的是嵌入式 LDAP 服务器(可选),则需要这样做。|
| ldapsdk | 4.1 |例如,如果你使用 OpenLDAP 的密码策略功能,则使用 Mozilla ldapsdk.<gtr="15"/>对 LDAP 密码策略控件进行解码。|
## OAuth2.0 核心—`spring-security-oauth2-core.jar`
`spring-security-oauth2-core.jar`包含支持 OAuth2.0 授权框架和 OpenID Connect Core1.0 的核心类和接口。它是使用 OAuth2.0 或 OpenID Connect Core1.0 的应用程序所必需的,例如客户机、资源服务器和授权服务器。顶级包是`org.springframework.security.oauth2.core`
## OAuth2.0 客户端—`spring-security-oauth2-client.jar`
`spring-security-oauth2-client.jar`包含 Spring Security 对 OAuth2.0 授权框架和 OpenID Connect Core1.0 的客户端支持。它是使用 OAuth2.0 登录或 OAuth 客户端支持的应用程序所必需的。顶级包是`org.springframework.security.oauth2.client`
## OAuth2.0Jose—`spring-security-oauth2-jose.jar`
`spring-security-oauth2-jose.jar`包含 Spring Security 对 Jose(JavaScript 对象签名和加密)框架的支持。Jose 框架旨在提供一种在各方之间安全地转移权利要求的方法。它是由一系列规范构建而成的:
* JSON Web Token
* JSON Web 签名
* JSON 网络加密
* JSON Web Key
它包含以下顶级包:
* `org.springframework.security.oauth2.jwt`
* `org.springframework.security.oauth2.jose`
## OAuth2.0 资源服务器—`spring-security-oauth2-resource-server.jar`
`spring-security-oauth2-resource-server.jar`包含 Spring Security 对 OAuth2.0 资源服务器的支持。它用于通过 OAuth2.0 承载令牌保护 API。顶级包是`org.springframework.security.oauth2.server.resource`
## ACL—`spring-security-acl.jar`
这个模块包含一个专门的域对象 ACL 实现。它用于将安全性应用于应用程序中的特定域对象实例。顶级包是`org.springframework.security.acls`
| Dependency |Version|说明|
|--------------------|-------|-------------------------------------------------------------------------------------------------------------------|
|spring-security-core| | |
| ehcache | 1.6.2 |如果使用了基于 EHCache 的 ACL 缓存实现,则需要(如果你使用自己的实现,则可以选择)。|
| spring-jdbc | |如果你使用的是默认的基于 JDBC 的 ACLService(如果你实现了自己的 ACLService,则是可选的),则是必需的。|
| spring-tx | |如果你使用的是默认的基于 JDBC 的 ACLService(如果你实现了自己的 ACLService,则是可选的),则是必需的。|
## 化学文摘社—`spring-security-cas.jar`
该模块包含 Spring Security 的 CAS 客户端集成。如果你想对 CAS 单点登录服务器使用 Spring 安全性 Web 身份验证,那么你应该使用它。顶级包是`org.springframework.security.cas`
| Dependency |Version|说明|
|--------------------|-------|--------------------------------------------------------------------------------|
|spring-security-core| | |
|spring-security-web | | |
| cas-client-core |3.1.12 |JA-SIG CAS 客户机.<br/>这是 Spring 安全集成的基础。|
| ehcache | 1.6.2 |如果你正在使用基于 EHCache 的票证缓存(可选),则需要这样做。|
## OpenID—`spring-security-openid.jar`
| |OpenID1.0 和 2.0 协议已被弃用,并鼓励用户迁移到 OpenID Connect,这得到了 Spring-Security-OAuth2 的支持。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------|
此模块包含对 OpenID Web 身份验证的支持。它用于针对外部 OpenID 服务器对用户进行身份验证。顶级包是`org.springframework.security.openid`。它需要 OpenID4Java。
| Dependency |Version|说明|
|--------------------|-------|------------------------------------------------------|
|spring-security-core| | |
|spring-security-web | | |
| openid4java-nodeps | 0.9.6 |Spring Security 的 OpenID 集成使用了 OpenID4Java。|
| httpclient | 4.1.1 |OpenID4Java-Nodeps 依赖于 HttpClient4。|
| guice | 2.0 |OpenID4Java-Nodeps 依赖于 Guice2。|
## 测试—`spring-security-test.jar`
该模块包含对具有 Spring 安全性的测试的支持。
## taglibs—`spring-secuity-taglibs.jar`
提供 Spring 安全性的 JSP 标记实现。
| Dependency |Version|说明|
|--------------------|-------|------------------------------------------------------------------------------------------------------------|
|spring-security-core| | |
|spring-security-web | | |
|spring-security-acl | |如果使用带有 ACLS 的`accesscontrollist`标记或`hasPermission()`表达式(可选),则需要这样做。|
| spring-expression | |如果你在标记访问约束中使用 SPEL 表达式,则需要这样做。|
---
[1](#_footnoteref_1)。需要的模块有`apacheds-core``apacheds-core-entry``apacheds-protocol-shared``apacheds-protocol-ldap``apacheds-server-jndi`
\ No newline at end of file
# Spring 安全
Spring 安全性是提供[认证](features/authentication/index.html)[授权](features/authorization/index.html)[防范常见攻击](features/exploits/index.html)的框架。由于对[imperative](servlet/index.html)[reactive](reactive/index.html)应用程序的安全都提供了第一类支持,因此它是保护基于 Spring 的应用程序的事实上的标准。
有关功能的完整列表,请参见引用的[Features](features/index.html)部分。
## 开始
如果你准备好开始保护应用程序,请参阅[servlet](servlet/getting-started.html)[reactive](reactive/getting-started.html)的入门部分。这些部分将引导你创建你的第一个安全应用程序。
如果你想了解 Spring 安全性是如何工作的,那么可以参考[建筑](servlet/architecture.html)部分。
如果你有任何问题,有一个很棒的[community](community.html)将很乐意帮助你!
\ No newline at end of file
# 先决条件
Spring 安全性要求 Java8 或更高的运行时环境。
Spring 由于安全性旨在以一种自包含的方式进行操作,因此你不需要在 Java 运行时环境中放置任何特殊的配置文件。特别是,你不需要配置特殊的 Java 身份验证和授权服务策略文件,也不需要将 Spring 安全性放入公共 Classpath 位置。
类似地,如果使用 EJB 容器或 Servlet 容器,则不需要在任何地方放置任何特殊的配置文件,也不需要在服务器类加载器中包含 Spring 安全性。所有必需的文件都包含在你的应用程序中。
这种设计提供了最大的部署时间灵活性,因为你可以将目标工件(无论是 JAR、WAR 还是 EAR)从一个系统复制到另一个系统,并且它可以立即工作。
\ No newline at end of file
# 注销
Spring 安全性默认情况下提供注销端点。登录后,你可以`GET /logout`查看默认的注销确认页,或者`POST /logout`启动注销。这将:
* 清除`ServerCsrfTokenRepository``ServerSecurityContextRepository`,并
* 重定向回登录页面
通常,你也希望注销时的会话无效。为了实现这一点,你可以将`WebSessionServerLogoutHandler`添加到你的注销配置中,如下所示:
```
@Bean
SecurityWebFilterChain http(ServerHttpSecurity http) throws Exception {
DelegatingServerLogoutHandler logoutHandler = new DelegatingServerLogoutHandler(
new WebSessionServerLogoutHandler(), new SecurityContextServerLogoutHandler()
);
http
.authorizeExchange((exchange) -> exchange.anyExchange().authenticated())
.logout((logout) -> logout.logoutHandler(logoutHandler));
return http.build();
}
```
\ No newline at end of file
# 反应式 X.509 认证
[Servlet X.509 authentication](../../servlet/authentication/x509.html#servlet-x509)类似,Active X509 身份验证过滤器允许从客户端提供的证书中提取身份验证令牌。
下面是一个反应式 X509 安全配置的示例:
爪哇
```
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.x509(withDefaults())
.authorizeExchange(exchanges -> exchanges
.anyExchange().permitAll()
);
return http.build();
}
```
Kotlin
```
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
x509 { }
authorizeExchange {
authorize(anyExchange, authenticated)
}
}
}
```
在上面的配置中,当`principalExtractor``authenticationManager`都不提供时,将使用默认值。默认的主体提取器是`SubjectDnX509PrincipalExtractor`,它从客户机提供的证书中提取 CN(通用名称)字段。默认的身份验证管理器是`ReactivePreAuthenticatedAuthenticationManager`,它执行用户帐户验证,检查具有`principalExtractor`提取的名称的用户帐户是否存在,并且该帐户没有被锁定、禁用或过期。
下一个示例演示了如何重写这些默认值。
爪哇
```
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
SubjectDnX509PrincipalExtractor principalExtractor =
new SubjectDnX509PrincipalExtractor();
principalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)");
ReactiveAuthenticationManager authenticationManager = authentication -> {
authentication.setAuthenticated("Trusted Org Unit".equals(authentication.getName()));
return Mono.just(authentication);
};
http
.x509(x509 -> x509
.principalExtractor(principalExtractor)
.authenticationManager(authenticationManager)
)
.authorizeExchange(exchanges -> exchanges
.anyExchange().authenticated()
);
return http.build();
}
```
Kotlin
```
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
val customPrincipalExtractor = SubjectDnX509PrincipalExtractor()
customPrincipalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)")
val customAuthenticationManager = ReactiveAuthenticationManager { authentication: Authentication ->
authentication.isAuthenticated = "Trusted Org Unit" == authentication.name
Mono.just(authentication)
}
return http {
x509 {
principalExtractor = customPrincipalExtractor
authenticationManager = customAuthenticationManager
}
authorizeExchange {
authorize(anyExchange, authenticated)
}
}
}
```
在此示例中,从客户端证书的 OU 字段中提取用户名,而不是从 CN 中提取用户名,并且根本不执行使用`ReactiveUserDetailsService`的帐户查找。相反,如果将提供的证书颁发给名为“可信的组织单元”的 OU,则将对请求进行身份验证。
有关配置 Netty 和`WebClient``curl`命令行工具以使用相互 TLS 并启用 X.509 身份验证的示例,请参见[https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509](https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509)
\ No newline at end of file
# 授权 ServerHttpRequest
Spring 安全性为授权传入的 HTTP 请求提供了支持。默认情况下, Spring Security 的授权将要求对所有请求进行身份验证。显式配置如下所示:
例 1。所有请求都需要经过身份验证的用户。
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.anyExchange().authenticated()
)
.httpBasic(withDefaults())
.formLogin(withDefaults());
return http.build();
}
```
Kotlin
```
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
formLogin { }
httpBasic { }
}
}
```
我们可以通过按优先级顺序添加更多规则来配置 Spring 安全性,使其具有不同的规则。
例 2。多个授权请求规则
爪哇
```
import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole;
// ...
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
// @formatter:off
http
// ...
.authorizeExchange((authorize) -> authorize (1)
.pathMatchers("/resources/**", "/signup", "/about").permitAll() (2)
.pathMatchers("/admin/**").hasRole("ADMIN") (3)
.pathMatchers("/db/**").access((authentication, context) -> (4)
hasRole("ADMIN").check(authentication, context)
.filter(decision -> !decision.isGranted())
.switchIfEmpty(hasRole("DBA").check(authentication, context))
)
.anyExchange().denyAll() (5)
);
// @formatter:on
return http.build();
}
```
Kotlin
```
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange { (1)
authorize(pathMatchers("/resources/**", "/signup", "/about"), permitAll) (2)
authorize("/admin/**", hasRole("ADMIN")) (3)
authorize("/db/**", { authentication, context -> (4)
hasRole("ADMIN").check(authentication, context)
.filter({ decision -> !decision.isGranted() })
.switchIfEmpty(hasRole("DBA").check(authentication, context))
})
authorize(anyExchange, denyAll) (5)
}
// ...
}
}
```
|**1**|指定了多个授权规则。<br/>每个规则都按照它们被声明的顺序被考虑。|
|-----|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们指定了任何用户都可以访问的多个 URL 模式。<br/>具体来说,如果 URL 以“/resources/”开头,等于“/signup”或等于“/about”,则任何用户都可以访问请求。|
|**3**|任何以“/admin/”开头的 URL 都将被限制为具有“role\_admin”权限的用户。<br/>你将注意到,由于我们正在调用`hasRole`方法,因此我们不需要指定“role\_”前缀。|
|**4**|任何以“/db/”开头的 URL 都需要用户同时具有“role\_admin”和“role\_DBA”。<br/>这表明了提供自定义`ReactiveAuthorizationManager`的灵活性,允许我们实现任意的授权逻辑。<br/>为了简单起见,示例使用 lambda 并将其委托给现有的`AuthorityReactiveAuthorizationManager.hasRole`实现。<br/>但是,在实际情况下,应用程序可能会在实现`ReactiveAuthorizationManager`的适当类中实现该逻辑。|
|**5**|任何尚未匹配的 URL 都将被拒绝访问。<br/>如果你不想意外地忘记更新授权规则,这是一个很好的策略。|
\ No newline at end of file
# EnableReactiveMethodSecurity
Spring 安全性支持使用[反应堆的背景](https://projectreactor.io/docs/core/release/reference/#context)的方法安全性,其设置使用`ReactiveSecurityContextHolder`。例如,这演示了如何检索当前登录的用户消息。
| |要使其工作,方法的返回类型必须是`org.reactivestreams.Publisher`(即`Mono`/`Flux`),或者函数必须是 Kotlin 协程函数。<br/>这是与反应器的`Context`积分所必需的。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
爪哇
```
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
Mono<String> messageByUsername = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getName)
.flatMap(this::findMessageByUsername)
// In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
.subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication));
StepVerifier.create(messageByUsername)
.expectNext("Hi user")
.verifyComplete();
```
Kotlin
```
val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
val messageByUsername: Mono<String> = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getName)
.flatMap(this::findMessageByUsername) // In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
.subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication))
StepVerifier.create(messageByUsername)
.expectNext("Hi user")
.verifyComplete()
```
`this::findMessageByUsername`定义为:
爪哇
```
Mono<String> findMessageByUsername(String username) {
return Mono.just("Hi " + username);
}
```
Kotlin
```
fun findMessageByUsername(username: String): Mono<String> {
return Mono.just("Hi $username")
}
```
下面是在反应性应用程序中使用方法安全性时的最小方法安全配置。
爪哇
```
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob")
.password("rob")
.roles("USER")
.build();
UserDetails admin = userBuilder.username("admin")
.password("admin")
.roles("USER","ADMIN")
.build();
return new MapReactiveUserDetailsService(rob, admin);
}
}
```
Kotlin
```
@EnableReactiveMethodSecurity
class SecurityConfig {
@Bean
fun userDetailsService(): MapReactiveUserDetailsService {
val userBuilder: User.UserBuilder = User.withDefaultPasswordEncoder()
val rob = userBuilder.username("rob")
.password("rob")
.roles("USER")
.build()
val admin = userBuilder.username("admin")
.password("admin")
.roles("USER", "ADMIN")
.build()
return MapReactiveUserDetailsService(rob, admin)
}
}
```
考虑以下类:
爪哇
```
@Component
public class HelloWorldMessageService {
@PreAuthorize("hasRole('ADMIN')")
public Mono<String> findMessage() {
return Mono.just("Hello World!");
}
}
```
Kotlin
```
@Component
class HelloWorldMessageService {
@PreAuthorize("hasRole('ADMIN')")
fun findMessage(): Mono<String> {
return Mono.just("Hello World!")
}
}
```
或者,使用 Kotlin 协程的下列类:
Kotlin
```
@Component
class HelloWorldMessageService {
@PreAuthorize("hasRole('ADMIN')")
suspend fun findMessage(): String {
delay(10)
return "Hello World!"
}
}
```
结合上面的配置,`@PreAuthorize("hasRole('ADMIN')")`将确保`findByMessage`仅由具有`ADMIN`角色的用户调用。需要注意的是,标准方法安全性中的任何表达式都可以用于`@EnableReactiveMethodSecurity`。但是,此时我们只支持返回类型为`Boolean``boolean`的表达式。这意味着表达式不能阻塞。
当与[WebFlux 安全性](../configuration/webflux.html#jc-webflux)集成时,反应器上下文由 Spring 安全性根据经过身份验证的用户自动建立。
爪哇
```
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
return http
// Demonstrate that method security works
// Best practice to use both for defense in depth
.authorizeExchange(exchanges -> exchanges
.anyExchange().permitAll()
)
.httpBasic(withDefaults())
.build();
}
@Bean
MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob")
.password("rob")
.roles("USER")
.build();
UserDetails admin = userBuilder.username("admin")
.password("admin")
.roles("USER","ADMIN")
.build();
return new MapReactiveUserDetailsService(rob, admin);
}
}
```
Kotlin
```
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class SecurityConfig {
@Bean
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, permitAll)
}
httpBasic { }
}
}
@Bean
fun userDetailsService(): MapReactiveUserDetailsService {
val userBuilder: User.UserBuilder = User.withDefaultPasswordEncoder()
val rob = userBuilder.username("rob")
.password("rob")
.roles("USER")
.build()
val admin = userBuilder.username("admin")
.password("admin")
.roles("USER", "ADMIN")
.build()
return MapReactiveUserDetailsService(rob, admin)
}
}
```
\ No newline at end of file
# WebFlux 安全性
Spring Security 的 WebFlux 支持依赖于`WebFilter`,并且对 Spring WebFlux 和 Spring WebFlux.FN 的工作原理相同。你可以找到一些示例应用程序来演示下面的代码:
* Hello WebFlux[HelloWebFlux ](https://github.com/spring-projects/spring-security-samples/tree/5.6.x/reactive/webflux/java/hello-security)
* FN[HelloWebFluxFN ](https://github.com/spring-projects/spring-security-samples/tree/5.6.x/reactive/webflux-fn/hello-security)
* Hello WebFlux 方法[HelloWebFlux 方法](https://github.com/spring-projects/spring-security-samples/tree/5.6.x/reactive/webflux/java/method)
## 最小 WebFlux 安全配置
你可以在下面找到最小的 WebFlux 安全配置:
例 1。最小 WebFlux 安全配置
爪哇
```
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
}
```
Kotlin
```
@EnableWebFluxSecurity
class HelloWebfluxSecurityConfig {
@Bean
fun userDetailsService(): ReactiveUserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build()
return MapReactiveUserDetailsService(userDetails)
}
}
```
该配置提供表单和 HTTP 基本身份验证,设置授权以要求经过身份验证的用户访问任何页面,设置默认的登录页面和默认的注销页面,设置与安全相关的 HTTP 标题,CSRF 保护,等等。
## 显式 WebFlux 安全配置
你可以在下面找到最小 WebFlux 安全配置的显式版本:
例 2。显式 WebFlux 安全配置
爪哇
```
@Configuration
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.anyExchange().authenticated()
)
.httpBasic(withDefaults())
.formLogin(withDefaults());
return http.build();
}
}
```
Kotlin
```
@Configuration
@EnableWebFluxSecurity
class HelloWebfluxSecurityConfig {
@Bean
fun userDetailsService(): ReactiveUserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build()
return MapReactiveUserDetailsService(userDetails)
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
formLogin { }
httpBasic { }
}
}
}
```
这个配置显式地设置了与我们的最小配置相同的所有内容。从这里,你可以轻松地对默认值进行更改。
通过在`config/src/test/`目录中搜索[enableWebfluxsecurity](https://github.com/ Spring-projects/ Spring-security/search?q=path%3aconfig%2fsrc%2ftest%2f+enableWebfluxsecurity),你可以在单元测试中找到更多显式配置的示例。
### 多链支撑
你可以通过`RequestMatcher`s 将多个`SecurityWebFilterChain`实例配置为单独的配置。
例如,可以为以`/api`开头的 URL 隔离配置,如下所示:
爪哇
```
@Configuration
@EnableWebFluxSecurity
static class MultiSecurityHttpConfig {
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
@Bean
SecurityWebFilterChain apiHttpSecurity(ServerHttpSecurity http) {
http
.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/**")) (2)
.authorizeExchange((exchanges) -> exchanges
.anyExchange().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerSpec::jwt); (3)
return http.build();
}
@Bean
SecurityWebFilterChain webHttpSecurity(ServerHttpSecurity http) { (4)
http
.authorizeExchange((exchanges) -> exchanges
.anyExchange().authenticated()
)
.httpBasic(withDefaults()); (5)
return http.build();
}
@Bean
ReactiveUserDetailsService userDetailsService() {
return new MapReactiveUserDetailsService(
PasswordEncodedUser.user(), PasswordEncodedUser.admin());
}
}
```
Kotlin
```
@Configuration
@EnableWebFluxSecurity
open class MultiSecurityHttpConfig {
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
@Bean
open fun apiHttpSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
securityMatcher(PathPatternParserServerWebExchangeMatcher("/api/**")) (2)
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2ResourceServer {
jwt { } (3)
}
}
}
@Bean
open fun webHttpSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { (4)
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
httpBasic { } (5)
}
}
@Bean
open fun userDetailsService(): ReactiveUserDetailsService {
return MapReactiveUserDetailsService(
PasswordEncodedUser.user(), PasswordEncodedUser.admin()
)
}
}
```
|**1**|将`SecurityWebFilterChain`配置为`@Order`,以指定安全应该首先考虑哪个`SecurityWebFilterChain` Spring|
|-----|------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|使用`PathPatternParserServerWebExchangeMatcher`声明此`SecurityWebFilterChain`将仅适用于以`/api/`开头的 URL 路径|
|**3**|指定将用于`/api/**`端点的身份验证机制|
|**4**|创建另一个优先级较低的`SecurityWebFilterChain`实例,以匹配所有其他 URL|
|**5**|指定将用于应用程序其余部分的身份验证机制|
Spring 安全性将为每个请求选择一个`SecurityWebFilterChain``@Bean`。它将按照`securityMatcher`定义的顺序匹配请求。
在这种情况下,这意味着如果 URL 路径以`/api`开始,那么 Spring Security 将使用`apiHttpSecurity`。如果 URL 不以`/api`开头,则 Spring Security 将默认为`webHttpSecurity`,其中隐含的`securityMatcher`与任何请求匹配。
\ No newline at end of file
# WebFlux 环境中的跨站点请求伪造
本节讨论 Spring Security 对 WebFlux 环境的[跨站点请求伪造](../../features/exploits/csrf.html#csrf)支持。
## 使用 Spring 安全 CSRF 保护
使用 Spring Security 的 CSRF 保护的步骤概述如下:
* [使用适当的 HTTP 动词](#webflux-csrf-idempotent)
* [配置 CSRF 保护](#webflux-csrf-configure)
* [包括 CSRF 令牌](#webflux-csrf-include)
### 使用适当的 HTTP 动词
防止 CSRF 攻击的第一步是确保你的网站使用正确的 HTTP 动词。这在[安全的方法必须是幂等的。](../../features/exploits/csrf.html#csrf-protection-idempotent)中有详细介绍。
### 配置 CSRF 保护
下一步是在应用程序中配置 Spring Security 的 CSRF 保护。 Spring 默认情况下,Security 的 CSRF 保护是启用的,但你可能需要定制配置。下面是一些常见的定制。
#### 自定义 CSRFTokenRepository
默认情况下, Spring Security 使用`WebSessionServerCsrfTokenRepository`将预期的 CSRF 令牌存储在`WebSession`中。在某些情况下,用户可能希望配置自定义`ServerCsrfTokenRepository`。例如,可能希望将 cookie 中的`CsrfToken`持久化到[支持基于 爪哇Script 的应用程序](#webflux-csrf-include-ajax-auto)
默认情况下,`CookieServerCsrfTokenRepository`将写到一个名为`XSRF-TOKEN`的 cookie,并从一个名为`X-XSRF-TOKEN`的头部或 HTTP 参数`_csrf`读取它。这些默认值来自[AngularJS](https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection)
你可以在 爪哇 配置中使用以下方法配置`CookieServerCsrfTokenRepository`:
例 1。在 cookie 中存储 CSRF 令牌
爪哇
```
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()))
return http.build();
}
```
Kotlin
```
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
csrf {
csrfTokenRepository = CookieServerCsrfTokenRepository.withHttpOnlyFalse()
}
}
}
```
| |示例显式设置`cookieHttpOnly=false`.<br/>这是允许 爪哇Script(即 Angularjs)读取它所必需的。<br/>如果不需要直接使用 爪哇Script 读取 cookie 的能力,建议省略`cookieHttpOnly=false`(通过使用`new CookieServerCsrfTokenRepository()`代替)以提高安全性。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 禁用 CSRF 保护
默认情况下启用了 CSRF 保护。但是,如果 CSRF 保护[对你的应用程序来说是有意义的](../../features/exploits/csrf.html#csrf-when),则禁用 CSRF 保护非常简单。
下面的 Java 配置将禁用 CSRF 保护。
例 2。禁用 CSRF 配置
Java
```
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.csrf(csrf -> csrf.disable()))
return http.build();
}
```
Kotlin
```
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
csrf {
disable()
}
}
}
```
### 包括 CSRF 令牌
为了使[同步器令牌模式](../../features/exploits/csrf.html#csrf-protection-stp)能够抵御 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF 令牌。这必须包含在请求的一部分(即表单参数、HTTP 头等)中,而该部分不是由浏览器自动包含在 HTTP 请求中的。
Spring Security 的[CSRFWebfilter ](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/server/csrf/CsrfWebFilter.html)[Mono\<CsrfToken\>](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfToken.html)公开为名为`ServerWebExchange`的属性。这意味着,任何视图技术都可以访问`Mono<CsrfToken>`以将预期的令牌公开为[form](#webflux-csrf-include-form-attr)[meta tag](#webflux-csrf-include-ajax-meta)
如果你的视图技术没有提供订阅`Mono<CsrfToken>`的简单方法,那么一个常见的模式是使用 Spring 的`@ControllerAdvice`直接公开`CsrfToken`。例如,以下代码将在 Spring Security 的[CsrfrequestDataValueProcessor ](#webflux-csrf-include-form-auto)使用的默认属性名(`_csrf`)上放置`CsrfToken`,以自动将 CSRF 令牌作为隐藏输入。
例 3。`CsrfToken`as`@ModelAttribute`
Java
```
@ControllerAdvice
public class SecurityControllerAdvice {
@ModelAttribute
Mono<CsrfToken> csrfToken(ServerWebExchange exchange) {
Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName());
return csrfToken.doOnSuccess(token -> exchange.getAttributes()
.put(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME, token));
}
}
```
Kotlin
```
@ControllerAdvice
class SecurityControllerAdvice {
@ModelAttribute
fun csrfToken(exchange: ServerWebExchange): Mono<CsrfToken> {
val csrfToken: Mono<CsrfToken>? = exchange.getAttribute(CsrfToken::class.java.name)
return csrfToken!!.doOnSuccess { token ->
exchange.attributes[CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME] = token
}
}
}
```
幸运的是,ThymeLeaf 提供了[整合](#webflux-csrf-include-form-auto),它的工作不需要任何额外的工作。
#### 表单 URL 编码
为了发布 HTML 表单,CSRF 令牌必须作为隐藏输入包含在表单中。例如,呈现的 HTML 可能看起来像:
例 4。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/reactive/result/view/CsrfRequestDataValueProcessor.html)提供与 Spring 的[RequestDataValueProcessor ](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/result/view/RequestDataValueProcessor.html)的集成。为了使`CsrfRequestDataValueProcessor`工作,必须订阅`Mono<CsrfToken>`,并且`CsrfToken`必须是与[作为属性公开](#webflux-csrf-include-subscribe)匹配的[默认 \_CSRF\_ATTR\_Name ](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/reactive/result/view/CsrfRequestDataValueProcessor.html#DEFAULT_CSRF_ATTR_NAME)
幸运的是,ThymeLeaf[提供支持](https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor)通过与`RequestDataValueProcessor`集成来为你处理所有的样板文件,以确保具有不安全的 HTTP 方法(即 POST)的窗体将自动包括实际的 CSRF 令牌。
##### CSRFToken 请求属性
如果用于在请求中包含实际 CSRF 令牌的[其他选择](#webflux-csrf-include)不起作用,则可以利用以下事实:`Mono<CsrfToken>`[is exposed](#webflux-csrf-include)作为`ServerWebExchange`属性,该属性名为`org.springframework.security.web.server.csrf.CsrfToken`
下面的 ThymeLeaf 示例假设你在一个名为`_csrf`的属性上[expose](#webflux-csrf-include-subscribe)
例 5。具有请求属性的表单中的 CSRF 令牌
```
<form th:action="@{/logout}"
method="post">
<input type="submit"
value="Log out" />
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}"/>
</form>
```
#### Ajax 和 JSON 请求
如果你正在使用 JSON,那么就不可能在 HTTP 参数中提交 CSRF 令牌。相反,你可以在 HTTP 头中提交令牌。
在下面的部分中,我们将讨论在基于 JavaScript 的应用程序中将 CSRF 令牌作为 HTTP 请求头包含在内的各种方法。
##### 自动包含
Spring 安全性可以很容易地[configured](#webflux-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 ](#webflux-csrf-include-form-auto)的另一种模式是在`meta`标记中包含 CSRF 标记。HTML 可能看起来是这样的:
例 6。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,可以通过以下方式完成此操作:
例 7。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);
});
});
```
下面的示例假设你在名为`_csrf`的属性上`CsrfToken`。使用 Thymeleaf 进行此操作的示例如下所示:
例 8。CSRF 元标记 JSP
```
<html>
<head>
<meta name="_csrf" th:content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
<!-- ... -->
</head>
<!-- ... -->
```
## CSRF 考虑因素
在实施针对 CSRF 攻击的保护时,有几个特殊的考虑因素需要考虑。本节讨论了与 WebFlux 环境相关的那些注意事项。有关更一般的讨论,请参见[CSRF 考虑因素](../../features/exploits/csrf.html#csrf-considerations)
### 登录
这是重要的[需要 CSRF 才能登录](../../features/exploits/csrf.html#csrf-considerations-login)请求,以防止伪造日志的企图。 Spring Security 的 WebFlux 支持提供了开箱即用的服务。
### 注销
重要的是[需要 CSRF 才能注销](../../features/exploits/csrf.html#csrf-considerations-logout)请求,以防止伪造注销尝试。默认情况下 Spring Security 的`LogoutWebFilter`只处理 HTTP POST 请求。这确保了注销需要 CSRF 令牌,并且恶意用户不能强制注销你的用户。
最简单的方法是使用表单注销。如果你真的想要一个链接,可以使用 JavaScript 让该链接执行一个 POST(例如,可能在一个隐藏的表单上)。对于禁用了 JavaScript 的浏览器,你可以选择让链接将用户带到将执行 POST 的注销确认页面。
如果你真的想使用 HTTP GET 与注销,你可以这样做,但请记住,这通常是不推荐的。例如,下面的 Java 配置将使用任何 HTTP 方法请求的 URL`/logout`执行注销:
例 9。用 HTTP GET 登出
Java
```
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.logout(logout -> logout.requiresLogout(new PathPatternParserServerWebExchangeMatcher("/logout")))
return http.build();
}
```
Kotlin
```
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
logout {
requiresLogout = PathPatternParserServerWebExchangeMatcher("/logout")
}
}
}
```
### CSRF 和会话暂停
默认情况下, Spring Security 将 CSRF 令牌存储在`WebSession`中。这可能导致会话过期的情况,这意味着没有预期的 CSRF 令牌来验证。
我们已经讨论了[一般解决方案](../../features/exploits/csrf.html#csrf-considerations-login)到会话的超时。本节讨论 CSRF 超时的细节,因为它与 WebFlux 支持有关。
将预期的 CSRF 令牌的存储更改为 cookie 中的存储是很简单的。有关详细信息,请参阅[自定义 CSRFTokenRepository ](#webflux-csrf-configure-custom-repository)部分。
###
我们有[已经讨论过了](../../features/exploits/csrf.html#csrf-considerations-multipart)如何保护多部分请求(文件上传)不受 CSRF 攻击导致[鸡和蛋](https://en.wikipedia.org/wiki/Chicken_or_the_egg)问题。本节讨论如何在 WebFlux 应用程序中实现将 CSRF 令牌放置在[body](#webflux-csrf-considerations-multipart-body)[url](#webflux-csrf-considerations-multipart-url)中。
| |关于使用具有 Spring 的多部分表单的更多信息可以在 Spring 引用的[多部分数据](https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web-reactive.html#webflux-multipart)部分中找到。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 将 CSRF 标记放入体内
我们有[已经讨论过了](../../features/exploits/csrf.html#csrf-considerations-multipart)在主体中放置 CSRF 标记的权衡。
在 WebFlux 应用程序中,可以使用以下配置对其进行配置:
例 10。启用从多部分/表单数据获取 CSRF 令牌
Java
```
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.csrf(csrf -> csrf.tokenFromMultipartDataEnabled(true))
return http.build();
}
```
Kotlin
```
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
csrf {
tokenFromMultipartDataEnabled = true
}
}
}
```
#### 在 URL 中包含 CSRF 令牌
我们有[已经讨论过了](../../features/exploits/csrf.html#csrf-considerations-multipart)在 URL 中放置 CSRF 标记的权衡。由于`CsrfToken`被公开为`ServerHttpRequest`[请求属性](#webflux-csrf-include),因此我们可以使用它来创建带有 CSRF 令牌的`action`。ThymeLeaf 的一个示例如下所示:
例 11。CSRF 令牌正在运行
```
<form method="post"
th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
enctype="multipart/form-data">
```
### HiddenHttpMethodFilter
我们已经[已经讨论过了](../../features/exploits/csrf.html#csrf-considerations-override-method)覆盖了 HTTP 方法。
在 Spring WebFlux 应用程序中,使用[HiddenHttpMethodFilter ](https://docs.spring.io/spring-framework/docs/5.2.x/javadoc-api/org/springframework/web/filter/reactive/HiddenHttpMethodFilter.html)重写 HTTP 方法。
\ No newline at end of file
# 安全 HTTP 响应标头
[安全 HTTP 响应标头](../../features/exploits/headers.html#headers)可以用来增加 Web 应用程序的安全性。本节专门讨论基于 WebFlux 的对安全 HTTP 响应头的支持。
## 默认安全标头
Spring 安全性提供了[默认的安全 HTTP 响应标头集](../../features/exploits/headers.html#headers-default)以提供安全的默认值。虽然这些标题中的每一个都被认为是最佳实践,但应该注意的是,并不是所有的客户机都使用这些标题,因此鼓励进行额外的测试。
你可以自定义特定的标题。例如,假设你希望使用默认值,但不希望为[X-帧-选项](../../servlet/exploits/headers.html#servlet-headers-frame-options)指定`SAMEORIGIN`
你可以通过以下配置轻松地实现这一点:
例 1。自定义默认安全标头
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers
.frameOptions(frameOptions -> frameOptions
.mode(Mode.SAMEORIGIN)
)
);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
frameOptions {
mode = Mode.SAMEORIGIN
}
}
}
}
```
如果不希望添加默认值,并且希望对应该使用的内容进行显式控制,则可以禁用默认值。下面举例说明:
例 2。禁用 HTTP 安全响应头
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers.disable());
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
disable()
}
}
}
```
## 缓存控制
Spring 默认情况下,安全性包括[缓存控制](../../features/exploits/headers.html#headers-cache-control)标头。
然而,如果你实际上想要缓存特定的响应,你的应用程序可以有选择地将它们添加到[ServerHtpResponse ](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/server/reactive/ServerHttpResponse.html)中,以覆盖由 Spring Security 设置的头。这对于确保 CSS、爪哇Script 和图像等内容被适当地缓存是有用的。
当使用 Spring WebFlux 时,这通常是在你的配置中完成的。关于如何做到这一点的详细信息可以在 Spring 参考文档的[静态资源](https://docs.spring.io/spring/docs/5.0.0.RELEASE/spring-framework-reference/web-reactive.html#webflux-config-static-resources)部分中找到。
如果有必要,还可以禁用 Spring Security 的缓存控制 HTTP 响应头。
例 3。已禁用缓存控制
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers
.cache(cache -> cache.disable())
);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
cache {
disable()
}
}
}
}
```
## 内容类型选项
Spring 默认情况下,安全性包括[内容类型](../../features/exploits/headers.html#headers-content-type-options)标头。但是,你可以通过以下方式禁用它:
例 4。禁用内容类型选项
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers
.contentTypeOptions(contentTypeOptions -> contentTypeOptions.disable())
);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
contentTypeOptions {
disable()
}
}
}
}
```
##
Spring 默认情况下,Security 提供[严格的运输安全](../../features/exploits/headers.html#headers-hsts)报头。但是,你可以显式地定制结果。例如,下面是一个显式提供 HSTS 的示例:
例 5。严格的运输安全
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers
.hsts(hsts -> hsts
.includeSubdomains(true)
.preload(true)
.maxAge(Duration.ofDays(365))
)
);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
hsts {
includeSubdomains = true
preload = true
maxAge = Duration.ofDays(365)
}
}
}
}
```
## X-帧-选项
默认情况下, Spring Security 使用[X-帧-选项](../../features/exploits/headers.html#headers-frame-options)禁用在 iFrame 中的呈现。
你可以使用以下方法自定义框架选项以使用相同的原点:
例 6。x-frame-options:SameOrigin
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers
.frameOptions(frameOptions -> frameOptions
.mode(SAMEORIGIN)
)
);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
frameOptions {
mode = SAMEORIGIN
}
}
}
}
```
## X-XSS-保护
默认情况下, Spring Security 指示浏览器使用 \\<headers-xss-protection,X-XSS-Protection header\>来阻止反射的 XSS 攻击。可以通过以下配置禁用`X-XSS-Protection`:
例 7。X-XSS-保护定制
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers
.xssProtection(xssProtection -> xssProtection.disable())
);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
xssProtection {
disable()
}
}
}
}
```
##
Spring 安全默认情况下不添加[内容安全策略](../../features/exploits/headers.html#headers-csp),因为合理的默认情况是不可能在没有应用程序上下文的情况下知道的。Web 应用程序作者必须声明安全策略,以强制执行和/或监视受保护资源。
例如,给出以下安全策略:
例 8。内容安全策略示例
```
Content-Security-Policy: script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/
```
你可以启用 CSP 报头,如下所示:
例 9。内容安全策略
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers
.contentSecurityPolicy(policy -> policy
.policyDirectives("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
)
);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
contentSecurityPolicy {
policyDirectives = "script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/"
}
}
}
}
```
要启用 CSP`report-only`头,请提供以下配置:
例 10。仅提供内容安全策略报告
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers
.contentSecurityPolicy(policy -> policy
.policyDirectives("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
.reportOnly()
)
);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
contentSecurityPolicy {
policyDirectives = "script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/"
reportOnly = true
}
}
}
}
```
## 推荐人政策
Spring 默认情况下,安全性不添加[推荐人政策](../../features/exploits/headers.html#headers-referrer)标头。你可以使用如下所示的配置来启用 Referrer 策略标头:
例 11。Referrer 策略配置
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers
.referrerPolicy(referrer -> referrer
.policy(ReferrerPolicy.SAME_ORIGIN)
)
);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
referrerPolicy {
policy = ReferrerPolicy.SAME_ORIGIN
}
}
}
}
```
## 特征策略
Spring 默认情况下,安全性不添加[特征策略](../../features/exploits/headers.html#headers-feature)头。以下`Feature-Policy`标题:
例 12。功能策略示例
```
Feature-Policy: geolocation 'self'
```
你可以启用功能策略标头,如下所示:
例 13。功能策略配置
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers
.featurePolicy("geolocation 'self'")
);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
featurePolicy("geolocation 'self'")
}
}
}
```
## 权限策略
Spring 默认情况下,安全性不添加[权限策略](../../features/exploits/headers.html#headers-permissions)标头。以下`Permissions-Policy`标题:
例 14。权限-策略示例
```
Permissions-Policy: geolocation=(self)
```
你可以启用权限策略标头,如下所示:
例 15。权限-策略配置
爪哇
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.headers(headers -> headers
.permissionsPolicy(permissions -> permissions
.policy("geolocation=(self)")
)
);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
headers {
permissionsPolicy {
policy = "geolocation=(self)"
}
}
}
}
```
## 清除站点数据
Spring 默认情况下,安全性不添加[清除站点数据](../../features/exploits/headers.html#headers-clear-site-data)标头。以下 Clear-Site-Data 报头:
例 16。清除站点数据示例
```
Clear-Site-Data: "cache", "cookies"
```
可以通过以下配置在注销时发送:
例 17。清除站点数据配置
Java
```
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
ServerLogoutHandler securityContext = new SecurityContextServerLogoutHandler();
ClearSiteDataServerHttpHeadersWriter writer = new ClearSiteDataServerHttpHeadersWriter(CACHE, COOKIES);
ServerLogoutHandler clearSiteData = new HeaderWriterServerLogoutHandler(writer);
DelegatingServerLogoutHandler logoutHandler = new DelegatingServerLogoutHandler(securityContext, clearSiteData);
http
// ...
.logout()
.logoutHandler(logoutHandler);
return http.build();
}
```
Kotlin
```
@Bean
fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
val securityContext: ServerLogoutHandler = SecurityContextServerLogoutHandler()
val writer = ClearSiteDataServerHttpHeadersWriter(CACHE, COOKIES)
val clearSiteData: ServerLogoutHandler = HeaderWriterServerLogoutHandler(writer)
val customLogoutHandler = DelegatingServerLogoutHandler(securityContext, clearSiteData)
return http {
// ...
logout {
logoutHandler = customLogoutHandler
}
}
}
```
\ No newline at end of file
此差异已折叠。
# 保护免受剥削
Spring 安全性提供了针对众多利用漏洞的保护。本节讨论 WebFlux 对以下内容的特定支持:
* [CSRF](csrf.html)
* [Headers](headers.html)
* [HTTP 请求](http.html)
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
# OAuth2.0 登录
OAuth2.0 登录功能提供了一个应用程序,该应用程序可以让用户使用他们在 OAuth2.0 提供商(例如 GitHub)或 OpenID Connect1.0 提供商(例如 Google)的现有帐户登录到该应用程序。OAuth2.0Login 实现了以下用例:“用 Google 登录”或“用 GitHub 登录”。
| |OAuth2.0 登录是通过使用**授权代码授予**实现的,如[OAuth2.0 授权框架](https://tools.ietf.org/html/rfc6749#section-4.1)[OpenID Connect Core1.0](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth)中所指定的。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
## 章节摘要
* [核心配置](core.html)
* [高级配置](advanced.html)
[OAuth2](../index.html)[核心配置](core.html)
# OAuth2.0 资源服务器
Spring 安全性支持使用两种形式的 OAuth2.0[不记名代币](https://tools.ietf.org/html/rfc6750.html)来保护端点:
* [JWT](https://tools.ietf.org/html/rfc7519)
* 不透明令牌
在应用程序已将其权限管理委托给[授权服务器](https://tools.ietf.org/html/rfc6749)(例如,OKTA 或 ping 标识)的情况下,这很方便。资源服务器可以参考此授权服务器来授权请求。
| |[**JWTs**](https://github.com/spring-projects/spring-security-samples/tree/5.6.x/reactive/webflux/java/oauth2/resource-server)的完整工作示例在[Spring Security repository](https://github.com/spring-projects/spring-security-samples/tree/5.6.x)中可用。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
[OAuth2 授权客户](../client/authorized-clients.html)[JWT](jwt.html)
# OAuth2WebFlux
Spring 安全性为反应性应用程序提供了 OAuth2 和 WebFlux 集成。
* [OAuth2 登录](login/index.html)-使用 OAuth2 或 OpenID Connect1.0 提供程序进行身份验证
* [OAuth2 客户端](client/index.html)-向 OAuth2 资源服务器发出请求
* [OAuth2 资源服务器](resource-server/index.html)-使用 OAuth2 保护 REST 端点
[EnableReactiveMethodSecurity ](../authorization/method.html)[OAuth2 登录](login/index.html)
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
# 反应性应用
反应性应用程序的工作原理与[Servlet Applications](../servlet/index.html#servlet-applications)非常不同。本节讨论 Spring 安全性如何与反应性应用程序一起工作,这些应用程序通常使用 Spring 的 WebFlux 编写。
[FAQ](../servlet/appendix/faq.html)[开始](getting-started.html)
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册