提交 7b758bce 编写于 作者: 茶陵後's avatar 茶陵後 👍

#18 spring security 基本格式审核调整

上级 1e421821
......@@ -483,105 +483,159 @@ module.exports = {
sidebarDepth: 2,
collapsable: false,
children: [
"/spring-security/overview.md",
"/spring-security/prerequisites.md",
"/spring-security/community.md",
"/spring-security/features-authentication-password-storage.md",
"/spring-security/whats-new.md",
"/spring-security/getting-spring-security.md",
"/spring-security/features.md",
"/spring-security/features-authentication.md",
"/spring-security/features-authentication-password-storage.md",
"/spring-security/features-exploits.md",
"/spring-security/features-exploits-csrf.md",
"/spring-security/features-exploits-headers.md",
"/spring-security/features-exploits-http.md",
"/spring-security/features-exploits.md",
"/spring-security/features-integrations-concurrency.md",
"/spring-security/features-integrations.md",
"/spring-security/features-integrations-cryptography.md",
"/spring-security/features-integrations-data.md",
"/spring-security/features-integrations-concurrency.md",
"/spring-security/features-integrations-jackson.md",
"/spring-security/features-integrations-localization.md",
"/spring-security/features-integrations.md",
"/spring-security/features.md",
"/spring-security/getting-spring-security.md",
"/spring-security/modules.md",
"/spring-security/overview.md",
"/spring-security/prerequisites.md",
"/spring-security/reactive-authentication-logout.md",
"/spring-security/reactive-authentication-x509.md",
"/spring-security/reactive-authorization-authorize-http-requests.md",
"/spring-security/reactive-authorization-method.md",
"/spring-security/reactive-configuration-webflux.md",
"/spring-security/reactive-exploits-csrf.md",
"/spring-security/reactive-exploits-headers.md",
"/spring-security/reactive-exploits-http.md",
"/spring-security/reactive-exploits.md",
"/spring-security/reactive-getting-started.md",
"/spring-security/reactive-integrations-cors.md",
"/spring-security/reactive-integrations-rsocket.md",
"/spring-security/reactive-oauth2-client-authorization-grants.md",
"/spring-security/reactive-oauth2-client-authorized-clients.md",
"/spring-security/reactive-oauth2-client-client-authentication.md",
"/spring-security/reactive-oauth2-client-core.md",
"/spring-security/reactive-oauth2-client.md",
"/spring-security/reactive-oauth2-login-advanced.md",
"/spring-security/reactive-oauth2-login-core.md",
"/spring-security/reactive-oauth2-login.md",
"/spring-security/reactive-oauth2-resource-server-bearer-tokens.md",
"/spring-security/reactive-oauth2-resource-server-jwt.md",
"/spring-security/reactive-oauth2-resource-server-multitenancy.md",
"/spring-security/reactive-oauth2-resource-server-opaque-token.md",
"/spring-security/reactive-oauth2-resource-server.md",
"/spring-security/reactive-oauth2.md",
"/spring-security/reactive-test-method.md",
"/spring-security/reactive-test-web-authentication.md",
"/spring-security/reactive-test-web-csrf.md",
"/spring-security/reactive-test-web-oauth2.md",
"/spring-security/reactive-test-web-setup.md",
"/spring-security/reactive-test-web.md",
"/spring-security/reactive-test.md",
"/spring-security/reactive.md",
"/spring-security/samples.md",
"/spring-security/servlet-appendix-database-schema.md",
"/spring-security/servlet-appendix-faq.md",
"/spring-security/servlet-appendix-namespace-authentication-manager.md",
"/spring-security/servlet-appendix-namespace-http.md",
"/spring-security/servlet-appendix-namespace-ldap.md",
"/spring-security/servlet-appendix-namespace-method-security.md",
"/spring-security/servlet-appendix-namespace-websocket.md",
"/spring-security/servlet-appendix-namespace.md",
"/spring-security/servlet-appendix.md",
"/spring-security/servlet.md",
"/spring-security/servlet-getting-started.md",
"/spring-security/servlet-architecture.md",
"/spring-security/servlet-authentication-anonymous.md",
"/spring-security/servlet-authentication.md",
"/spring-security/servlet-authentication-architecture.md",
"/spring-security/servlet-authentication-cas.md",
"/spring-security/servlet-authentication-events.md",
"/spring-security/servlet-authentication-jaas.md",
"/spring-security/servlet-authentication-logout.md",
"/spring-security/servlet-authentication-openid.md",
"/spring-security/servlet-authentication-passwords.md",
"/spring-security/servlet-authentication-passwords-input.md",
"/spring-security/servlet-authentication-passwords-form.md",
"/spring-security/servlet-authentication-passwords-basic.md",
"/spring-security/servlet-authentication-passwords-digest.md",
"/spring-security/servlet-authentication-passwords-form.md",
"/spring-security/servlet-authentication-passwords-input.md",
"/spring-security/servlet-authentication-passwords-storage-dao-authentication-provider.md",
"/spring-security/servlet-authentication-passwords-storage.md",
"/spring-security/servlet-authentication-passwords-storage-in-memory.md",
"/spring-security/servlet-authentication-passwords-storage-jdbc.md",
"/spring-security/servlet-authentication-passwords-storage-ldap.md",
"/spring-security/servlet-authentication-passwords-storage-password-encoder.md",
"/spring-security/servlet-authentication-passwords-storage-user-details-service.md",
"/spring-security/servlet-authentication-passwords-storage-user-details.md",
"/spring-security/servlet-authentication-passwords-storage.md",
"/spring-security/servlet-authentication-passwords.md",
"/spring-security/servlet-authentication-preauth.md",
"/spring-security/servlet-authentication-rememberme.md",
"/spring-security/servlet-authentication-runas.md",
"/spring-security/servlet-authentication-passwords-storage-user-details-service.md",
"/spring-security/servlet-authentication-passwords-storage-password-encoder.md",
"/spring-security/servlet-authentication-passwords-storage-dao-authentication-provider.md",
"/spring-security/servlet-authentication-passwords-storage-ldap.md",
"/spring-security/servlet-authentication-session-management.md",
"/spring-security/servlet-authentication-rememberme.md",
"/spring-security/servlet-authentication-openid.md",
"/spring-security/servlet-authentication-anonymous.md",
"/spring-security/servlet-authentication-preauth.md",
"/spring-security/servlet-authentication-jaas.md",
"/spring-security/servlet-authentication-cas.md",
"/spring-security/servlet-authentication-x509.md",
"/spring-security/servlet-authentication.md",
"/spring-security/servlet-authentication-runas.md",
"/spring-security/servlet-authentication-logout.md",
"/spring-security/servlet-authentication-events.md",
"/spring-security/servlet-authorization-.md",
"/spring-security/servlet-authorization-acls.md",
"/spring-security/servlet-authorization-architecture.md",
"/spring-security/servlet-authorization-authorize-http-requests.md",
"/spring-security/servlet-authorization-authorize-requests.md",
"/spring-security/servlet-authorization-expression-based.md",
"/spring-security/servlet-authorization-method-security.md",
"/spring-security/servlet-authorization-secure-objects.md",
"/spring-security/servlet-authorization-method-security.md",
"/spring-security/servlet-authorization-acls.md",
"/spring-security/servlet-oauth2-.md",
"/spring-security/servlet-oauth2-login.md",
"/spring-security/servlet-oauth2-login-core.md",
"/spring-security/servlet-oauth2-login-advanced.md",
"/spring-security/servlet-oauth2-client.md",
"/spring-security/servlet-oauth2-client-core.md",
"/spring-security/servlet-oauth2-client-authorization-grants.md",
"/spring-security/servlet-oauth2-client-client-authentication.md",
"/spring-security/servlet-oauth2-client-authorized-clients.md",
"/spring-security/servlet-oauth2-resource-server.md",
"/spring-security/servlet-oauth2-resource-server-jwt.md",
"/spring-security/servlet-oauth2-resource-server-opaque-token.md",
"/spring-security/servlet-oauth2-resource-server-multitenancy.md",
"/spring-security/servlet-oauth2-resource-server-bearer-tokens.md",
"/spring-security/servlet-saml2.md",
"/spring-security/servlet-saml2-login.md",
"/spring-security/servlet-saml2-login-overview.md",
"/spring-security/servlet-saml2-login-authentication-requests.md",
"/spring-security/servlet-saml2-login-authentication.md",
"/spring-security/servlet-saml2-logout.md",
"/spring-security/servlet-saml2-metadata.md",
"/spring-security/servlet-exploits.md",
"/spring-security/servlet-exploits-csrf.md",
"/spring-security/servlet-exploits-headers.md",
"/spring-security/servlet-exploits-http.md",
"/spring-security/servlet-exploits-firewall.md",
"/spring-security/servlet-integrations.md",
"/spring-security/servlet-integrations-concurrency.md",
"/spring-security/servlet-integrations-jackson.md",
"/spring-security/servlet-integrations-localization.md",
"/spring-security/servlet-integrations-servlet-api.md",
"/spring-security/servlet-integrations-data.md",
"/spring-security/servlet-integrations-mvc.md",
"/spring-security/servlet-integrations-websocket.md",
"/spring-security/servlet-integrations-cors.md",
"/spring-security/servlet-integrations-jsp-taglibs.md",
"/spring-security/servlet-configuration-java.md",
"/spring-security/servlet-configuration-kotlin.md"
"/spring-security/servlet-configuration-kotlin.md",
"/spring-security/servlet-configuration-xml-namespace.md",
"/spring-security/servlet-test.md",
"/spring-security/servlet-test-method.md",
"/spring-security/servlet-test-mockmvc.md",
"/spring-security/servlet-test-mockmvc-setup.md",
"/spring-security/servlet-test-mockmvc-request-post-processors.md",
"/spring-security/servlet-test-mockmvc-authentication.md",
"/spring-security/servlet-test-mockmvc-csrf.md",
"/spring-security/servlet-test-mockmvc-form-login.md",
"/spring-security/servlet-test-mockmvc-http-basic.md",
"/spring-security/servlet-test-mockmvc-oauth2.md",
"/spring-security/servlet-test-mockmvc-logout.md",
"/spring-security/servlet-test-mockmvc-request-builders.md",
"/spring-security/servlet-test-mockmvc-result-matchers.md",
"/spring-security/servlet-test-mockmvc-result-handlers.md",
"/spring-security/servlet-appendix.md",
"/spring-security/servlet-appendix-database-schema.md",
"/spring-security/servlet-appendix-namespace.md",
"/spring-security/servlet-appendix-namespace-authentication-manager.md",
"/spring-security/servlet-appendix-namespace-http.md",
"/spring-security/servlet-appendix-namespace-method-security.md",
"/spring-security/servlet-appendix-namespace-ldap.md",
"/spring-security/servlet-appendix-namespace-websocket.md",
"/spring-security/servlet-appendix-faq.md",
"/spring-security/reactive.md",
"/spring-security/reactive-getting-started.md",
"/spring-security/reactive-authentication-x509.md",
"/spring-security/reactive-authentication-logout.md",
"/spring-security/reactive-authorization-authorize-http-requests.md",
"/spring-security/reactive-authorization-method.md",
"/spring-security/reactive-oauth2.md",
"/spring-security/reactive-oauth2-login.md",
"/spring-security/reactive-oauth2-login-core.md",
"/spring-security/reactive-oauth2-login-advanced.md",
"/spring-security/reactive-oauth2-client.md",
"/spring-security/reactive-oauth2-client-core.md",
"/spring-security/reactive-oauth2-client-authorization-grants.md",
"/spring-security/reactive-oauth2-client-client-authentication.md",
"/spring-security/reactive-oauth2-client-authorized-clients.md",
"/spring-security/reactive-oauth2-resource-server.md",
"/spring-security/reactive-oauth2-resource-server-jwt.md",
"/spring-security/reactive-oauth2-resource-server-opaque-token.md",
"/spring-security/reactive-oauth2-resource-server-multitenancy.md",
"/spring-security/reactive-oauth2-resource-server-bearer-tokens.md",
"/spring-security/reactive-exploits.md",
"/spring-security/reactive-exploits-csrf.md",
"/spring-security/reactive-exploits-headers.md",
"/spring-security/reactive-exploits-http.md",
"/spring-security/reactive-integrations-cors.md",
"/spring-security/reactive-integrations-rsocket.md",
"/spring-security/reactive-test.md",
"/spring-security/reactive-test-method.md",
"/spring-security/reactive-test-web.md",
"/spring-security/reactive-test-web-setup.md",
"/spring-security/reactive-test-web-authentication.md",
"/spring-security/reactive-test-web-csrf.md",
"/spring-security/reactive-test-web-oauth2.md",
"/spring-security/reactive-configuration-webflux.md"
],
initialOpenGroupIndex: 0 // 可选的, 默认值是 0
}
......
......@@ -74,7 +74,7 @@ Authorization: Bearer some-token-value # Resource Server will process this
这意味着当在配置中使用`@EnableWebFlux`时,它可以在`@Controller`方法中使用:
爪哇
Java
```
@GetMapping("/foo")
......@@ -94,7 +94,7 @@ fun foo(authentication: BearerTokenAuthentication): Mono<String> {
由于`BearerTokenAuthentication`持有`OAuth2AuthenticatedPrincipal`,这也意味着控制器方法也可以使用它:
爪哇
Java
```
@GetMapping("/foo")
......@@ -118,7 +118,7 @@ fun foo(@AuthenticationPrincipal principal: OAuth2AuthenticatedPrincipal): Mono<
例如,如果使用`@EnableReactiveMethodSecurity`使你可以使用`@PreAuthorize`注释,则可以这样做:
爪哇
Java
```
@PreAuthorize("principal?.attributes['sub'] = 'foo'")
......@@ -142,7 +142,7 @@ fun forFoosEyesOnly(): Mono<String> {
第一个是将应用程序配置为资源服务器的`SecurityWebFilterChain`。当使用不透明令牌时,这个`SecurityWebFilterChain`看起来像:
爪哇
Java
```
@Bean
......@@ -178,7 +178,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
例 1。替换 SecurityWebFilterchain
爪哇
Java
```
@EnableWebFluxSecurity
......@@ -225,7 +225,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
例如,第二个`@Bean` Spring 启动创建了一个`ReactiveOpaqueTokenIntrospector`,它将`String`令牌解码为`OAuth2AuthenticatedPrincipal`的验证实例:
爪哇
Java
```
@Bean
......@@ -247,11 +247,10 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
并且它的配置可以使用`introspectionUri()``introspectionClientCredentials()`进行重写,或者使用`introspector()`进行替换。
### `
### 使用`inrospectionUri()`
可以配置授权服务器的内省 URI[作为配置属性](#webflux-oauth2resourceserver-opaque-introspectionuri),也可以在 DSL 中提供它:
爪哇
Java
```
@EnableWebFluxSecurity
......@@ -292,13 +291,11 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
}
```
使用`introspectionUri()`优先于任何配置属性。
### `
### 使用`introspectionUri()`优先于任何配置属性。
`introspectionUri()`更强大的是`introspector()`,它将完全取代`ReactiveOpaqueTokenIntrospector`的任何引导自动配置:
爪哇
Java
```
@EnableWebFluxSecurity
......@@ -343,7 +340,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
或者,暴露`ReactiveOpaqueTokenIntrospector``@Bean`具有与`introspector()`相同的效果:
爪哇
Java
```
@Bean
......@@ -371,7 +368,7 @@ OAuth2.0 内省端点通常会返回一个`scope`属性,指示它被授予的
这意味着,要保护具有由不透明令牌派生的作用域的端点或方法,相应的表达式应该包括以下前缀:
爪哇
Java
```
@EnableWebFluxSecurity
......@@ -410,7 +407,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
或类似于方法安全性:
爪哇
Java
```
@PreAuthorize("hasAuthority('SCOPE_messages')")
......@@ -441,7 +438,7 @@ fun getMessages(): Flux<Message> { }
当然,这可以使用自定义`ReactiveOpaqueTokenIntrospector`进行定制,该自定义`ReactiveOpaqueTokenIntrospector`查看属性集并以其自己的方式进行转换:
爪哇
Java
```
public class CustomAuthoritiesOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
......@@ -486,7 +483,7 @@ class CustomAuthoritiesOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector
在此之后,可以简单地将此自定义内省检测器配置为`@Bean`:
爪哇
Java
```
@Bean
......@@ -529,7 +526,7 @@ spring:
在这种情况下,你可以创建一个自定义的`ReactiveOpaqueTokenIntrospector`,它仍然会到达端点,但随后会更新返回的主体,使其具有 JWTS 声明的属性:
爪哇
Java
```
public class JwtOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
......@@ -581,7 +578,7 @@ class JwtOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
在此之后,可以简单地将此自定义内省检测器配置为`@Bean`:
爪哇
Java
```
@Bean
......@@ -613,7 +610,7 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
* 调用并返回来自`/userinfo`端点的响应
爪哇
Java
```
public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
......@@ -668,7 +665,7 @@ class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
如果你 AREN 不使用`spring-security-oauth2-client`,它仍然很简单。你只需要用你自己的`WebClient`实例调用`/userinfo`:
爪哇
Java
```
public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
......@@ -700,7 +697,7 @@ class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
无论哪种方式,在创建了`ReactiveOpaqueTokenIntrospector`之后,你应该将其发布为`@Bean`,以覆盖默认值:
爪哇
Java
```
@Bean
......
# 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)
# HttpFirewall
在针对你定义的模式进行测试时,了解该机制是什么以及使用了什么URL值是很重要的。
Servlet 规范为`HttpServletRequest`定义了几个属性,这些属性可以通过getter方法访问,并且我们可能希望对其进行匹配。它们是`contextPath``servletPath``pathInfo``queryString`。 Spring 安全性只对保护应用程序内的路径感兴趣,因此`contextPath`被忽略。不幸的是, Servlet 规范并没有确切地定义`servletPath``pathInfo`对于特定的请求URI的值将包含什么。例如,一个URL的每个路径段可以包含参数,如[RFC 2396](https://www.ietf.org/rfc/rfc2396.txt)<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>。规范没有明确说明是否应该将这些值包含在`servletPath``pathInfo`值中,并且不同的容器之间的行为是不同的。当应用程序部署在不从这些值中删除路径参数的容器中时,存在这样的危险:攻击者可能会将它们添加到所请求的URL中,从而导致模式匹配意外成功或失败。<sup class="footnote">[<a id="_footnoteref_2" class="footnote" href="#_footnotedef_2" title="View footnote.">2</a>]</sup>。传入URL中的其他变体也是可能的。例如,它可能包含路径遍历序列(如`/../`)或多个前向斜杠(`//`),这也可能导致模式匹配失败。一些容器在执行 Servlet 映射之前将这些规范化,但其他容器则不这样做。为了防止此类问题,`FilterChainProxy`使用`HttpFirewall`策略来检查和包装请求。默认情况下,未规范化的请求会被自动拒绝,并且为了匹配的目的,会删除路径参数和重复的斜杠。<sup class="footnote">[<a id="_footnoteref_3" class="footnote" href="#_footnotedef_3" title="View footnote.">3</a>]</sup>。因此,使用`FilterChainProxy`来管理安全筛选链是非常重要的。请注意,`servletPath``pathInfo`值是由容器解码的,因此你的应用程序不应该有任何包含半冒号的有效路径,因为为了匹配的目的,这些部分将被删除。
如上所述,默认策略是使用 Ant 样式的路径进行匹配,这可能是大多数用户的最佳选择。该策略在类`AntPathRequestMatcher`中实现,该类使用 Spring 的`AntPathMatcher`对串联的`servletPath``pathInfo`执行不区分大小写的模式匹配,忽略`queryString`
如果出于某种原因,你需要一个更强大的匹配策略,那么可以使用正则表达式。那么策略执行`RegexRequestMatcher`。有关此类的更多信息,请参见Javadoc。
在实践中,我们建议你在服务层使用方法安全性来控制对应用程序的访问,而不是完全依赖于在Web-Application级别定义的安全约束的使用。URL会发生变化,很难考虑到应用程序可能支持的所有可能的URL,以及如何操纵请求。你应该试着把自己限制在几条简单易懂的路径上。始终尝试使用“默认拒绝”方法,其中你有一个包罗万象的通配符(/**或**)在最后定义并拒绝访问。
在服务层定义的安全性要强大得多,也更难绕过,因此你应该始终利用 Spring Security的方法安全性选项。
`HttpFirewall`还通过拒绝HTTP响应头中的新行字符来防止[HTTP响应拆分](https://www.owasp.org/index.php/HTTP_Response_Splitting)
默认情况下使用`StrictHttpFirewall`。此实现拒绝似乎是恶意的请求。如果对你的需求来说过于严格,那么你可以自定义拒绝的请求类型。然而,重要的是,你要知道这可能会使你的应用程序受到攻击。例如,如果你希望利用 Spring MVC的矩阵变量,可以使用以下配置:
例1.允许矩阵变量
Java
```
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowSemicolon(true);
return firewall;
}
```
XML
```
<b:bean id="httpFirewall"
class="org.springframework.security.web.firewall.StrictHttpFirewall"
p:allowSemicolon="true"/>
<http-firewall ref="httpFirewall"/>
```
Kotlin
```
@Bean
fun httpFirewall(): StrictHttpFirewall {
val firewall = StrictHttpFirewall()
firewall.setAllowSemicolon(true)
return firewall
}
```
`StrictHttpFirewall`提供了一个允许的有效HTTP方法列表,这些方法被允许对[跨站点跟踪](https://owasp.org/www-community/attacks/Cross_Site_Tracing)[HTTP动词篡改](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/06-Test_HTTP_Methods)进行保护。默认的有效方法是“delete”、“get”、“head”、“options”、“patch”、“post”和“put”。如果你的应用程序需要修改有效的方法,你可以配置一个自定义的`StrictHttpFirewall` Bean。例如,以下将只允许HTTP“get”和“postmethod”方法:
例2.只允许get和post(P)
Java
```
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
return firewall;
}
```
XML
```
<b:bean id="httpFirewall"
class="org.springframework.security.web.firewall.StrictHttpFirewall"
p:allowedHttpMethods="GET,HEAD"/>
<http-firewall ref="httpFirewall"/>
```
Kotlin
```
@Bean
fun httpFirewall(): StrictHttpFirewall {
val firewall = StrictHttpFirewall()
firewall.setAllowedHttpMethods(listOf("GET", "POST"))
return firewall
}
```
| |如果你正在使用`new MockHttpServletRequest()`,则它当前将创建一个HTTP方法作为空字符串“。<br/>这是一个无效的HTTP方法,并且将被 Spring Security拒绝。<br/>你可以通过将其替换为`new MockHttpServletRequest("GET", "")`来解决此问题。<br/>有关请求改进此问题的请参见[SPR\_16851](https://jira.spring.io/browse/SPR-16851)。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
如果必须允许任何HTTP方法(不推荐),则可以使用`StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)`。这将完全禁用HTTP方法的验证。
`StrictHttpFirewall`还检查头名称、值和参数名称。它要求每个字符都有一个定义的代码点,而不是一个控制字符。
可以使用以下方法根据需要放松或调整此要求:
* `StrictHttpFirewall#setAllowedHeaderNames(Predicate)`
* `StrictHttpFirewall#setAllowedHeaderValues(Predicate)`
* `StrictHttpFirewall#setAllowedParameterNames(Predicate)`
| |而且,参数值可以用`setAllowedParameterValues(Predicate)`来控制。|
|---|-------------------------------------------------------------------------------------|
例如,要关闭此检查,你可以将你的`StrictHttpFirewall`与始终返回`Predicate``true`连接起来,如:
例3.允许任何头名称、头值和参数名称
Java
```
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHeaderNames((header) -> true);
firewall.setAllowedHeaderValues((header) -> true);
firewall.setAllowedParameterNames((parameter) -> true);
return firewall;
}
```
Kotlin
```
@Bean
fun httpFirewall(): StrictHttpFirewall {
val firewall = StrictHttpFirewall()
firewall.setAllowedHeaderNames { true }
firewall.setAllowedHeaderValues { true }
firewall.setAllowedParameterNames { true }
return firewall
}
```
或者,你可能需要允许一个特定的值。
例如,iPhone X使用`User-Agent`,其中包含一个不在ISO-8859-1字符集中的字符。由于这一事实,一些应用程序服务器将把这个值解析为两个单独的字符,后者是一个未定义的字符。
你可以使用`setAllowedHeaderValues`方法来解决这个问题,如下所示:
例4.允许某些用户代理
Java
```
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
Pattern allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*");
Pattern userAgent = ...;
firewall.setAllowedHeaderValues((header) -> allowed.matcher(header).matches() || userAgent.matcher(header).matches());
return firewall;
}
```
Kotlin
```
@Bean
fun httpFirewall(): StrictHttpFirewall {
val firewall = StrictHttpFirewall()
val allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*")
val userAgent = Pattern.compile(...)
firewall.setAllowedHeaderValues { allowed.matcher(it).matches() || userAgent.matcher(it).matches() }
return firewall
}
```
对于标头值,你可以考虑在验证时将它们解析为UTF-8,如下所示:
例5.将标题解析为UTF-8
Java
```
firewall.setAllowedHeaderValues((header) -> {
String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);
return allowed.matcher(parsed).matches();
});
```
Kotlin
```
firewall.setAllowedHeaderValues {
val parsed = String(header.getBytes(ISO_8859_1), UTF_8)
return allowed.matcher(parsed).matches()
}
```
---
[1](#_footnoteref_1)。当浏览器不支持cookie并且`jsessionid`参数在分号之后的URL中附加时,你可能已经看到了这种情况。然而,RFC允许在URL的任何路径段中存在这些参数。
[2](#_footnoteref_2)。一旦请求离开`FilterChainProxy`,将返回原始值,因此应用程序仍然可用。
[3](#_footnoteref_3)。因此,例如,原始请求路径`/secure;hack=1/somefile.html;hack=2`将返回为`/secure/somefile.html`
[HTTP](http.html)[整合](../integrations/index.html)
此差异已折叠。
# HTTP
所有基于HTTP的通信都应该受到保护[using TLS](../../features/exploits/http.html#http)
下面你可以找到有关 Servlet 特定特性的详细信息,这些特性有助于HTTPS的使用。
## 重定向到HTTPS
如果客户机使用HTTP而不是HTTPS发出请求,则可以将安全性配置为重定向到HTTPS。
例如,以下 Java 配置将把任何HTTP请求重定向到HTTPS:
例1.重定向到HTTPS
Java
```
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) {
http
// ...
.requiresChannel(channel -> channel
.anyRequest().requiresSecure()
);
}
}
```
Kotlin
```
@Configuration
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
// ...
requiresChannel {
secure(AnyRequestMatcher.INSTANCE, "REQUIRES_SECURE_CHANNEL")
}
}
}
}
```
下面的XML配置将把所有HTTP请求重定向到HTTPS
例2.使用XML配置重定向到HTTPS
```
<http>
<intercept-url pattern="/**" access="ROLE_USER" requires-channel="https"/>
...
</http>
```
## 严格的运输安全
Spring 安全性为[严格的运输安全](headers.html#servlet-headers-hsts)提供支持,并在默认情况下启用它。
## 代理服务器配置
Spring 安全性[与代理服务器集成](../../features/exploits/http.html#http-proxy-server)
[安全HTTP响应标头](headers.html)[HttpFirewall ](firewall.html)
# 保护免受剥削
本节讨论 Servlet 对[Spring Security’s protection against common exploits](../../features/exploits/index.html#exploits)的特定支持。
## 章节摘要
* [Cross Site Request Forgery (CSRF) for Servlet Environments](csrf.html)
* [安全HTTP响应标头](headers.html)
* [HTTP](http.html)
* [HttpFirewall](firewall.html)
[SAML2元数据](../saml2/metadata.html)[Cross Site Request Forgery (CSRF) for Servlet Environments](csrf.html)
# 你好 Spring 安全
本节介绍了如何在 Spring 引导中使用 Spring 安全性的最小设置。
| |可以找到已完成的应用程序[在我们的样品库中](https://github.com/spring-projects/spring-security-samples/tree/5.6.x/servlet/spring-boot/java/hello-security)<br/>为了你的方便,你可以通过[点击这里](https://start.spring.io/starter.zip?type=maven-project&language=java&packaging=jar&jvmVersion=1.8&groupId=example&artifactId=hello-security&name=hello-security&description=Hello%20Security&packageName=example.hello-security&dependencies=web,security)下载一个最小的 Spring 引导+ Spring 安全应用程序。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
## 更新依赖项
你需要做的唯一一步是通过使用[Maven](../getting-spring-security.html#getting-maven-boot)[Gradle](../getting-spring-security.html#getting-gradle-boot)更新依赖关系。
## 启动Hello Spring 安全启动
现在,你可以通过使用 Maven 插件的`run`目标[run the Spring Boot application](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-running-with-the-maven-plugin)。下面的示例展示了如何这样做(以及这样做产生的输出的开始):
例1.运行 Spring 启动应用程序
```
$ ./mvn spring-boot:run
...
INFO 23689 --- [ restartedMain] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
...
```
## Spring 引导自动配置
Spring 自动启动:
* 启用 Spring Security的默认配置,该配置将创建 Servlet `Filter`作为名为`springSecurityFilterChain`的 Bean。此 Bean 负责应用程序内的所有安全性(保护应用程序的URL、验证提交的用户名和密码、重定向到表单中的日志,等等)。
* 创建一个`UserDetailsService` Bean,其用户名为`user`,并随机生成一个登录到控制台的密码。
* 对于每个请求,用名为`springSecurityFilterChain`的 Bean 容器注册`Filter`
Spring Boot的配置不是很多,但它做了很多。以下是这些特征的摘要:
* 与应用程序的任何交互都需要经过身份验证的用户。
* 为你生成默认的登录表单
* 让用户名为`user`且密码已登录到控制台的用户使用基于表单的身份验证进行身份验证(在前面的示例中,密码为`8e557245-73e2-4286-969a-ff57fe326336`
* 使用bcrypt保护密码存储
* 让用户注销
* [CSRF攻击](https://en.wikipedia.org/wiki/Cross-site_request_forgery)预防
* [Session Fixation](https://en.wikipedia.org/wiki/Session_fixation)保护
* 安全报头集成
* [HTTP严格的传输安全](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security)用于安全请求
* [X-Content-Type-Options](https://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).ASPX)积分
* 缓存控制(可以稍后由应用程序重写,以允许缓存静态资源)
* [X-XSS-保护](https://msdn.microsoft.com/en-us/library/dd565647(v=vs.85).ASPX)积分
* x-frame-options集成,帮助防止[点击劫持](https://en.wikipedia.org/wiki/Clickjacking)
* 与以下 Servlet API方法集成:
* [`HttpServletRequest#getRemoteUser()`](https://DOCS.oracle.com/javaee/6/api/javax/ Servlet/http/httpservletrequest.html#getremoteuser())
* [`HttpServletRequest.html#getUserPrincipal()`](https://DOCS.oracle.com/javaee/6/api/javax/ Servlet/http/httpservletrequest.html#getUserprincipal())
* [`HttpServletRequest.html#isUserInRole(java.lang.String)`](https://DOCS.oracle.com/javaee/6/api/javax/ Servlet/http/httpservletrequest.html#isuserinrole( Java.lang.string))
* [`HttpServletRequest.html#login(java.lang.String, java.lang.String)`](https://DOCS.oracle.com/javaee/6/api/javax/ Servlet/http/httpservletrequest.html#login( Java.lang.string,%20java.lang.string))
* [`HttpServletRequest.html#logout()`](https://DOCS.oracle.com/javaee/6/api/javax/ Servlet/http/httpservletrequest.html#logout())
[Servlet Applications](index.html)[建筑](architecture.html)
# 并发支持
在大多数环境中,安全性是以per`Thread`为基础存储的。这意味着,当在新的`Thread`上完成工作时,`SecurityContext`将丢失。 Spring 安全性提供了一些基础设施,以帮助用户更容易地实现这一点。 Spring 安全性为在多线程环境中使用 Spring 安全性提供了低层次的抽象。实际上,这是 Spring 安全性构建在[`AsyncContext.start(Runnable)`]( Servlet-api.html#servletapi-start-runnable)和[Spring MVC Async Integration](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();
}
}
```
虽然非常简单,但它可以无缝地将SecurityContext从一个线程转移到另一个线程。这一点很重要,因为在大多数情况下,SecurityContextholder是以每个线程为基础的。例如,你可能使用了 Spring Security的[`<global-method-security>`](../acception/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();
```
上面的代码执行以下步骤:
* 创建将调用我们的安全服务的`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();
```
我们拥有的代码使用起来很简单,但仍然需要了解我们正在使用 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);
```
代码执行以下步骤:
* 创建用于我们的`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);
}
```
现在我们的代码不知道`SecurityContext`正在传播到`Thread`,然后运行`originalRunnable`,然后清除`SecurityContextHolder`。在本例中,使用相同的用户运行每个线程。如果我们希望在调用`SecurityContextHolder`时使用来自`executor.execute(Runnable)`的用户(即当前登录的用户)来处理`originalRunnable`,该怎么办?这可以通过从我们的`DelegatingSecurityContextExecutor`构造函数中删除`SecurityContext`参数来完成。例如:
```
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
```
现在,每当执行`executor.execute(Runnable)`时,`SecurityContext`首先由`SecurityContextHolder`得到,然后`SecurityContext`用于创建我们的`DelegatingSecurityContextRunnable`。这意味着我们运行`Runnable`的用户与调用`executor.execute(Runnable)`代码的用户相同。
## Spring 安全并发类
请参考Javadoc,以获取与 Java 并发API和 Spring 任务抽象的附加集成。一旦你理解了前面的代码,它们就非常不言自明了。
* `DelegatingSecurityContextCallable`
* `DelegatingSecurityContextExecutor`
* `DelegatingSecurityContextExecutorService`
* `DelegatingSecurityContextRunnable`
* `DelegatingSecurityContextScheduledExecutorService`
* `DelegatingSecurityContextSchedulingTaskExecutor`
* `DelegatingSecurityContextAsyncTaskExecutor`
* `DelegatingSecurityContextTaskExecutor`
* `DelegatingSecurityContextTaskScheduler`
[整合](index.html)[Jackson](jackson.html)
# CORS
Spring Framework提供[CORS的一流支持](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-cors)。CORS必须在 Spring 安全性之前进行处理,因为飞行前请求将不包含任何cookie(即`JSESSIONID`)。如果请求不包含任何cookie并且 Spring 安全性是第一位的,则该请求将确定用户未经过身份验证(因为在该请求中没有cookie)并拒绝它。
确保先处理CORS的最简单方法是使用`CorsFilter`。用户可以通过以下方式提供`CorsConfigurationSource`,将`CorsFilter`与 Spring 安全性集成在一起:
Java
```
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors(withDefaults())
...
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
```
Kotlin
```
@EnableWebSecurity
open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
// by default uses a Bean by the name of corsConfigurationSource
cors { }
// ...
}
}
@Bean
open fun corsConfigurationSource(): CorsConfigurationSource {
val configuration = CorsConfiguration()
configuration.allowedOrigins = listOf("https://example.com")
configuration.allowedMethods = listOf("GET", "POST")
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", configuration)
return source
}
}
```
或在XML中
```
<http>
<cors configuration-source-ref="corsSource"/>
...
</http>
<b:bean id="corsSource" class="org.springframework.web.cors.UrlBasedCorsConfigurationSource">
...
</b:bean>
```
如果使用 Spring MVC的CORS支持,则可以省略指定`CorsConfigurationSource`,并且 Spring Security将利用提供给 Spring MVC的CORS配置。
Java
```
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// if Spring MVC is on classpath and no CorsConfigurationSource is provided,
// Spring Security will use CORS configuration provided to Spring MVC
.cors(withDefaults())
...
}
}
```
Kotlin
```
@EnableWebSecurity
open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
// if Spring MVC is on classpath and no CorsConfigurationSource is provided,
// Spring Security will use CORS configuration provided to Spring MVC
cors { }
// ...
}
}
}
```
或在XML中
```
<http>
<!-- Default to Spring MVC's CORS configuration -->
<cors />
...
</http>
```
[WebSocket](websocket.html)[JSP Taglib](jsp-taglibs.html)
# Spring 数据集成
Spring 安全性提供了 Spring 数据集成,允许在查询中引用当前用户。将用户包括在查询中以支持分页结果不仅是有用的,而且是必要的,因为在此之后对结果进行过滤将不会扩展。
## Spring 数据和 Spring 安全配置
要使用此支持,请添加`org.springframework.security:spring-security-data`依赖项,并提供`SecurityEvaluationContextExtension`类型的 Bean:
Java
```
@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中的安全表达式
现在,安全性可以在查询中使用。例如:
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,查询中的所有[常见的安全表达式](../authorization/expression-based.html#common-expressions)都是可用的。
[Servlet APIs](servlet-api.html)[Spring MVC](mvc.html)
\ 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);
```
| |以下 Spring 安全模块提供了Jackson支持:<br/><br/>* Spring-security-core(`CoreJackson2Module`)<br/><br/>* Spring-security-web(`WebJackson2Module``WebServletJackson2Module``WebServerJackson2Module`<br/><11"gt="9"/>r=“r=”/>(<18"r="19"r=">>>>>(<<<<<gt="r="10">>>>>>|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
[并发性](concurrency.html)[本地化](localization.html)
# JSP标记库
## 宣布Taglib
要使用任何标记,你必须在JSP中声明安全性taglib:
```
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
```
## 授权标签
此标记用于确定是否应该对其内容进行评估。在 Spring Security3.0中,可以用两种方式<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>。第一种方法使用[Web安全表达式](../authorization/expression-based.html#el-access-web),在标记的`access`属性中指定。表达式求值将委托给在应用程序上下文中定义的`SecurityExpressionHandler<FilterInvocation>`(你应该在`<http>`名称空间配置中启用Web表达式,以确保此服务可用)。所以,举个例子,你可能
```
<sec:authorize access="hasRole('supervisor')">
This content will only be visible to users who have the "supervisor" authority in their list of <tt>GrantedAuthority</tt>s.
</sec:authorize>
```
当与 Spring Security的PermissionEvaluator结合使用时,该标记还可以用于检查权限。例如:
```
<sec:authorize access="hasPermission(#domain,'read') or hasPermission(#domain,'write')">
This content will only be visible to users who have read or write permission to the Object found as a request attribute named "domain".
</sec:authorize>
```
一个常见的要求是只显示一个特定的链接,如果用户实际上被允许单击它的话。我们怎样才能事先确定某件事是否会被允许?这个标记也可以在另一种模式下操作,这种模式允许你将特定的URL定义为一个属性。如果允许用户调用该URL,则将计算标记主体,否则将跳过该标记主体。所以你可能会有一些类似的东西
```
<sec:authorize url="/admin">
This content will only be visible to users who are authorized to send requests to the "/admin" URL.
</sec:authorize>
```
要使用此标记,还必须在应用程序上下文中有`WebInvocationPrivilegeEvaluator`的实例。如果使用名称空间,将自动注册一个名称空间。这是`DefaultWebInvocationPrivilegeEvaluator`的一个实例,该实例为提供的URL创建一个虚拟Web请求,并调用安全拦截器查看请求是成功还是失败。这允许你委派到使用`<http>`命名空间配置中的`intercept-url`声明定义的访问控制设置,并节省了在JSP中复制信息(例如所需的角色)的时间。这种方法还可以与`method`属性结合,提供HTTP方法,以进行更具体的匹配。
通过将`var`属性设置为变量名,可以将计算标记(无论是授予还是拒绝访问)的布尔结果存储在页面上下文范围变量中,从而避免了在页面的其他点重复和重新计算条件的需要。
### 禁用标记授权以进行测试
在页面中为未经授权的用户隐藏链接并不会阻止他们访问该URL。例如,他们可以直接在浏览器中输入。作为测试过程的一部分,你可能想要显示隐藏的区域,以检查链接是否真的在后端得到了保护。如果将系统属性`spring.security.disableUISecurity`设置为`true`,则`authorize`标记仍将运行,但不会隐藏其内容。默认情况下,它还会用`<span class="securityHiddenUI">…​</span>`标记包围内容。这允许你以特定的CSS样式(例如不同的背景颜色)显示“隐藏”内容。例如,尝试在启用此属性的情况下运行“tutorial”示例应用程序。
如果你想从默认的`span`标记中更改周围的文本(或使用空字符串完全删除它),还可以设置属性`spring.security.securedUIPrefix``spring.security.securedUISuffix`
## 身份验证标记
此标记允许访问存储在安全上下文中的当前`Authentication`对象。它在JSP中直接呈现对象的属性。因此,例如,如果`Authentication``principal`属性是 Spring Security的`UserDetails`对象的实例,那么使用`<sec:authentication property="principal.username" />`将呈现当前用户的名称。
当然,在这种情况下没有必要使用JSP标记,有些人更喜欢在视图中尽可能少地保留逻辑。你可以访问MVC控制器中的`Authentication`对象(通过调用`SecurityContextHolder.getContext().getAuthentication()`),并将数据直接添加到模型中,以便由视图进行呈现。
## AccessControlist标签
此标记仅在与 Spring Security的ACL模块一起使用时有效。它检查指定域对象所需权限的逗号分隔列表。如果当前用户拥有所有这些权限,那么将对标记主体进行评估。如果他们不这么做,就会被跳过。一个例子可能是
| |一般来说,这个标记应该被认为是废弃的。<br/>而不是使用[授权标签](#taglibs-authorize)。|
|---|-----------------------------------------------------------------------------------------------------------------|
```
<sec:accesscontrollist hasPermission="1,2" domainObject="${someObject}">
This will be shown if the user has all of the permissions represented by the values "1" or "2" on the given object.
</sec:accesscontrollist>
```
这些权限被传递给在应用程序上下文中定义的`PermissionFactory`,将它们转换为ACL`Permission`实例,因此它们可以是工厂支持的任何格式-它们不必是整数,它们可以是`READ``WRITE`之类的字符串。如果没有找到`PermissionFactory`,将使用`DefaultPermissionFactory`的实例。应用程序上下文中的`AclService`将用于为所提供的对象加载`Acl`实例。将使用所需的权限调用`Acl`,以检查是否已授予所有权限。
该标记还支持`var`属性,与`authorize`标记的方式相同。
## csrfinput标记
如果启用了CSRF保护,则此标记将插入一个隐藏的表单字段,其中包含CSRF保护令牌的正确名称和值。如果未启用CSRF保护,则此标记不输出任何内容。
Spring 通常情况下,Security会自动为你使用的任何`<form:form>`标记插入一个CSRF窗体字段,但是如果由于某种原因你无法使用`<form:form>``csrfInput`是一个方便的替换。
你应该将此标记放置在HTML`<form></form>`块中,在该块中你通常会放置其他输入字段。不要将此标记放置在 Spring `<form:form></form:form>`块中。 Spring 安全自动处理 Spring 表单。
```
<form method="post" action="/do/something">
<sec:csrfInput />
Name:<br />
<input type="text" name="name" />
...
</form>
```
## CSRFmetatags标签
如果启用了CSRF保护,则此标记将插入包含CSRF保护令牌窗体域和头名称以及CSRF保护令牌值的元标记。这些元标记对于在应用程序的JavaScript中使用CSRF保护非常有用。
你应该将`csrfMetaTags`放在HTML`<head></head>`块中,在该块中你通常会放置其他元标记。使用此标记后,就可以使用JavaScript轻松地访问表单字段名、标头名和令牌值。本例中使用了jQuery来简化任务。
```
<!DOCTYPE html>
<html>
<head>
<title>CSRF Protected JavaScript Page</title>
<meta name="description" content="This is the description for this page" />
<sec:csrfMetaTags />
<script type="text/javascript" language="javascript">
var csrfParameter = $("meta[name='_csrf_parameter']").attr("content");
var csrfHeader = $("meta[name='_csrf_header']").attr("content");
var csrfToken = $("meta[name='_csrf']").attr("content");
// using XMLHttpRequest directly to send an x-www-form-urlencoded request
var ajax = new XMLHttpRequest();
ajax.open("POST", "https://www.example.org/do/something", true);
ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded data");
ajax.send(csrfParameter + "=" + csrfToken + "&name=John&...");
// using XMLHttpRequest directly to send a non-x-www-form-urlencoded request
var ajax = new XMLHttpRequest();
ajax.open("POST", "https://www.example.org/do/something", true);
ajax.setRequestHeader(csrfHeader, csrfToken);
ajax.send("...");
// using JQuery to send an x-www-form-urlencoded request
var data = {};
data[csrfParameter] = csrfToken;
data["name"] = "John";
...
$.ajax({
url: "https://www.example.org/do/something",
type: "POST",
data: data,
...
});
// using JQuery to send a non-x-www-form-urlencoded request
var headers = {};
headers[csrfHeader] = csrfToken;
$.ajax({
url: "https://www.example.org/do/something",
type: "POST",
headers: headers,
...
});
<script>
</head>
<body>
...
</body>
</html>
```
如果不启用CSRF保护,`csrfMetaTags`将不输出任何内容。
---
[1](#_footnoteref_1)。还支持 Spring Security2.0中的遗留选项,但不推荐。
[Spring’s CORS Support](cors.html)[Java Configuration](../configuration/java.html)
# 本地化
如果你需要支持其他语言环境,那么你需要了解的所有内容都包含在本节中。
所有异常消息都可以本地化,包括与身份验证失败和访问被拒绝(授权失败)相关的消息。针对开发人员或系统部署人员的异常和日志消息(包括不正确的属性、违反接口契约、使用不正确的构造函数、启动时间验证、调试级别的日志记录)没有本地化,而是在 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 本地化的更多详细信息。
将“联系人”示例应用程序设置为使用本地化消息。
[Jackson](jackson.html)[Servlet APIs](servlet-api.html)
\ No newline at end of file
# Spring MVC集成
Spring 安全性提供了与 Spring MVC的许多可选集成。本节将进一步详细介绍集成。
## @enableWebMVCSecurity
| |在 Spring Security4.0中,`@EnableWebMvcSecurity`已被弃用。<br/>替换为`@EnableWebSecurity`,这将决定在 Classpath 的基础上添加 Spring MVC特性。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
要启用 Spring 与 Spring MVC的安全集成,请在配置中添加`@EnableWebSecurity`注释。
| |Spring 安全性提供了使用 Spring MVC的的配置。这意味着,如果你正在使用更高级的选项,比如直接与集成,那么你将需要手动提供 Spring 安全性配置。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
## MVCrequestMatcher
Spring 安全性提供了与 Spring MVC如何在具有`MvcRequestMatcher`的URL上匹配的深度集成。这有助于确保你的安全规则与用于处理请求的逻辑相匹配。
为了使用`MvcRequestMatcher`,你必须将 Spring 安全配置放在与你的`DispatcherServlet`相同的`ApplicationContext`中。这是必要的,因为 Spring Security的`MvcRequestMatcher`期望名称为`HandlerMappingIntrospector` Bean 的`mvcHandlerMappingIntrospector`被用于执行匹配的 Spring MVC配置注册。
对于`web.xml`,这意味着你应该将配置放在`DispatcherServlet.xml`中。
```
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- All Spring Configuration (both MVC and Security) are in /WEB-INF/spring/ -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/*.xml</param-value>
</context-param>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Load from the ContextLoaderListener -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
```
下面`WebSecurityConfiguration`中置于`DispatcherServlet`s`ApplicationContext`中。
Java
```
public class SecurityInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { RootConfiguration.class,
WebMvcConfiguration.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
```
Kotlin
```
class SecurityInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
override fun getRootConfigClasses(): Array<Class<*>>? {
return null
}
override fun getServletConfigClasses(): Array<Class<*>> {
return arrayOf(
RootConfiguration::class.java,
WebMvcConfiguration::class.java
)
}
override fun getServletMappings(): Array<String> {
return arrayOf("/")
}
}
```
| |始终建议通过匹配`HttpServletRequest`和方法安全性来提供授权规则。<br/><br/>通过匹配`HttpServletRequest`来提供授权规则是很好的,因为它在代码路径中很早就发生了,并且有助于减少<br/>方法安全性确保如果有人绕过了Web授权规则,那么你的应用程序仍然是安全的。<br/>这就是所谓的[纵深防御](https://en.wikipedia.org/wiki/Defense_in_depth_(computing))|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
考虑一个映射如下的控制器:
Java
```
@RequestMapping("/admin")
public String admin() {
```
Kotlin
```
@RequestMapping("/admin")
fun admin(): String {
```
如果我们希望将对此控制器方法的访问限制为管理用户,那么开发人员可以通过在`HttpServletRequest`上匹配以下内容来提供授权规则:
Java
```
protected configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.antMatchers("/admin").hasRole("ADMIN")
);
}
```
Kotlin
```
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(AntPathRequestMatcher("/admin"), hasRole("ADMIN"))
}
}
}
```
或在XML中
```
<http>
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
</http>
```
对于任一种配置,URL`/admin`将要求经过身份验证的用户是管理用户。然而,根据我们的 Spring MVC配置,URL`/admin.html`也将映射到我们的`admin()`方法。此外,根据我们的 Spring MVC配置,URL`/admin/`也将映射到我们的`admin()`方法。
问题在于,我们的安全规则仅保护`/admin`。我们可以为 Spring MVC的所有排列添加额外的规则,但这将是非常冗长和乏味的。
相反,我们可以利用 Spring security的`MvcRequestMatcher`。下面的配置将通过使用 Spring MVC在URL上进行匹配来保护与 Spring MVC匹配的相同的URL。
Java
```
protected configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.mvcMatchers("/admin").hasRole("ADMIN")
);
}
```
Kotlin
```
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize("/admin", hasRole("ADMIN"))
}
}
}
```
或在XML中
```
<http request-matcher="mvc">
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
</http>
```
## @AuthenticationPrincipal
Spring 安全性提供了`AuthenticationPrincipalArgumentResolver`,它可以自动解析 Spring MVC参数的当前`Authentication.getPrincipal()`。通过使用`@EnableWebSecurity`,你将自动将其添加到 Spring MVC配置中。如果使用基于XML的配置,则必须自己添加该配置。例如:
```
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
```
一旦`AuthenticationPrincipalArgumentResolver`被正确配置,你就可以在 Spring MVC层中与 Spring 安全性完全解耦。
考虑一种情况,其中一个自定义`UserDetailsService`返回一个`Object`,它实现`UserDetails`和你自己的`CustomUser``Object`。可以使用以下代码访问当前经过身份验证的用户的`CustomUser`:
Java
```
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser() {
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
CustomUser custom = (CustomUser) authentication == null ? null : authentication.getPrincipal();
// .. find messages for this user and return them ...
}
```
Kotlin
```
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(): ModelAndView {
val authentication: Authentication = SecurityContextHolder.getContext().authentication
val custom: CustomUser? = if (authentication as CustomUser == null) null else authentication.principal
// .. find messages for this user and return them ...
}
```
从 Spring Security3.2开始,我们可以通过添加一个注释来更直接地解决该参数。例如:
Java
```
import org.springframework.security.core.annotation.AuthenticationPrincipal;
// ...
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {
// .. find messages for this user and return them ...
}
```
Kotlin
```
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@AuthenticationPrincipal customUser: CustomUser?): ModelAndView {
// .. find messages for this user and return them ...
}
```
有时可能有必要以某种方式转换本金。例如,如果`CustomUser`需要是最终的,则不能对其进行扩展。在这种情况下,`UserDetailsService`可能返回一个`Object`,它实现`UserDetails`并提供一个名为`getCustomUser`的方法来访问`CustomUser`。例如,它可能看起来像:
Java
```
public class CustomUserUserDetails extends User {
// ...
public CustomUser getCustomUser() {
return customUser;
}
}
```
Kotlin
```
class CustomUserUserDetails(
username: String?,
password: String?,
authorities: MutableCollection<out GrantedAuthority>?
) : User(username, password, authorities) {
// ...
val customUser: CustomUser? = null
}
```
然后,我们可以使用一个[Spel表达式](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html)访问`CustomUser`,它使用`Authentication.getPrincipal()`作为根对象:
Java
```
import org.springframework.security.core.annotation.AuthenticationPrincipal;
// ...
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") CustomUser customUser) {
// .. find messages for this user and return them ...
}
```
Kotlin
```
import org.springframework.security.core.annotation.AuthenticationPrincipal
// ...
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") customUser: CustomUser?): ModelAndView {
// .. find messages for this user and return them ...
}
```
我们也可以在SPEL表达式中引用bean。例如,如果我们使用 JPA 来管理我们的用户,并且我们希望修改并保存当前用户的属性,那么可以使用以下内容。
Java
```
import org.springframework.security.core.annotation.AuthenticationPrincipal;
// ...
@PutMapping("/users/self")
public ModelAndView updateName(@AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") CustomUser attachedCustomUser,
@RequestParam String firstName) {
// change the firstName on an attached instance which will be persisted to the database
attachedCustomUser.setFirstName(firstName);
// ...
}
```
Kotlin
```
import org.springframework.security.core.annotation.AuthenticationPrincipal
// ...
@PutMapping("/users/self")
open fun updateName(
@AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") attachedCustomUser: CustomUser,
@RequestParam firstName: String?
): ModelAndView {
// change the firstName on an attached instance which will be persisted to the database
attachedCustomUser.setFirstName(firstName)
// ...
}
```
通过在我们自己的注释上使用`@AuthenticationPrincipal`元注释,我们可以进一步消除对 Spring 安全性的依赖。下面我们将演示如何在名为`@CurrentUser`的注释上实现这一点。
| |重要的是要认识到,为了消除对 Spring 安全性的依赖关系,将创建`@CurrentUser`的是消耗应用程序。<br/>这一步骤并不是严格必需的,但有助于将你对 Spring 安全性的依赖关系隔离到一个更中心的位置。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
Java
```
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface CurrentUser {}
```
Kotlin
```
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@AuthenticationPrincipal
annotation class CurrentUser
```
现在已经指定了`@CurrentUser`,我们可以使用它来发送信号,以解析当前已验证用户的`CustomUser`。我们还将对 Spring 安全性的依赖隔离到一个文件中。
Java
```
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) {
// .. find messages for this user and return them ...
}
```
Kotlin
```
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@CurrentUser customUser: CustomUser?): ModelAndView {
// .. find messages for this user and return them ...
}
```
## Spring MVC异步集成
Spring Web MVC3.2+对[异步请求处理](https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-ann-async)具有出色的支持。 Spring 在没有额外配置的情况下,Security将自动将`SecurityContext`设置为调用控制器返回的`Thread``Callable`。例如,下面的方法将自动使用创建`Callable`时可用的`SecurityContext`调用其`Callable`:
Java
```
@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public Object call() throws Exception {
// ...
return "someView";
}
};
}
```
Kotlin
```
@RequestMapping(method = [RequestMethod.POST])
open fun processUpload(file: MultipartFile?): Callable<String> {
return Callable {
// ...
"someView"
}
}
```
| |将SecurityContext与Callable的<br/><br/>更严格地说, Spring Security与`WebAsyncManager`集成。<br/>用于处理`Callable``SecurityContext`是在调用`SecurityContextHolder`时存在于`SecurityContextHolder`上的`SecurityContext`。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
没有控制器返回的`DeferredResult`的自动集成。这是因为`DeferredResult`是由用户处理的,因此无法自动与其集成。然而,你仍然可以使用[并发支持](../../features/integrations/concurrency.html#concurrency)来提供具有 Spring 安全性的透明集成。
## Spring MVC和CSRF集成
### 自动令牌包含
Spring 在使用[Spring MVC form tag](https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/view.html#view-jsp-formtaglib-formtag)的窗体中,安全性将自动[包括CSRF令牌](../exploits/csrf.html#servlet-csrf-include)。例如,下面的JSP:
```
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:form="http://www.springframework.org/tags/form" version="2.0">
<jsp:directive.page language="java" contentType="text/html" />
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<!-- ... -->
<c:url var="logoutUrl" value="/logout"/>
<form:form action="${logoutUrl}"
method="post">
<input type="submit"
value="Log out" />
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
</form:form>
<!-- ... -->
</html>
</jsp:root>
```
将输出类似于以下内容的HTML:
```
<!-- ... -->
<form action="/context/logout" method="post">
<input type="submit" value="Log out"/>
<input type="hidden" name="_csrf" value="f81d4fae-7dec-11d0-a765-00a0c91e6bf6"/>
</form>
<!-- ... -->
```
### 解析CSRFToken
Spring Security提供了`CsrfTokenArgumentResolver`,它可以自动解析 Spring MVC参数的当前`CsrfToken`。通过使用[@enableWebSecurity](../configuration/java.html#jc-hello-wsca),你将自动将其添加到 Spring MVC配置中。如果使用基于XML的配置,则必须自己添加该配置。
一旦正确配置了`CsrfTokenArgumentResolver`,就可以将`CsrfToken`公开到基于HTML的静态应用程序中。
Java
```
@RestController
public class CsrfController {
@RequestMapping("/csrf")
public CsrfToken csrf(CsrfToken token) {
return token;
}
}
```
Kotlin
```
@RestController
class CsrfController {
@RequestMapping("/csrf")
fun csrf(token: CsrfToken): CsrfToken {
return token
}
}
```
保持`CsrfToken`是其他域的秘密是很重要的。这意味着如果你使用[跨源共享](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS),那么你应该**NOT**`CsrfToken`公开到任何外部域。
[Spring Data](data.html)[WebSocket](websocket.html)
# Servlet API集成
## Servlet 2.5+集成
### HttpServletRequest.getRemoteUser()
[HttpServletRequest.getRemoteUser()](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getRemoteUser())将返回`SecurityContextHolder.getContext().getAuthentication().getName()`的结果,这通常是当前的用户名。如果你希望在应用程序中显示当前用户名,这将非常有用。此外,检查此值是否为null可以用来指示用户是否已通过身份验证或是匿名的。了解用户是否经过了身份验证,对于确定某些UI元素是否应该显示是有用的(例如,只有在用户经过身份验证的情况下才应该显示注销链接)。
### HttpServletRequest.getUserPrincipal()
[HttpServletRequest.getUserPrincipal()](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal())将返回`SecurityContextHolder.getContext().getAuthentication()`的结果。这意味着它是`Authentication`,当使用基于用户名和密码的身份验证时,它通常是`UsernamePasswordAuthenticationToken`的一个实例。如果你需要有关你的用户的其他信息,这可能是有用的。例如,你可能已经创建了一个自定义`UserDetailsService`,它返回一个自定义的`UserDetails`,其中包含用户的姓和名。你可以通过以下方式获得此信息:
Java
```
Authentication auth = httpServletRequest.getUserPrincipal();
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
MyCustomUserDetails userDetails = (MyCustomUserDetails) auth.getPrincipal();
String firstName = userDetails.getFirstName();
String lastName = userDetails.getLastName();
```
Kotlin
```
val auth: Authentication = httpServletRequest.getUserPrincipal()
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
val userDetails: MyCustomUserDetails = auth.principal as MyCustomUserDetails
val firstName: String = userDetails.firstName
val lastName: String = userDetails.lastName
```
| |应该注意的是,在整个应用程序中执行这么多逻辑通常是糟糕的做法。<br/>相反,应该将其集中以减少 Spring 安全性和 Servlet API的任何耦合。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### HttpServletRequest.isUserinRole(字符串)
[HttpServletRequest.isUserinRole(字符串)](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#isUserInRole(java.lang.String))将确定`SecurityContextHolder.getContext().getAuthentication().getAuthorities()`是否包含带有传递到`isUserInRole(String)`的角色的`GrantedAuthority`。通常,用户不应该将“role\_”前缀传入此方法,因为它是自动添加的。例如,如果你想确定当前用户是否拥有“role\_admin”权限,可以使用以下方法:
Java
```
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
```
Kotlin
```
val isAdmin: Boolean = httpServletRequest.isUserInRole("ADMIN")
```
这对于确定是否应该显示某些UI组件可能很有用。例如,你可能仅在当前用户是管理员的情况下才显示管理链接。
## Servlet 3+集成
下面的部分描述了 Spring 安全性集成的 Servlet 3种方法。
### HttpServletRequest.Authenticate(HttpServletRequest,HttpServletResponse)
可以使用[HttpServletRequest.Authenticate(HttpServletRequest,HttpServletResponse)](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#authenticate%28javax.servlet.http.HttpServletResponse%29)方法来确保对用户进行身份验证。如果未对它们进行身份验证,则将使用配置的身份验证中心点来请求用户进行身份验证(即重定向到登录页面)。
### HttpServletRequest.login(字符串,字符串)
可以使用[HttpServletRequest.login(字符串,字符串)](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#login%28java.lang.String,%20java.lang.String%29)方法对当前`AuthenticationManager`的用户进行身份验证。例如,下面将尝试使用用户名“user”和密码“password”进行身份验证:
Java
```
try {
httpServletRequest.login("user","password");
} catch(ServletException ex) {
// fail to authenticate
}
```
Kotlin
```
try {
httpServletRequest.login("user", "password")
} catch (ex: ServletException) {
// fail to authenticate
}
```
| |如果你希望 Spring 安全性来处理失败的身份验证尝试,那么不需要捕获ServletException。|
|---|---------------------------------------------------------------------------------------------------------------------------|
### HttpServletRequest.logout()
可以使用[HttpServletRequest.logout()](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#logout%28%29)方法将当前用户注销。
通常,这意味着SecurityContextholder将被清除,HttpSession将无效,任何“记住我”的身份验证都将被清除,等等。然而,所配置的LogouthAndler实现将根据你的 Spring 安全配置而有所不同。需要注意的是,在调用了HttpServletRequest.logout()之后,你仍然负责编写响应。通常情况下,这需要重定向到欢迎页面。
### AsyncContext.start(可运行)
确保你的凭据将被传播到新线程的[AsyncContext.start(可运行)](https://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html#start%28java.lang.Runnable%29)方法。利用 Spring Security的并发支持, Spring Security覆盖了AsyncContext.Start,以确保在处理Runnable时使用当前的SecurityContext。例如,下面将输出当前用户的身份验证:
Java
```
final AsyncContext async = httpServletRequest.startAsync();
async.start(new Runnable() {
public void run() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
try {
final HttpServletResponse asyncResponse = (HttpServletResponse) async.getResponse();
asyncResponse.setStatus(HttpServletResponse.SC_OK);
asyncResponse.getWriter().write(String.valueOf(authentication));
async.complete();
} catch(Exception ex) {
throw new RuntimeException(ex);
}
}
});
```
Kotlin
```
val async: AsyncContext = httpServletRequest.startAsync()
async.start {
val authentication: Authentication = SecurityContextHolder.getContext().authentication
try {
val asyncResponse = async.response as HttpServletResponse
asyncResponse.status = HttpServletResponse.SC_OK
asyncResponse.writer.write(String.valueOf(authentication))
async.complete()
} catch (ex: Exception) {
throw RuntimeException(ex)
}
}
```
### 异步 Servlet 支持
如果你正在使用基于 Java 的配置,那么你已经准备好了。如果你正在使用XML配置,那么有一些更新是必要的。第一步是确保你已经更新了web.xml,以至少使用3.0模式,如下所示:
```
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
</web-app>
```
接下来,你需要确保你的SpringSecurityFilterchain是为处理异步请求而设置的。
```
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ASYNC</dispatcher>
</filter-mapping>
```
就是这样!现在 Spring 安全性将确保你的SecurityContext也在异步请求上传播。
那么,它是如何工作的呢?如果你真的不感兴趣,请跳过这一节的其余部分,否则请继续阅读。这其中的大部分是内置在 Servlet 规范中的,但是 Spring 安全性做了一些调整,以确保异步请求能够正常工作。在 Spring Security3.2之前,一旦提交了HttpServletResponse,来自SecurityContextholder的SecurityContext就会自动保存。这可能会在异步环境中引起问题。例如,考虑以下几点:
Java
```
httpServletRequest.startAsync();
new Thread("AsyncThread") {
@Override
public void run() {
try {
// Do work
TimeUnit.SECONDS.sleep(1);
// Write to and commit the httpServletResponse
httpServletResponse.getOutputStream().flush();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}.start();
```
Kotlin
```
httpServletRequest.startAsync()
object : Thread("AsyncThread") {
override fun run() {
try {
// Do work
TimeUnit.SECONDS.sleep(1)
// Write to and commit the httpServletResponse
httpServletResponse.outputStream.flush()
} catch (ex: java.lang.Exception) {
ex.printStackTrace()
}
}
}.start()
```
问题是安全性不知道这个线程,因此SecurityContext不会传播到它。这意味着当我们提交HttpServletResponse时,不存在SecurityContext。 Spring 当Security在提交HttpServletResponse时自动保存SecurityContext时,它将丢失已登录的用户。
自版本3.2以来, Spring 安全性已经足够聪明,不再在调用HttpServletRequest.startasync()时提交HttpServletResponse时自动保存SecurityContext。
## Servlet 3.1+集成
下面的部分描述了 Spring 安全性集成的 Servlet 3.1方法。
### HttpServletRequest#changesessionID()
在 Servlet 3.1及更高版本中,[HttpServletRequest.changesessionID()](https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html#changeSessionId())是防止[Session Fixation](../authentication/session-management.html#ns-session-fixation)攻击的默认方法。
[本地化](localization.html)[Spring Data](data.html)
\ No newline at end of file
# WebSocket 安全
Spring Security4增加了对保护[Spring’s WebSocket support](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html)的支持。本节介绍如何使用 Spring Security的 WebSocket 支持。
直接支持JSR-356
Spring 安全性不提供直接的JSR-356支持,因为这样做将提供很少的价值。这是因为格式未知,所以有[little Spring can do to secure an unknown format](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-intro-sub-protocol)。此外,JSR-356不提供截获消息的方法,因此安全性将是侵入性的。
## WebSocket 配置
Spring Security4.0通过 Spring 消息抽象引入了对WebSockets的授权支持。要使用 Java 配置配置来配置授权,只需扩展`AbstractSecurityWebSocketMessageBrokerConfigurer`并配置`MessageSecurityMetadataSourceRegistry`。例如:
Java
```
@Configuration
public class WebSocketSecurityConfig
extends AbstractSecurityWebSocketMessageBrokerConfigurer { (1) (2)
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers("/user/**").authenticated() (3)
}
}
```
Kotlin
```
@Configuration
open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() { (1) (2)
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
messages.simpDestMatchers("/user/**").authenticated() (3)
}
}
```
这将确保:
|**1**|任何入站连接消息都需要一个有效的CSRF令牌来执行[同源政策](#websocket-sameorigin)|
|-----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|对于任何入站请求,SecurityContextholder都在Simpuser header属性中填充用户。|
|**3**|我们的信息需要得到适当的授权。具体地说,任何以“/user/”开头的入站消息都需要角色\_user。有关授权的更多详细信息,请参见[WebSocket 授权](#websocket-authorization)。|
Spring 安全性还提供了[XML命名空间](../appendix/namespace/websocket.html#nsa-websocket-security)用于保护WebSockets的支持。类似的基于XML的配置如下所示:
```
<websocket-message-broker> (1) (2)
(3)
<intercept-message pattern="/user/**" access="hasRole('USER')" />
</websocket-message-broker>
```
这将确保:
|**1**|任何入站连接消息都需要一个有效的CSRF令牌来执行[同源政策](#websocket-sameorigin)|
|-----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|对于任何入站请求,SecurityContextholder都在Simpuser header属性中填充用户。|
|**3**|我们的信息需要得到适当的授权。具体地说,任何以“/user/”开头的入站消息都需要角色\_user。有关授权的更多详细信息,请参见[WebSocket Authorization](#websocket-authorization)。|
## WebSocket 认证
WebSockets重用在建立 WebSocket 连接时在HTTP请求中找到的相同的身份验证信息。这意味着`Principal`上的`HttpServletRequest`将被传递给WebSockets。如果使用 Spring 安全性,则`HttpServletRequest`上的`Principal`将自动被重写。
更具体地说,为了确保用户已经对你的 WebSocket 应用程序进行了身份验证,所有必要的是确保设置 Spring 安全性以对基于HTTP的Web应用程序进行身份验证。
## WebSocket Authorization
Spring Security4.0通过 Spring 消息抽象引入了对WebSockets的授权支持。要使用 Java 配置配置来配置授权,只需扩展`AbstractSecurityWebSocketMessageBrokerConfigurer`并配置`MessageSecurityMetadataSourceRegistry`。例如:
Java
```
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().authenticated() (1)
.simpSubscribeDestMatchers("/user/queue/errors").permitAll() (2)
.simpDestMatchers("/app/**").hasRole("USER") (3)
.simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") (4)
.simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() (5)
.anyMessage().denyAll(); (6)
}
}
```
Kotlin
```
@Configuration
open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() {
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
messages
.nullDestMatcher().authenticated() (1)
.simpSubscribeDestMatchers("/user/queue/errors").permitAll() (2)
.simpDestMatchers("/app/**").hasRole("USER") (3)
.simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") (4)
.simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() (5)
.anyMessage().denyAll() (6)
}
}
```
这将确保:
|**1**|任何没有目的地的消息(即消息类型或订阅以外的任何消息)都需要对用户进行身份验证。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|任何人都可以订阅/user/queue/errors|
|**3**|任何具有以“/app/”开头的目标的消息都将要求用户具有角色\_user|
|**4**|任何以“/user/”或“/topic/friends/”开头、类型为Subscribe的消息都需要角色\_user|
|**5**|任何其他类型为消息或订阅的消息都将被拒绝。由于6,我们不需要这个步骤,但它说明了如何在特定的消息类型上进行匹配。|
|**6**|任何其他消息都将被拒绝。这是一个好主意,以确保你不会错过任何消息。|
Spring 安全性还提供了[XML命名空间](../appendix/namespace/websocket.html#nsa-websocket-security)用于保护WebSockets的支持。类似的基于XML的配置如下所示:
```
<websocket-message-broker>
(1)
<intercept-message type="CONNECT" access="permitAll" />
<intercept-message type="UNSUBSCRIBE" access="permitAll" />
<intercept-message type="DISCONNECT" access="permitAll" />
<intercept-message pattern="/user/queue/errors" type="SUBSCRIBE" access="permitAll" /> (2)
<intercept-message pattern="/app/**" access="hasRole('USER')" /> (3)
(4)
<intercept-message pattern="/user/**" access="hasRole('USER')" />
<intercept-message pattern="/topic/friends/*" access="hasRole('USER')" />
(5)
<intercept-message type="MESSAGE" access="denyAll" />
<intercept-message type="SUBSCRIBE" access="denyAll" />
<intercept-message pattern="/**" access="denyAll" /> (6)
</websocket-message-broker>
```
这将确保:
|**1**|任何类型为“连接”、“取消订阅”或“断开连接”的消息都需要对用户进行身份验证。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|任何人都可以订阅/user/queue/errors|
|**3**|任何具有以“/app/”开头的目标的消息都将要求用户具有角色\_user|
|**4**|任何以“/user/”或“/topic/friends/”开头、类型为Subscribe的消息都需要角色\_user|
|**5**|任何其他类型为消息或订阅的消息都将被拒绝。由于6,我们不需要这个步骤,但它说明了如何在特定的消息类型上进行匹配。|
|**6**|具有目的的任何其他消息都将被拒绝。这是一个好主意,以确保你不会错过任何消息。|
### WebSocket 授权说明
为了正确地保护你的应用程序,理解 Spring 的 WebSocket 支持非常重要。
#### WebSocket 对消息类型的授权
理解消息的订阅和消息类型之间的区别以及它在 Spring 中的工作方式非常重要。
考虑一个聊天应用程序。
* 系统可以通过目标“/topic/system/notifications”向所有用户发送通知消息。
* 客户端可以通过订阅“/topic/system/notification”来接收通知。
虽然我们希望客户机能够订阅“/topic/system/notifications”,但我们并不希望使他们能够向该目的地发送消息。如果我们允许向“/topic/system/notifications”发送消息,那么客户端可以直接向该端点发送消息并模拟系统。
通常,应用程序会拒绝发送到以[代理前缀](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp)(即“/topic/”或“/queue/”)开头的目标的任何消息。
#### WebSocket 目的地授权
了解目的地是如何转变的也很重要。
考虑一个聊天应用程序。
* 用户可以通过向“/app/chat”的目的地发送消息来向特定用户发送消息。
* 应用程序看到消息,确保将“from”属性指定为当前用户(我们不能信任客户端)。
* 然后,应用程序使用`SimpMessageSendingOperations.convertAndSendToUser("toUser", "/queue/messages", message)`将消息发送给收件人。
* 消息被转换为“/queue/user/messages-\<sessionid\>”的目标。
对于上面的应用程序,我们希望允许我们的客户机侦听“/user/queue”,它被转换为“/queue/user/messages-\<sessionid\>”。但是,我们不希望客户机能够侦听“/queue/\*”,因为这将允许客户机查看每个用户的消息。
通常,应用程序会拒绝发送到以[代理前缀](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp)(即“/topic/”或“/queue/”)开头的消息的任何订阅。当然,我们可能会提供例外情况,以解释诸如
### 出站消息
Spring 包含一个标题为[消息流](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-message-flow)的部分,该部分描述了消息如何在系统中流动。需要注意的是, Spring 安全性仅保护`clientInboundChannel`。 Spring 安全性不试图保护`clientOutboundChannel`
这其中最重要的原因是业绩。对于每一条传入的消息,通常都会有更多的消息传出。我们鼓励保护对端点的订阅,而不是保护出站消息。
## 执行同源政策
需要强调的是,对于 WebSocket 连接,浏览器并不强制执行[同源政策](https://en.wikipedia.org/wiki/Same-origin_policy)。这是一个极其重要的考虑因素。
### 为什么是同源?
考虑以下场景。用户访问bank.com并对其帐户进行身份验证。同一个用户在浏览器中打开另一个标签,然后访问Evil.com。相同的源策略确保Evil.com不能将数据读写到bank.com。
对于WebSockets,相同的源策略不适用。事实上,除非bank.com明确禁止,否则evil.com可以代表用户读写数据。这意味着用户可以在 WebSocket 上做的任何事情(即转账),Evil.com都可以代表用户做。
由于Sockjs试图模拟WebSockets,因此它也绕过了相同的源策略。这意味着开发人员在使用Sockjs时需要显式地保护其应用程序不受外部域的影响。
### Spring WebSocket 允许原产地
幸运的是,由于 Spring 4.1.5 Spring 的 WebSocket 和Sockjs支持限制了对[当前域](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-server-allowed-origins)的访问。 Spring 安全性增加了额外的保护层,以提供[纵深防御](https://en.wikipedia.org/wiki/Defense_in_depth_(computing))。
### 将CSRF添加到Stomp头
Spring 默认情况下,安全性要求在任何连接消息类型中使用[CSRF token](../../features/exploits/csrf.html#csrf)。这确保只有能够访问CSRF令牌的站点才能进行连接。由于只有**同源**可以访问CSRF令牌,因此不允许外部域进行连接。
通常,我们需要在HTTP报头或HTTP参数中包含CSRF令牌。然而,Sockjs不允许这些选项。相反,我们必须在Stomp头中包含令牌。
应用程序可以通过访问名为\_CSRF的请求属性[获取CSRF令牌](../exploits/csrf.html#servlet-csrf-include)。例如,下面将允许访问JSP中的`CsrfToken`:
```
var headerName = "${_csrf.headerName}";
var token = "${_csrf.token}";
```
如果使用静态HTML,则可以在REST端点上公开`CsrfToken`。例如,下面将公开URL/CSRF上的`CsrfToken`
Java
```
@RestController
public class CsrfController {
@RequestMapping("/csrf")
public CsrfToken csrf(CsrfToken token) {
return token;
}
}
```
Kotlin
```
@RestController
class CsrfController {
@RequestMapping("/csrf")
fun csrf(token: CsrfToken): CsrfToken {
return token
}
}
```
JavaScript可以对端点进行REST调用,并使用响应来填充headername和令牌。
我们现在可以在我们的STOMP客户端中包含令牌。例如:
```
...
var headers = {};
headers[headerName] = token;
stompClient.connect(headers, function(frame) {
...
}
```
### 禁用WebSockets中的CSRF
如果你希望允许其他域访问你的站点,则可以禁用 Spring Security的保护。例如,在 Java 配置中,你可以使用以下方法:
Java
```
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
...
@Override
protected boolean sameOriginDisabled() {
return true;
}
}
```
Kotlin
```
@Configuration
open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() {
// ...
override fun sameOriginDisabled(): Boolean {
return true
}
}
```
## 与Sockjs合作
[SockJS](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-fallback)提供后备传输以支持较旧的浏览器。在使用后备选项时,我们需要放松一些安全约束,以允许Sockjs使用 Spring 安全性。
### sockjs&frame-选项
Sockjs可以使用[利用iframe的传输](https://github.com/sockjs/sockjs-client/tree/v0.3.4)。 Spring 默认情况下,安全性将[deny](../../features/exploits/headers.html#headers-frame-options)网站框起来,以防止点击劫持攻击。为了允许基于SockJS帧的传输工作,我们需要配置 Spring 安全性,以允许相同的源来帧内容。
你可以使用[框架-选项](../appendix/namespace/http.html#nsa-frame-options)元素自定义X-frame-options。例如,下面将指示 Spring Security使用“X-Frame-Options:SameOrigin”,它允许在相同的域内使用IFrames:
```
<http>
<!-- ... -->
<headers>
<frame-options
policy="SAMEORIGIN" />
</headers>
</http>
```
类似地,你可以使用以下方式自定义框架选项,以便在 Java 配置中使用相同的原点:
Java
```
@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.headers(headers -> headers
.frameOptions(frameOptions -> frameOptions
.sameOrigin()
)
);
}
}
```
Kotlin
```
@EnableWebSecurity
open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
// ...
headers {
frameOptions {
sameOrigin = true
}
}
}
}
}
```
### Sockjs&放松的CSRF
对于任何基于HTTP的传输,Sockjs都会在Connect消息上使用POST。通常,我们需要在HTTP报头或HTTP参数中包含CSRF令牌。然而,Sockjs不允许这些选项。相反,我们必须像[将CSRF添加到Stomp头](#websocket-sameorigin-csrf)中描述的那样,在stomp头中包含令牌。
这也意味着我们需要放松对Web层的CSRF保护。具体地说,我们希望禁用我们的连接URL的CSRF保护。我们不希望禁用每个URL的CSRF保护。否则,我们的网站将容易受到CSRF攻击。
我们可以通过提供CSRF请求匹配器轻松地实现这一点。我们的 Java 配置使这一点变得非常容易。例如,如果我们的Stomp端点是“/chat”,那么我们可以使用以下配置,仅禁用以“/chat/”开头的URL的CSRF保护:
Java
```
@Configuration
@EnableWebSecurity
public class WebSecurityConfig
extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
// ignore our stomp endpoints since they are protected using Stomp headers
.ignoringAntMatchers("/chat/**")
)
.headers(headers -> headers
// allow same origin to frame our site to support iframe SockJS
.frameOptions(frameOptions -> frameOptions
.sameOrigin()
)
)
.authorizeHttpRequests(authorize -> authorize
...
)
...
```
Kotlin
```
@Configuration
@EnableWebSecurity
open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
csrf {
ignoringAntMatchers("/chat/**")
}
headers {
frameOptions {
sameOrigin = true
}
}
authorizeRequests {
// ...
}
// ...
```
如果我们使用基于XML的配置,我们可以使用[[电子邮件保护]](../acception/namespace/http.html#NSA-csrf-request-matcher-ref)。例如:
```
<http ...>
<csrf request-matcher-ref="csrfMatcher"/>
<headers>
<frame-options policy="SAMEORIGIN"/>
</headers>
...
</http>
<b:bean id="csrfMatcher"
class="AndRequestMatcher">
<b:constructor-arg value="#{T(org.springframework.security.web.csrf.CsrfFilter).DEFAULT_CSRF_MATCHER}"/>
<b:constructor-arg>
<b:bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
<b:bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
<b:constructor-arg value="/chat/**"/>
</b:bean>
</b:bean>
</b:constructor-arg>
</b:bean>
```
[Spring MVC](mvc.html)[Spring’s CORS Support](cors.html)
\ No newline at end of file
# 整合
Spring 安全性与许多框架和API集成在一起。在本节中,我们将讨论 Spring 安全性集成:
## 章节摘要
* [并发性](concurrency.html)
* [Jackson](jackson.html)
* [本地化](localization.html)
* [Servlet APIs](servlet-api.html)
* [Spring Data](data.html)
* [Spring MVC](mvc.html)
* [WebSocket](websocket.html)
* [Spring’s CORS Support](cors.html)
* [JSP Taglib](jsp-taglibs.html)
[HttpFirewall](../exploits/firewall.html)[并发性](concurrency.html)
\ No newline at end of file
# OAuth2
Spring 安全性提供了全面的OAuth2支持。本节讨论如何将OAuth2集成到基于 Servlet 的应用程序中。
## 章节摘要
* [OAuth2登录](login/index.html)
* [OAuth2客户端](client/index.html)
* [OAuth2资源服务器](resource-server/index.html)
[域对象安全ACLS](../authorization/acls.html)[OAuth2登录](login/index.html)
\ No newline at end of file
# 授权客户功能
## 解决授权客户
`@RegisteredOAuth2AuthorizedClient`注释提供了将方法参数解析为`OAuth2AuthorizedClient`类型的参数值的功能。与使用`OAuth2AuthorizedClientManager``OAuth2AuthorizedClientService`访问`OAuth2AuthorizedClient`相比,这是一种方便的替代方法。
Java
```
@Controller
public class OAuth2ClientController {
@GetMapping("/")
public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
...
return "index";
}
}
```
Kotlin
```
@Controller
class OAuth2ClientController {
@GetMapping("/")
fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2AuthorizedClient): String {
val accessToken = authorizedClient.accessToken
...
return "index"
}
}
```
`@RegisteredOAuth2AuthorizedClient`注释由`OAuth2AuthorizedClientArgumentResolver`处理,它直接使用[`OAuth2AuthorizedClientManager`](core.html#oAuth2client-authorized-manager-provider),因此继承了它的功能。
## 用于 Servlet 环境的WebClient集成
OAuth2.0客户端支持使用`ExchangeFilterFunction``WebClient`集成。
`ServletOAuth2AuthorizedClientExchangeFilterFunction`提供了一种简单的机制,通过使用`OAuth2AuthorizedClient`请求受保护的资源,并将相关的`OAuth2AccessToken`作为承载令牌。它直接使用[`OAuth2AuthorizedClientManager`](core.html#OAuth2client-authorized-manager-provider),因此继承了以下功能:
* 如果客户端尚未获得授权,则将请求`OAuth2AccessToken`
* `authorization_code`-触发授权请求重定向以初始化流
* `client_credentials`-访问令牌是直接从令牌端点获得的
* `password`-访问令牌是直接从令牌端点获得的
* 如果`OAuth2AccessToken`过期,如果`OAuth2AuthorizedClientProvider`可用于执行授权,则将刷新(或更新)该权限
下面的代码展示了如何使用OAuth2.0客户端支持配置`WebClient`的示例:
Java
```
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
```
Kotlin
```
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClient {
val oauth2Client = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build()
}
```
### 提供授权客户
通过解析`ClientRequest.attributes()`(请求属性)中的`OAuth2AuthorizedClient``ServletOAuth2AuthorizedClientExchangeFilterFunction`确定要使用的客户机(用于请求)。
下面的代码展示了如何将`OAuth2AuthorizedClient`设置为请求属性:
Java
```
@GetMapping("/")
public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
String resourceUri = ...
String body = webClient
.get()
.uri(resourceUri)
.attributes(oauth2AuthorizedClient(authorizedClient)) (1)
.retrieve()
.bodyToMono(String.class)
.block();
...
return "index";
}
```
Kotlin
```
@GetMapping("/")
fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2AuthorizedClient): String {
val resourceUri: String = ...
val body: String = webClient
.get()
.uri(resourceUri)
.attributes(oauth2AuthorizedClient(authorizedClient)) (1)
.retrieve()
.bodyToMono()
.block()
...
return "index"
}
```
|**1**|`oauth2AuthorizedClient()``static`中的一个`static`方法。|
|-----|---------------------------------------------------------------------------------------------------------|
下面的代码展示了如何将`ClientRegistration.getRegistrationId()`设置为请求属性:
Java
```
@GetMapping("/")
public String index() {
String resourceUri = ...
String body = webClient
.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta")) (1)
.retrieve()
.bodyToMono(String.class)
.block();
...
return "index";
}
```
Kotlin
```
@GetMapping("/")
fun index(): String {
val resourceUri: String = ...
val body: String = webClient
.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta")) (1)
.retrieve()
.bodyToMono()
.block()
...
return "index"
}
```
|**1**|`clientRegistrationId()``static`中的一个`static`方法。|
|-----|-------------------------------------------------------------------------------------------------------|
### 对授权客户违约
如果`OAuth2AuthorizedClient``ClientRegistration.getRegistrationId()`都不作为请求属性提供,则`ServletOAuth2AuthorizedClientExchangeFilterFunction`可以根据其配置来确定要使用的*默认值*客户端。
如果`setDefaultOAuth2AuthorizedClient(true)`被配置并且用户已经使用`HttpSecurity.oauth2Login()`进行了身份验证,则使用与当前`OAuth2AccessToken`关联的`OAuth2AccessToken`
以下代码显示了具体的配置:
Java
```
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultOAuth2AuthorizedClient(true);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
```
Kotlin
```
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClient {
val oauth2Client = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauth2Client.setDefaultOAuth2AuthorizedClient(true)
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build()
}
```
| |由于所有HTTP请求都将接收访问令牌,因此建议对此功能保持谨慎。|
|---|---------------------------------------------------------------------------------------------------------|
或者,如果`setDefaultClientRegistrationId("okta")`被配置为有效的`ClientRegistration`,则使用与`OAuth2AuthorizedClient`关联的`OAuth2AccessToken`
以下代码显示了具体的配置:
Java
```
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("okta");
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
```
Kotlin
```
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClient {
val oauth2Client = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauth2Client.setDefaultClientRegistrationId("okta")
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build()
}
```
| |由于所有HTTP请求都将接收访问令牌,因此建议对此功能保持谨慎。|
|---|---------------------------------------------------------------------------------------------------------|
[OAuth2客户端身份验证](client-authentication.html)[OAuth2资源服务器](../resource-server/index.html)
\ No newline at end of file
# 客户端身份验证支持
## JWT持有人
| |有关[JWT Bearer](https://datatracker.ietf.org/doc/html/rfc7523#section-2.2)客户端身份验证的更多详细信息,请参考JSON Web Token配置文件中的OAuth2.0客户端身份验证和授权授予。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
JWT承载客户端身份验证的默认实现是`NimbusJwtClientAuthenticationParametersConverter`,这是一个`Converter`,它通过在`client_assertion`参数中添加签名的JSON Web令牌来定制令牌请求参数。
用于对JWS进行签名的`java.security.PrivateKey``javax.crypto.SecretKey`由与`NimbusJwtClientAuthenticationParametersConverter`关联的`com.nimbusds.jose.jwk.JWK`解析器提供。
###
使用`private_key_jwt`进行身份验证
给出了OAuth2.0客户端注册的以下 Spring Boot2.x属性:
```
spring:
security:
oauth2:
client:
registration:
okta:
client-id: okta-client-id
client-authentication-method: private_key_jwt
authorization-grant-type: authorization_code
...
```
下面的示例展示了如何配置`DefaultAuthorizationCodeTokenResponseClient`:
Java
```
Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
// Assuming RSA key type
RSAPublicKey publicKey = ...
RSAPrivateKey privateKey = ...
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
return null;
};
OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
new OAuth2AuthorizationCodeGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(
new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));
DefaultAuthorizationCodeTokenResponseClient tokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
tokenResponseClient.setRequestEntityConverter(requestEntityConverter);
```
Kotlin
```
val jwkResolver: Function<ClientRegistration, JWK> =
Function<ClientRegistration, JWK> { clientRegistration ->
if (clientRegistration.clientAuthenticationMethod.equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
// Assuming RSA key type
var publicKey: RSAPublicKey
var privateKey: RSAPrivateKey
RSAKey.Builder(publicKey) = //...
.privateKey(privateKey) = //...
.keyID(UUID.randomUUID().toString())
.build()
}
null
}
val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter()
requestEntityConverter.addParametersConverter(
NimbusJwtClientAuthenticationParametersConverter(jwkResolver)
)
val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
tokenResponseClient.setRequestEntityConverter(requestEntityConverter)
```
### 使用`client_secret_jwt`进行身份验证
给出了OAuth2.0客户端注册的以下 Spring Boot2.x属性:
```
spring:
security:
oauth2:
client:
registration:
okta:
client-id: okta-client-id
client-secret: okta-client-secret
client-authentication-method: client_secret_jwt
authorization-grant-type: client_credentials
...
```
下面的示例展示了如何配置`DefaultClientCredentialsTokenResponseClient`:
Java
```
Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) {
SecretKeySpec secretKey = new SecretKeySpec(
clientRegistration.getClientSecret().getBytes(StandardCharsets.UTF_8),
"HmacSHA256");
return new OctetSequenceKey.Builder(secretKey)
.keyID(UUID.randomUUID().toString())
.build();
}
return null;
};
OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter =
new OAuth2ClientCredentialsGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(
new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));
DefaultClientCredentialsTokenResponseClient tokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
tokenResponseClient.setRequestEntityConverter(requestEntityConverter);
```
Kotlin
```
val jwkResolver = Function<ClientRegistration, JWK?> { clientRegistration: ClientRegistration ->
if (clientRegistration.clientAuthenticationMethod == ClientAuthenticationMethod.CLIENT_SECRET_JWT) {
val secretKey = SecretKeySpec(
clientRegistration.clientSecret.toByteArray(StandardCharsets.UTF_8),
"HmacSHA256"
)
OctetSequenceKey.Builder(secretKey)
.keyID(UUID.randomUUID().toString())
.build()
}
null
}
val requestEntityConverter = OAuth2ClientCredentialsGrantRequestEntityConverter()
requestEntityConverter.addParametersConverter(
NimbusJwtClientAuthenticationParametersConverter(jwkResolver)
)
val tokenResponseClient = DefaultClientCredentialsTokenResponseClient()
tokenResponseClient.setRequestEntityConverter(requestEntityConverter)
```
[OAuth2授权授予](authorization-grants.html)[OAuth2授权客户](authorized-clients.html)
\ No newline at end of file
此差异已折叠。
# OAuth2.0客户端
OAuth2.0客户机特性为[OAuth2.0授权框架](https://tools.ietf.org/html/rfc6749#section-1.1)中定义的客户机角色提供支持。
在高层次上,可用的核心特性是:
授权赠款支助
* [授权代码](https://tools.ietf.org/html/rfc6749#section-1.3.1)
* [刷新令牌](https://tools.ietf.org/html/rfc6749#section-6)
* [客户凭据](https://tools.ietf.org/html/rfc6749#section-1.3.4)
* [资源所有者密码凭据](https://tools.ietf.org/html/rfc6749#section-1.3.3)
* [JWT Bearer](https://datatracker.ietf.org/doc/html/rfc7523#section-2.1)
客户端身份验证支持
* [JWT Bearer](https://datatracker.ietf.org/doc/html/rfc7523#section-2.2)
HTTP客户端支持
* [`WebClient` Servlet 环境的集成](授权-clients.html#OAuth2client-Webclient- Servlet)(用于请求受保护的资源)
`HttpSecurity.oauth2Client()`DSL为定制OAuth2.0客户端使用的核心组件提供了许多配置选项。此外,`HttpSecurity.oauth2Client().authorizationCodeGrant()`实现了对授权代码授权的定制。
以下代码显示了`HttpSecurity.oauth2Client()`DSL提供的完整配置选项:
例1. OAuth2客户端配置选项
Java
```
@EnableWebSecurity
public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Client(oauth2 -> oauth2
.clientRegistrationRepository(this.clientRegistrationRepository())
.authorizedClientRepository(this.authorizedClientRepository())
.authorizedClientService(this.authorizedClientService())
.authorizationCodeGrant(codeGrant -> codeGrant
.authorizationRequestRepository(this.authorizationRequestRepository())
.authorizationRequestResolver(this.authorizationRequestResolver())
.accessTokenResponseClient(this.accessTokenResponseClient())
)
);
}
}
```
Kotlin
```
@EnableWebSecurity
class OAuth2ClientSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
oauth2Client {
clientRegistrationRepository = clientRegistrationRepository()
authorizedClientRepository = authorizedClientRepository()
authorizedClientService = authorizedClientService()
authorizationCodeGrant {
authorizationRequestRepository = authorizationRequestRepository()
authorizationRequestResolver = authorizationRequestResolver()
accessTokenResponseClient = accessTokenResponseClient()
}
}
}
}
}
```
除了`HttpSecurity.oauth2Client()`DSL之外,还支持XML配置。
以下代码显示了[安全命名空间](../../appendix/namespace/http.html#nsa-oauth2-client)中可用的完整配置选项:
例2. OAuth2客户机XML配置选项
```
<http>
<oauth2-client client-registration-repository-ref="clientRegistrationRepository"
authorized-client-repository-ref="authorizedClientRepository"
authorized-client-service-ref="authorizedClientService">
<authorization-code-grant
authorization-request-repository-ref="authorizationRequestRepository"
authorization-request-resolver-ref="authorizationRequestResolver"
access-token-response-client-ref="accessTokenResponseClient"/>
</oauth2-client>
</http>
```
`OAuth2AuthorizedClientManager`负责与一个或多个`OAuth2AuthorizedClientProvider`协作管理OAuth2.0客户端的授权(或重新授权)。
下面的代码展示了如何注册`OAuth2AuthorizedClientManager``@Bean`并将其与`OAuth2AuthorizedClientProvider`复合相关联的示例,该复合提供对`authorization_code``refresh_token``client_credentials``password`授权授予类型的支持:
Java
```
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
```
Kotlin
```
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
val authorizedClientProvider: OAuth2AuthorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
```
## 章节摘要
* [核心接口和类](core.html)
* [OAuth2授权授予](authorization-grants.html)
* [OAuth2客户端身份验证](client-authentication.html)
* [OAuth2授权客户](authorized-clients.html)
[高级配置](../login/advanced.html)[核心接口和类](core.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)
\ No newline at end of file
# 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标识)的情况下,这很方便。资源服务器可以参考此授权服务器来授权请求。
本节详细介绍了 Spring Security如何为OAuth2.0[不记名代币](https://tools.ietf.org/html/rfc6750.html)提供支持。
| |[JWTs](https://github.com/spring-projects/spring-security-samples/tree/5.6.x/servlet/spring-boot/java/oauth2/resource-server/jwe)[不透明令牌](https://github.com/spring-projects/spring-security-samples/tree/5.6.x/servlet/spring-boot/java/oauth2/resource-server/opaque)的工作样例都可以在[Spring Security Samples repository](https://github.com/spring-projects/spring-security-samples/tree/5.6.x)中找到。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
让我们来看看承载令牌身份验证在 Spring 安全性中是如何工作的。首先,我们看到,像[基本身份验证](../../authentication/passwords/basic.html#servlet-authentication-basic)一样,[WWW-认证](https://tools.ietf.org/html/rfc7235#section-4.1)头被发送回未经验证的客户机。
![BeareRauthenticationCentryPoint](https://docs.spring.io/spring-security/reference/_images/servlet/oauth2/bearerauthenticationentrypoint.png)
图1.发送WWW-身份验证报头
上面的图是基于我们的[`SecurityFilterChain`](../../architecture.html# Servlet-SecurityFilterchain)图构建的。
![number 1](https://docs.spring.io/spring-security/reference/_images/icons/number_1.png)首先,用户向资源`/private`发出未经授权的请求。
![number 2](https://docs.spring.io/spring-security/reference/_images/icons/number_2.png) Spring security的[`FilterSecurityInterceptor`](.../授权/authorization/authorization/authorization-requests.html# Servlet-authorization-filtersecurityinterceptor)通过抛出`AccessDeniedException`表示未经验证的请求是*拒绝*
![number 3](https://docs.spring.io/spring-security/reference/_images/icons/number_3.png)由于用户未经过身份验证,[`ExceptionTranslationFilter`](..../architecture.html# Servlet-ExceptionTranslationFilter)发起*启动身份验证*。已配置的[`AuthenticationEntryPoint`](...../authentication/architecture.html# Servlet-authentication-authentryPoint)是[`BearerTokenAuthenticationEntryPoint`](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/oauth2/server/resource/web/bearerertokenauthentrypoint.html)的一个实例,它发送一个WWW-authenticate报`RequestCache`通常是不保存请求的`NullRequestCache`,因为客户机能够重放它最初请求的请求。
当客户端收到`WWW-Authenticate: Bearer`头时,它知道应该使用承载令牌进行重试。下面是正在处理的承载令牌的流程。
![BeareRtoKenAuthenticationFilter](https://docs.spring.io/spring-security/reference/_images/servlet/oauth2/bearertokenauthenticationfilter.png)
图2.不记名令牌的认证
该图构建于我们的[`SecurityFilterChain`](../../architecture.html# Servlet-SecurityFilterchain)图。
![number 1](https://docs.spring.io/spring-security/reference/_images/icons/number_1.png)当用户提交其承载令牌时,`BearerTokenAuthenticationFilter`通过从`HttpServletRequest`中提取令牌来创建`BearerTokenAuthenticationToken`,这是[`Authentication`](../../authentication/architecture.html# Servlet-authentication-authentication)的一种类型。
![number 2](https://docs.spring.io/spring-security/reference/_images/icons/number_2.png)接下来,将`HttpServletRequest`传递给`AuthenticationManagerResolver`,后者选择`AuthenticationManager`。将`BearerTokenAuthenticationToken`传递到要进行身份验证的`AuthenticationManager`中。`AuthenticationManager`的详细内容取决于你是为[JWT](jwt.html#oauth2resourceserver-jwt-minimalconfiguration)还是[不透明令牌](opaque-token.html#oauth2resourceserver-opaque-minimalconfiguration)配置的。
![number 3](https://docs.spring.io/spring-security/reference/_images/icons/number_3.png)如果身份验证失败,则*失败*
* [SecurityContextholder](../../authentication/architecture.html#servlet-authentication-securitycontextholder)被清除。
* 调用`AuthenticationEntryPoint`以触发要再次发送的WWW-Authenticate头。
![number 4](https://docs.spring.io/spring-security/reference/_images/icons/number_4.png)如果身份验证成功,则*成功*
* [认证](../../authentication/architecture.html#servlet-authentication-authentication)设置在[SecurityContextholder](../../authentication/architecture.html#servlet-authentication-securitycontextholder)上。
* `BearerTokenAuthenticationFilter`调用`FilterChain.doFilter(request,response)`以继续应用程序逻辑的其余部分。
[OAuth2授权客户](../client/authorized-clients.html)[JWT](jwt.html)
\ No newline at end of file
此差异已折叠。
# SAML2.0登录
SAML2.0登录特性为应用程序提供了充当SAML2.0依赖方的能力,使用户[log in](https://wiki.shibboleth.net/confluence/display/CONCEPT/FlowsAndConfig)通过使用其在SAML2.0声明方(OKTA、ADFS等)的现有帐户来访问应用程序。
| |SAML2.0登录是通过使用**Web浏览器SSO配置文件**实现的,如[SAML2配置文件](https://www.oasis-open.org/committees/download.php/35389/sstc-saml-profiles-errata-2.0-wd-06-diff.pdf#page=15)中所指定的。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
自2009年以来,对依赖方的支持以[扩展项目](https://github.com/spring-projects/spring-security-saml/tree/1e013b07a7772defd6a26fcfae187c9bf661ee8f#spring-saml)的形式存在。2019年,该过程开始将其移植到[Spring Security](https://github.com/spring-projects/spring-security)proper中。这个过程类似于2017年开始的[Spring Security’s OAuth 2.0 support](../../oauth2/index.html)的过程。
| |[SAML2.0登录](https://github.com/spring-projects/spring-security-samples/tree/5.6.x/servlet/spring-boot/java/saml2/login)的工作示例在[Spring Security Samples repository](https://github.com/spring-projects/spring-security-samples/tree/5.6.x)中可用。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
## 章节摘要
* [SAML2日志概览](overview.html)
* [SAML2身份验证请求](authentication-requests.html)
* [SAML2身份验证响应](authentication.html)
[SAML2](../index.html)[SAML2日志概览](overview.html)
\ No newline at end of file
此差异已折叠。
# 生成`<saml2:SPSSODescriptor>`元数据
你可以通过将`Saml2MetadataFilter`添加到筛选链来发布元数据端点,如下所示:
Java
```
DefaultRelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository);
Saml2MetadataFilter filter = new Saml2MetadataFilter(
relyingPartyRegistrationResolver,
new OpenSamlMetadataResolver());
http
// ...
.saml2Login(withDefaults())
.addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
```
Kotlin
```
val relyingPartyRegistrationResolver: Converter<HttpServletRequest, RelyingPartyRegistration> =
DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository)
val filter = Saml2MetadataFilter(
relyingPartyRegistrationResolver,
OpenSamlMetadataResolver()
)
http {
//...
saml2Login { }
addFilterBefore<Saml2WebSsoAuthenticationFilter>(filter)
}
```
你可以使用此元数据端点将你的依赖方注册为你的断言方.这通常很简单,只需找到正确的表单字段来提供元数据端点即可。
默认情况下,元数据端点是`/saml2/service-provider-metadata/{registrationId}`。你可以通过调用过滤器上的`setRequestMatcher`方法来更改这一点:
Java
```
filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata/{registrationId}", "GET"));
```
Kotlin
```
filter.setRequestMatcher(AntPathRequestMatcher("/saml2/metadata/{registrationId}", "GET"))
```
或者,如果你在构造函数中注册了一个自定义依赖方注册解析器,那么你可以在没有`registrationId`提示的情况下指定一个路径,就像这样:
Java
```
filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata", "GET"));
```
Kotlin
```
filter.setRequestMatcher(AntPathRequestMatcher("/saml2/metadata", "GET"))
```
[SAML2注销](logout.html)[保护免受剥削](../exploits/index.html)
\ No newline at end of file
# SAML2
Spring 安全性提供了全面的SAML2支持。本节讨论如何将SAML2集成到基于 Servlet 的应用程序中。
## 章节摘要
* [SAML2登录](login/index.html)
* [SAML2注销](logout.html)
* [SAML2元数据](metadata.html)
[不记名代币](../oauth2/resource-server/bearer-tokens.html)[SAML2登录](login/index.html)
\ No newline at end of file
此差异已折叠。
此差异已折叠。
# 基于测试表单的身份验证
你可以使用 Spring Security的测试支持轻松地创建一个请求来测试基于表单的身份验证。例如,下面的`formLogin`[`RequestPostProcessor`](request-post-processors.html)将向“/login”提交一篇文章,其中包含用户名“user”、密码“password”和一个有效的CSRF令牌:
Java
```
mvc
.perform(formLogin())
```
Kotlin
```
mvc
.perform(formLogin())
```
定制请求很容易。例如,下面将向“/auth”提交一篇带有用户名“admin”、密码“pass”和有效的CSRF令牌的文章:
Java
```
mvc
.perform(formLogin("/auth").user("admin").password("pass"))
```
Kotlin
```
mvc
.perform(formLogin("/auth").user("admin").password("pass"))
```
我们还可以自定义包含用户名和密码的参数名。例如,上面的请求被修改为包括HTTP参数“U”上的用户名和HTTP参数“P”上的密码。
Java
```
mvc
.perform(formLogin("/auth").user("u","admin").password("p","pass"))
```
Kotlin
```
mvc
.perform(formLogin("/auth").user("u","admin").password("p","pass"))
```
[嘲笑CSRF ](csrf.html)[模拟HTTP BASIC ](http-basic.html)
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册