# Servlet 身份验证体系结构
本讨论扩展到Servlet Security: The Big Picture,以描述在 Servlet 身份验证中使用的 Spring 安全性的主要体系结构组件。如果你需要具体的流程来解释这些部分是如何组合在一起的,请查看认证机制特定部分。
SecurityContextholder-
SecurityContextholder
是 Spring 安全性存储谁是已认证的详细信息的地方。SecurityContext-是从
SecurityContextHolder
获得的,并且包含当前已验证用户的认证
。认证-可以是对
身份验证管理器
的输入,以提供用户已提供的用于验证的凭据或来自SecurityContext
的当前用户。大企业-在
Authentication
上授予主体的权限(即角色、范围等)ProviderManager-
AuthenticationManager
的最常见的实现。身份验证提供者-用于
ProviderManager
执行特定类型的身份验证。[带有
AuthenticationEntryPoint
的请求凭据](# Servlet-authentication-authentryPoint)-用于从客户端请求凭据(即重定向到登录页面,发送WWW-Authenticate
响应等)抽象处理过滤器-用于身份验证的基
Filter
。这也为身份验证的高级流程以及各个部分如何协同工作提供了一个很好的思路。
# SecurityContextHolder
hi Servlet/身份验证/体系结构
Spring 安全性的身份验证模型的核心是SecurityContextHolder
。它包含SecurityContext。
在SecurityContextHolder
中, Spring 安全性存储了谁是已认证的详细信息。 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 将对授权使用此信息。 |
如果你希望获得有关经过身份验证的主体的信息,可以通过访问SecurityContextHolder
来实现。
例 2。访问当前通过身份验证的用户
爪哇
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> 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确保始终清除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中获得的。SecurityContext
包含一个认证对象。
# 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中得到。
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
对象。aGrantedAuthority
是授予委托人的权力,这并不奇怪。这种权威通常是“角色”,如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 的过滤器如何执行认证的 API。返回的[Authentication
](# Servlet-authentication-authentication)然后由控制器在SecurityContextholder上设置(即[ Spring Security 的Filters
s](.../architecture.html# Servlet-security-gt)),该控制器调用<<r="99"/>)。如果你没有与 * 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
类型。
在实践中,每个AuthenticationProvider
都知道如何执行特定类型的身份验证。例如,一个AuthenticationProvider
可能能够验证用户名/密码,而另一个可能能够验证 SAML 断言。这允许每个AuthenticationProvider
执行非常特定类型的身份验证,同时支持多种类型的身份验证,并且只公开单个AuthenticationManager
Bean。
ProviderManager
还允许配置一个可选的父AuthenticationManager
,在没有AuthenticationProvider
可以执行身份验证的情况下,可以查询该父AuthenticationManager
。父可以是AuthenticationManager
的任何类型,但它通常是ProviderManager
的实例。
实际上,多个ProviderManager
实例可能共享同一个父AuthenticationManager
。在多个[SecurityFilterChain
](../architecture.html# Servlet-securityfilterchain)实例具有一些共同的身份验证(共享的父AuthenticationManager
)的场景中,这种情况有些常见,但也存在不同的身份验证机制(不同的ProviderManager
实例)。
默认情况下,ProviderManager
将尝试清除由成功的身份验证请求返回的Authentication
对象中的任何敏感凭据信息。这可以防止像密码这样的信息在HttpSession
中保留的时间超过必要的时间。
这可能会在使用用户对象的缓存时引起问题,例如,在无状态应用程序中提高性能。如果Authentication
包含对缓存中某个对象的引用(例如UserDetails
实例),并且它的凭据已被删除,那么它将不再可能根据缓存的值进行身份验证。如果你正在使用缓存,则需要考虑到这一点。一个明显的解决方案是首先在缓存实现中或在创建返回的Authentication
对象的AuthenticationProvider
中复制对象。或者,你可以在ProviderManager
上禁用eraseCredentialsAfterAuthentication
属性。有关更多信息,请参见Javadoc (opens new window)。
# 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
实现可以执行重定向到登录页面,用WWW-认证报头进行响应,等等。
# 抽象处理过滤器
[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
可以对提交给它的任何身份验证请求进行身份验证。
当用户提交其凭据时,
AbstractAuthenticationProcessingFilter
从Authentication
创建一个[Authentication
](# Servlet-authentication-authentication)来进行身份验证。创建的Authentication
类型取决于AbstractAuthenticationProcessingFilter
的子类。例如,[UsernamePasswordAuthenticationFilter
](passwords/form.html# Servlet-authentication-usernamepasswordauthenticationfilter)从用户 Name和密码中创建一个UsernamePasswordAuthenticationToken
,它们在HttpServletRequest
中提交。
接下来,将[
Authentication
](# Servlet-authentication-authentication)传递到[AuthenticationManager
](# Servlet-authentication-authenticationManager)中进行身份验证。
如果身份验证失败,则失败
调用
RememberMeServices.loginFail
。如果 Remember Me 没有配置,这是一个禁止操作。调用
AuthenticationFailureHandler
。
如果身份验证成功,则成功。
SessionAuthenticationStrategy
被通知有一个新的登录。认证设置在SecurityContextholder上。稍后,
SecurityContextPersistenceFilter
将SecurityContext
保存到HttpSession
。调用
RememberMeServices.loginSuccess
。如果 Remember Me 没有配置,这是一个禁止操作。ApplicationEventPublisher
发布InteractiveAuthenticationSuccessEvent
。调用
AuthenticationSuccessHandler
。
← 认证 用户名/密码身份验证 →