Servlet Authentication Architecture
ProviderManager AuthenticationProvider GrantedAuthority Request Credentials AuthenticationEntryPoint AbstractAuthenticationProcessingFilter SecurityContext Spring Security :description: 这篇概述描述了 Spring Security 中用于处理认证的架构组件。核心是 SecurityContextHolder,其中存储了已认证用户的详细信息。Authentication 接口定义了 Spring Security 的过滤器如何执行认证,AuthenticationManager 管理认证流程。ProviderManager 是 AuthenticationManager 的最常见实现,它使用 AuthenticationProvider 列表来执行特定类型的认证。Request Credentials with AuthenticationEntryPoint 用于向客户端请求凭证,AbstractAuthenticationProcessingFilter 提供了一个基本过滤器用于认证用户凭证。这篇文章提供了认证流程的高级视图,其中涉及这些组件如何协同工作来确定用户的认证状态。
此讨论在 Servlet Security: The Big Picture 的基础上进行扩展,以描述 Spring Security 中用于 Servlet 身份验证的主要架构组件。如果你需要具体流程来解释这些部分如何配合使用,请查看 Authentication Mechanism 的特定部分。
-
SecurityContextHolder -
SecurityContextHolder
是 Spring Security 存储用户 authenticated 详细信息的位置。 -
AuthenticationManager
- 从SecurityContextHolder
获取,包含当前经过身份验证用户的Authentication
。 -
Authentication - 可以作为
AuthenticationManager
的输入,提供用户提供的用于身份验证的凭据或来自SecurityContext
的当前用户。 -
GrantedAuthority - 在
Authentication
上授予主体的权限(即角色、范围等) -
AuthenticationManager - 定义 Spring Security 的过滤器如何执行 authentication 的 API。
-
ProviderManager -
AuthenticationManager
最常见的实现。 -
AuthenticationProvider - 由
ProviderManager
用于执行特定类型的身份验证。 -
Request Credentials with
AuthenticationEntryPoint
- 用于向客户端请求凭据(即重定向到登录页面、发送WWW-Authenticate
响应等) -
AbstractAuthenticationProcessingFilter - 用于身份验证的基础
Filter
。这也很好地了解了身份验证的高级流程以及各个部分如何协同工作。
SecurityContextHolder
Spring Security 认证模型的核心是 SecurityContextHolder
。它包含 SecurityContext。
SecurityContextHolder
是 Spring Security 存储谁 authenticated 的详细信息的位置。Spring Security 不关心如何填充 SecurityContextHolder
。如果它包含一个值,那么它将用作当前经过身份验证的用户。
指示用户已认证的最简单方法是直接设置 SecurityContextHolder
:
SecurityContextHolder
-
Java
-
Kotlin
SecurityContext context = SecurityContextHolder.createEmptyContext(); (1)
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER"); (2)
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context); (3)
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 并不关心在 SecurityContext 上设置了哪种类型的 Authentication 实现。在这里,我们使用 TestingAuthenticationToken ,因为它非常简单。更常见的生产场景是 UsernamePasswordAuthenticationToken(userDetails, password, authorities) 。 |
3 | 最后,我们在 SecurityContextHolder 上设置 SecurityContext 。Spring Security 使用此信息进行 authorization。 |
要获取已认证主体的相关信息,请访问 SecurityContextHolder
。
-
Java
-
Kotlin
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
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
。
由于某些应用程序与线程的工作方式很具体,因此并不完全适合使用 ThreadLocal
。例如,Swing 客户端可能希望 Java Virtual Machine 中的所有线程使用相同的安全上下文。你可以在启动时使用 strategy 为 SecurityContextHolder
配置,以指定如何存储上下文。对于独立应用程序,你将使用 SecurityContextHolder.MODE_GLOBAL
strategy。其他应用程序可能希望由安全线程生成的线程也假定相同安全标识。你可以使用 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
实现此目的。你可以通过两种方式更改模式,即从默认 SecurityContextHolder.MODE_THREADLOCAL
更改。第一种方法是设置系统属性。第二种方法是在 SecurityContextHolder
中调用一个 static 方法。大多数应用程序不需要改变默认设置。但是,如果你有需要,请查看 SecurityContextHolder
的 JavaDoc 以了解更多信息。
SecurityContext
从 SecurityContextHolder获得 {security-api-url}org/springframework/security/core/context/SecurityContext.html[SecurityContext
]。`SecurityContext`包含一个 Authentication对象。
Authentication
在 Spring 安全中,{security-api-url}org/springframework/security/core/Authentication.html[Authentication
] 接口有两个主要用途:
-
提供用户提供的用来身份验证的凭据时输入到 <<`AuthenticationManager`,servlet-authentication-authenticationmanager>>。当在该场景中使用时,
isAuthenticated()
返回false
。 -
表示当前经过身份验证的用户。您可以从 SecurityContext 获取当前
Authentication
。
Authentication
包含:
-
principal
:识别用户。使用用户名/密码进行身份验证时,这通常是UserDetails
的一个实例。 -
credentials
:通常是一个密码。在许多情况下,它会在用户通过身份认证后被清除,以确保其未泄漏。 -
authorities
:<<`GrantedAuthority`,servlet-authentication-granted-authority>> 实例是用户获得的高级别权限。有两个示例是角色和作用域。
GrantedAuthority
{security-api-url}org/springframework/security/core/GrantedAuthority.html[GrantedAuthority
] 实例是授予用户的高级权限。两个示例是角色和作用域。
你可以从 <<`Authentication.getAuthorities(),servlet-authentication-authentication>> method.
This method provides a `Collection
of GrantedAuthority
objects>> 中获取 GrantedAuthority
实例。GrantedAuthority
不出所料,它是一项授予负责人的权限。此类权限通常是 “roles”,例如 ROLE_ADMINISTRATOR
或 ROLE_HR_SUPERVISOR
。这些角色稍后将配置用于 Web 授权、方法授权和域对象授权。Spring Security 的其他部分会解释这些权限并希望它们存在。使用基于用户名/密码的身份验证时, GrantedAuthority
实例通常由 UserDetailsService
加载。
通常,GrantedAuthority
对象是应用程序范围的权限。它们不特定于给定的域对象。因此,你不太可能有一个 GrantedAuthority
来表示对权限为 Employee
对象 54 的权限,因为如果存在数千个这样的权限,你将很快耗尽内存(或至少导致应用程序花很长时间来认证用户)。当然,Spring Security 的设计明确满足此常见要求,但你应该使用项目的域对象安全功能来解决此目的。
AuthenticationManager
{security-api-url}org/springframework/security/authentication/AuthenticationManager.html[AuthenticationManager
]是定义 Spring Security 的过滤器 authentication执行方式的 API。返回的 <<`Authentication`,servlet-authentication-authentication>> 随后由触发了 AuthenticationManager`的控制器(即,Spring Security’s `Filters
instances)设置在 SecurityContextHolder上。如果你未与 Spring Security 的 Filters`实例集成,你可以直接设置 `SecurityContextHolder`且不需要使用 `AuthenticationManager
。
虽然 AuthenticationManager
的实现可以是任何东西,但最常见的实现是 <<`ProviderManager`,servlet-authentication-providermanager>>。
ProviderManager
{security-api-url}org/springframework/security/authentication/ProviderManager.html[ProviderManager
] 是 <<`AuthenticationManager`,servlet-authentication-authenticationmanager>> 最常用的实现。ProviderManager`委托给 <<`AuthenticationProvider
,servlet-authentication-authenticationprovider>> 实例的 List
。每个 AuthenticationProvider`都有一个机会指出身份验证应成功、失败,或者指出它无法做出决定并允许下游 `AuthenticationProvider`决定。如果未配置的 `AuthenticationProvider`实例均无法进行身份验证,则身份验证会因出现 `ProviderNotFoundException`而失败,`ProviderNotFoundException`是一个特殊 `AuthenticationException
,指出未配置 `ProviderManager`以支持传入的 `Authentication`类型。
在实践中,每个 AuthenticationProvider
知道如何执行特定类型的身份验证。例如,一个 AuthenticationProvider
可能会验证用户名/密码,而另一个可能会验证 SAML 断言。这能让每个 AuthenticationProvider
在支持多种身份验证类型且仅公开一个 AuthenticationManager
bean 的情况下,执行非常具体的身份验证类型。
ProviderManager
还允许配置一个可选的父 AuthenticationManager
,在没有 AuthenticationProvider
可以执行身份验证的情况下咨询它。父元素可以是任何类型的 AuthenticationManager
,但它通常是 ProviderManager
的一个实例。
事实上,多个 ProviderManager
实例可以共享相同的父 AuthenticationManager
。这在有多个 @{6} 实例共享一些相同的身份验证(共享父 AuthenticationManager
),但身份验证机制又不相同的(不同的 ProviderManager
实例)的情况下比较常见。
默认情况下,ProviderManager
尝试清除成功身份验证请求返回的 Authentication
对象中的任何敏感凭据信息。这可以防止诸如密码之类的信息在 HttpSession
中保留的时间超过必要时间。
当使用用户对象的缓存时,可能会导致问题,例如,提高无状态应用程序的性能。如果 `Authentication`包含对缓存中对象的引用(例如 `UserDetails`实例)并且其凭据已删除,则无法再对缓存值进行身份验证。如果使用缓存,则需要考虑此问题。一个显而易见的解决方案是首先对对象进行复制,无论是在缓存实现还是在创建返回 `Authentication`对象的 `AuthenticationProvider`中。或者,可以在 `ProviderManager`上禁用 `eraseCredentialsAfterAuthentication`属性。请参阅 {security-api-url}org/springframework/security/authentication/ProviderManager.html[ProviderManager] 类的 Javadoc。
AuthenticationProvider
你可以注入多个 {security-api-url}org/springframework/security/authentication/AuthenticationProvider.html[AuthenticationProvider`s] instances into <<`ProviderManager
,servlet-authentication-providermanager>>。每个 AuthenticationProvider`会执行特定类型的认证。例如,`DaoAuthenticationProvider
支持基于用户名/密码的认证,而 `JwtAuthenticationProvider`支持验证 JWT 令牌。
Request Credentials with AuthenticationEntryPoint
{security-api-url}org/springframework/security/web/AuthenticationEntryPoint.html[AuthenticationEntryPoint
] 用于发送一个 HTTP 响应,该响应请求客户端提供凭据。
有时候,客户端主动包含凭证(例如用户名和密码)来请求资源。在这些情况下,Spring Security 不需要提供要求客户端提供凭据的 HTTP 响应,因为它们已经包含在内了。
在其他情况下,客户端会向其无权访问的资源发出未经验证的请求。在这种情况下,会使用 `AuthenticationEntryPoint`的实现向客户端请求凭证。该 `AuthenticationEntryPoint`实现可能执行 redirect to a log in page,使用 WWW-Authenticate标题做出响应或采取其他操作。
AbstractAuthenticationProcessingFilter
{security-api-url}org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html[AbstractAuthenticationProcessingFilter
] 用作用户凭据的身份验证的基 Filter
。在对凭据进行身份验证之前,Spring 安全通常使用 <<`AuthenticationEntryPoint`,servlet-authentication-authenticationentrypoint>> 请求凭据。
接下来,AbstractAuthenticationProcessingFilter
可以对提交给它的任何身份验证请求进行身份验证。
当用户提交其凭证时,AbstractAuthenticationProcessingFilter`会根据 `HttpServletRequest`创建一个待认证的 <<`Authentication
,servlet-authentication-authentication>>。创建的 Authentication`类型取决于 `AbstractAuthenticationProcessingFilter`的子类。例如,`UsernamePasswordAuthenticationFilter
根据在 HttpServletRequest`中提交的 _username_和 _password_创建一个 `UsernamePasswordAuthenticationToken
。
接下来,将 <<`Authentication`,servlet-authentication-authentication>> 传递到 <<`AuthenticationManager`,servlet-authentication-authenticationmanager>> 进行身份验证。
如果身份验证失败,则_Failure_。
-
调用
RememberMeServices.loginFail
。如果未配置记住我,则这是一个无操作。请参阅 {security-api-url}org/springframework/security/web/authentication/rememberme/package-frame.html[rememberme
] 包。 -
调用
AuthenticationFailureHandler
。请参阅 {security-api-url}org/springframework/security/web/authentication/AuthenticationFailureHandler.html[AuthenticationFailureHandler
] 接口。
如果身份验证成功,则 Success
-
通知
SessionAuthenticationStrategy
有新登录。请参阅 {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[SessionAuthenticationStrategy
] 接口。 -
Authentication 在 SecurityContextHolder 上设置。稍后,如果你需要保存
SecurityContext
,以便它可以在将来的请求中自动设置,则必须显式调用SecurityContextRepository#saveContext
。请参阅 {security-api-url}org/springframework/security/web/context/SecurityContextHolderFilter.html[SecurityContextHolderFilter
] 类。 -
RememberMeServices.loginSuccess`被调用。如果未配置自动登录,则这是一个空操作。请参阅 {security-api-url}org/springframework/security/web/authentication/rememberme/package-frame.html[`rememberme
] 包。 -
ApplicationEventPublisher
publishes anInteractiveAuthenticationSuccessEvent
. -
AuthenticationSuccessHandler`被调用。请参阅 {security-api-url}org/springframework/security/web/authentication/AuthenticationSuccessHandler.html[`AuthenticationSuccessHandler
] 接口。