# Servlet 身份验证体系结构 本讨论扩展到[Servlet Security: The Big Picture](../architecture.html#servlet-architecture),以描述在 Servlet 身份验证中使用的 Spring 安全性的主要体系结构组件。如果你需要具体的流程来解释这些部分是如何组合在一起的,请查看[认证机制](index.html#servlet-authentication-mechanisms)特定部分。 * [SecurityContextholder](#servlet-authentication-securitycontextholder)-`SecurityContextholder`是 Spring 安全性存储谁是[已认证](../../features/authentication/index.html#authentication)的详细信息的地方。 * [SecurityContext](#servlet-authentication-securitycontext)-是从`SecurityContextHolder`获得的,并且包含当前已验证用户的`认证`。 * [认证](#servlet-authentication-authentication)-可以是对`身份验证管理器`的输入,以提供用户已提供的用于验证的凭据或来自`SecurityContext`的当前用户。 * [大企业](#servlet-authentication-granted-authority)-在`Authentication`上授予主体的权限(即角色、范围等) * [身份验证管理器](#servlet-authentication-authenticationmanager)-定义 Spring 安全性过滤器如何执行[认证](../../features/authentication/index.html#authentication)的 API。 * [ProviderManager](#servlet-authentication-providermanager)-`AuthenticationManager`的最常见的实现。 * [身份验证提供者](#servlet-authentication-authenticationprovider)-用于`ProviderManager`执行特定类型的身份验证。 * [带有`AuthenticationEntryPoint`的请求凭据](# Servlet-authentication-authentryPoint)-用于从客户端请求凭据(即重定向到登录页面,发送`WWW-Authenticate`响应等) * [抽象处理过滤器](#servlet-authentication-abstractprocessingfilter)-用于身份验证的基`Filter`。这也为身份验证的高级流程以及各个部分如何协同工作提供了一个很好的思路。 ## SecurityContextHolder hi Servlet/身份验证/体系结构 Spring 安全性的身份验证模型的核心是`SecurityContextHolder`。它包含[SecurityContext](#servlet-authentication-securitycontext)。 ![SecurityContextholder](https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/securitycontextholder.png) 在`SecurityContextHolder`中, Spring 安全性存储了谁是[已认证](../../features/authentication/index.html#authentication)的详细信息。 Spring 安全性并不关心`SecurityContextHolder`是如何填充的。如果它包含一个值,那么它将被用作当前经过身份验证的用户。 表示用户已通过身份验证的最简单方法是直接设置`SecurityContextHolder`。 例 1。设置`SecurityContextHolder` 爪哇 ``` SecurityContext context = SecurityContextHolder.createEmptyContext(); (1) Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER"); (2) context.setAuthentication(authentication); SecurityContextHolder.setContext(context); (3) ``` Kotlin ``` val context: SecurityContext = SecurityContextHolder.createEmptyContext() (1) val authentication: Authentication = TestingAuthenticationToken("username", "password", "ROLE_USER") (2) context.authentication = authentication SecurityContextHolder.setContext(context) (3) ``` |**1**|我们首先创建一个空的`SecurityContext`。
重要的是创建一个新的`SecurityContext`实例,而不是使用`SecurityContextHolder.getContext().setAuthentication(authentication)`,以避免跨多个线程的竞争条件。| |-----|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |**2**|接下来我们创建一个新的[`Authentication`](# Servlet-authentication-authentication)对象。
Spring Security 并不关心在`Authentication`上设置了什么类型的`Authentication`实现。
这里我们使用`TestingAuthenticationToken`,因为它非常简单。
一个更常见的生产场景是。| |**3**|最后,我们在`SecurityContextHolder`上设置`SecurityContext`。
Spring Security 将对[授权](../authorization/index.html#servlet-authorization)使用此信息。| 如果你希望获得有关经过身份验证的主体的信息,可以通过访问`SecurityContextHolder`来实现。 例 2。访问当前通过身份验证的用户 爪哇 ``` SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); String username = authentication.getName(); Object principal = authentication.getPrincipal(); Collection authorities = authentication.getAuthorities(); ``` Kotlin ``` val context = SecurityContextHolder.getContext() val authentication = context.authentication val username = authentication.name val principal = authentication.principal val authorities = authentication.authorities ``` 默认情况下,`SecurityContextHolder`使用`ThreadLocal`来存储这些细节,这意味着`SecurityContext`对于同一线程中的方法总是可用的,即使`SecurityContext`没有显式地作为参数传递给这些方法。以这种方式使用`ThreadLocal`是非常安全的,如果在处理当前主体的请求之后要注意清除线程。 Spring Security 的[FilterchainProxy](../architecture.html#servlet-filterchainproxy)确保始终清除`SecurityContext`。 一些应用程序 AREN 并不完全适合使用`ThreadLocal`,因为它们处理线程的方式很特殊。例如,Swing 客户机可能希望 Java 虚拟机中的所有线程使用相同的安全上下文。`SecurityContextHolder`可以在启动时配置一个策略,以指定如何存储上下文。对于独立的应用程序,你将使用`SecurityContextHolder.MODE_GLOBAL`策略。其他应用程序可能希望安全线程生成的线程也具有相同的安全标识。这是通过使用`SecurityContextHolder.MODE_INHERITABLETHREADLOCAL`实现的。你可以通过两种方式从默认的`SecurityContextHolder.MODE_THREADLOCAL`更改模式。第一种是设置一个系统属性,第二种是在`SecurityContextHolder`上调用一个静态方法。大多数应用程序不需要更改默认设置,但是如果需要更改,请查看`SecurityContextHolder`的 Javadoc 以了解更多信息。 ## SecurityContext [`SecurityContext`](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/core/context/securitycontext.html)是从[SecurityContextholder](#servlet-authentication-securitycontextholder)中获得的。`SecurityContext`包含一个[认证](#servlet-authentication-authentication)对象。 ## Authentication [`Authentication`](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/core/authentication.html)在 Spring 安全性中有两个主要目的: * 输入到[`AuthenticationManager`](# Servlet-authentication-authenticationManager),以提供用户提供的用于验证的凭据。在此场景中使用时,`isAuthenticated()`返回`false`。 * 表示当前经过身份验证的用户。当前的`Authentication`可以从[SecurityContext](#servlet-authentication-securitycontext)中得到。 `Authentication`包含: * `principal`-标识用户。当使用用户名/密码进行身份验证时,这通常是[`UserDetails`](passwords/user-details.html# Servlet-authentication-userdetails)的一个实例。 * `credentials`-通常是密码。在许多情况下,在用户通过身份验证以确保其不会泄漏后,将清除该漏洞。 * `authorities`-[`大企业`s](# Servlet-authentication-granted-authority)是授予用户的高级权限。几个例子是角色或范围。 ## GrantedAuthority [`GrantedAuthority`s](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/core/grantedauthority.html)是授予用户的高级权限。几个例子是角色或范围。 `GrantedAuthority`s 可以从[`Authentication.getAuthorities()`](# Servlet-authentication-authentication)方法获得。该方法提供了`Collection`的`GrantedAuthority`对象。a`GrantedAuthority`是授予委托人的权力,这并不奇怪。这种权威通常是“角色”,如`ROLE_ADMINISTRATOR`或`ROLE_HR_SUPERVISOR`。这些角色稍后将被配置用于 Web 授权、方法授权和域对象授权。 Spring 安全性的其他部分能够解释这些权威,并期望它们存在。当使用基于用户名/密码的身份验证时,`GrantedAuthority`s 通常是由[`UserDetailsService`]加载的(密码/user-details-service.html# Servlet-authentication-userDetailsService)。 通常`GrantedAuthority`对象是应用程序范围的权限。它们不是特定于给定域对象的。因此,你可能不需要`GrantedAuthority`来表示对`Employee`对象号 54 的权限,因为如果有数千个这样的权限,你将很快耗尽内存(或者至少导致应用程序花费很长时间来验证用户)。当然, Spring 安全性是专门为处理这一常见需求而设计的,但是你应该为此目的使用项目的域对象安全功能。 ## AuthenticationManager [`AuthenticationManager`](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/SpringFramework/security/Authentication/AuthenticationManager.html)是定义 Spring Security 的过滤器如何执行[认证](../../features/authentication/index.html#authentication)的 API。返回的[`Authentication`](# Servlet-authentication-authentication)然后由控制器在[SecurityContextholder](#servlet-authentication-securitycontextholder)上设置(即[ Spring Security 的`Filters`s](.../architecture.html# Servlet-security-gt)),该控制器调用<)。如果你没有与 * Spring Security 的`Filters`s* 集成,则可以直接设置`SecurityContextHolder`,并且不需要使用`AuthenticationManager`。 虽然`AuthenticationManager`的实现可以是任何东西,但最常见的实现是[`ProviderManager`](# Servlet-attentification-providermanager)。 ## ProviderManager [`ProviderManager`](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/authentication/providermanager.html)是最常用的[`AuthenticationManager`](# Servlet-authentication-authentication manager)的实现。`ProviderManager`委托给[`List`的[`身份验证提供者`s](# Servlet-authenticentication-authenticationprov 每个`AuthenticationProvider`都有机会指示身份验证应该成功、失败或指示它不能做出决定,并允许下游`AuthenticationProvider`进行决定。如果所有配置的`AuthenticationProvider`都不能进行身份验证,则使用`ProviderNotFoundException`进行身份验证将失败,这是一个特殊的`AuthenticationException`,表示`ProviderManager`未配置为支持传递到它的`Authentication`类型。 ![ProviderManager](https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/providermanager.png) 在实践中,每个`AuthenticationProvider`都知道如何执行特定类型的身份验证。例如,一个`AuthenticationProvider`可能能够验证用户名/密码,而另一个可能能够验证 SAML 断言。这允许每个`AuthenticationProvider`执行非常特定类型的身份验证,同时支持多种类型的身份验证,并且只公开单个`AuthenticationManager` Bean。 `ProviderManager`还允许配置一个可选的父`AuthenticationManager`,在没有`AuthenticationProvider`可以执行身份验证的情况下,可以查询该父`AuthenticationManager`。父可以是`AuthenticationManager`的任何类型,但它通常是`ProviderManager`的实例。 ![ProviderManager 母公司](https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/providermanager-parent.png) 实际上,多个`ProviderManager`实例可能共享同一个父`AuthenticationManager`。在多个[`SecurityFilterChain`](../architecture.html# Servlet-securityfilterchain)实例具有一些共同的身份验证(共享的父`AuthenticationManager`)的场景中,这种情况有些常见,但也存在不同的身份验证机制(不同的`ProviderManager`实例)。 ![ProviderManagers 母公司](https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/providermanagers-parent.png) 默认情况下,`ProviderManager`将尝试清除由成功的身份验证请求返回的`Authentication`对象中的任何敏感凭据信息。这可以防止像密码这样的信息在`HttpSession`中保留的时间超过必要的时间。 这可能会在使用用户对象的缓存时引起问题,例如,在无状态应用程序中提高性能。如果`Authentication`包含对缓存中某个对象的引用(例如`UserDetails`实例),并且它的凭据已被删除,那么它将不再可能根据缓存的值进行身份验证。如果你正在使用缓存,则需要考虑到这一点。一个明显的解决方案是首先在缓存实现中或在创建返回的`Authentication`对象的`AuthenticationProvider`中复制对象。或者,你可以在`ProviderManager`上禁用`eraseCredentialsAfterAuthentication`属性。有关更多信息,请参见[Javadoc](https://docs.spring.io/spring-security/site/docs/5.6.2/api/org/springframework/security/authentication/ProviderManager.html)。 ## AuthenticationProvider 多个[`AuthenticationProvider`s](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/authentication/authenticationprovider.html)可以被注入到[`ProviderManager`](# Servlet-authentication-providermanager)中。每个`AuthenticationProvider`执行特定类型的身份验证。例如,[`DaoAuthenticationProvider`](passwords/dao-authentication-provider.html# Servlet-authentication-daoauthenticationprovider)支持基于用户名/密码的身份验证,而`JwtAuthenticationProvider`支持对 JWT 令牌进行身份验证。 ## 使用`AuthenticationEntryPoint`请求凭据 [`AuthenticationEntryPoint`](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/web/authenticationentrypoint.html)用于发送请求客户端凭据的 HTTP 响应。 有时,客户端会主动地包含一些凭据,例如用户名/密码,以请求资源。在这些情况下, Spring 安全性不需要提供一个 HTTP 响应来请求来自客户机的凭据,因为它们已经包含在其中。 在其他情况下,客户端将向未经授权访问的资源发出未经身份验证的请求。在这种情况下,`AuthenticationEntryPoint`的实现用于从客户端请求凭据。`AuthenticationEntryPoint`实现可以执行[重定向到登录页面](passwords/form.html#servlet-authentication-form),用[WWW-认证](passwords/basic.html#servlet-authentication-basic)报头进行响应,等等。 ## 抽象处理过滤器 [`AbstractAuthenticationProcessingFilter`](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/web/authentication/abstractothenticationprocessingfilter.html)被用作验证用户凭据的基础`Filter`。在能够对凭据进行身份验证之前, Spring Security 通常使用[](# Servlet-Authentication-AuthenticationEntryPoint)请求凭据。 接下来,`AbstractAuthenticationProcessingFilter`可以对提交给它的任何身份验证请求进行身份验证。 ![抽象处理过滤器](https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/abstractauthenticationprocessingfilter.png) ![number 1](https://docs.spring.io/spring-security/reference/_images/icons/number_1.png)当用户提交其凭据时,`AbstractAuthenticationProcessingFilter`从`Authentication`创建一个[`Authentication`](# Servlet-authentication-authentication)来进行身份验证。创建的`Authentication`类型取决于`AbstractAuthenticationProcessingFilter`的子类。例如,[`UsernamePasswordAuthenticationFilter`](passwords/form.html# Servlet-authentication-usernamepasswordauthenticationfilter)从*用户 Name*和*密码*中创建一个`UsernamePasswordAuthenticationToken`,它们在`HttpServletRequest`中提交。 ![number 2](https://docs.spring.io/spring-security/reference/_images/icons/number_2.png)接下来,将[`Authentication`](# Servlet-authentication-authentication)传递到[`AuthenticationManager`](# Servlet-authentication-authenticationManager)中进行身份验证。 ![number 3](https://docs.spring.io/spring-security/reference/_images/icons/number_3.png)如果身份验证失败,则*失败* * [SecurityContextholder](#servlet-authentication-securitycontextholder)被清除。 * 调用`RememberMeServices.loginFail`。如果 Remember Me 没有配置,这是一个禁止操作。 * 调用`AuthenticationFailureHandler`。 ![number 4](https://docs.spring.io/spring-security/reference/_images/icons/number_4.png)如果身份验证成功,则*成功*。 * `SessionAuthenticationStrategy`被通知有一个新的登录。 * [认证](#servlet-authentication-authentication)设置在[SecurityContextholder](#servlet-authentication-securitycontextholder)上。稍后,`SecurityContextPersistenceFilter`将`SecurityContext`保存到`HttpSession`。 * 调用`RememberMeServices.loginSuccess`。如果 Remember Me 没有配置,这是一个禁止操作。 * `ApplicationEventPublisher`发布`InteractiveAuthenticationSuccessEvent`。 * 调用`AuthenticationSuccessHandler`。 [认证](index.html)[用户名/密码](passwords/index.html)