Servlet 身份验证架构
本文档基于 Servlet 安全:全局视图 的基础上,扩展描述了 Spring Security 在 Servlet 身份验证中使用的主要架构组件。如果你需要具体的流程来解释这些组件如何协同工作,请查看 身份验证机制 特定章节。
-
SecurityContextHolder -
SecurityContextHolder
是 Spring Security 存储已认证主体详细信息的地方。 -
SecurityContext - 从
SecurityContextHolder
获取,包含当前已认证用户的Authentication
。 -
Authentication - 可以作为
AuthenticationManager
的输入,提供用户用于认证的凭证;也可以是SecurityContext
中当前用户的信息。 -
GrantedAuthority - 授予给
Authentication
上的 principal 的权限(例如角色、作用域等) -
AuthenticationManager - 定义 Spring Security 的 Filter 如何执行 身份验证 的 API。
-
ProviderManager - 是最常用的
AuthenticationManager
实现。 -
AuthenticationProvider - 由
ProviderManager
使用,用于执行特定类型的身份验证。 -
使用
AuthenticationEntryPoint
请求凭证 - 用于向客户端请求凭证(例如重定向到登录页、发送WWW-Authenticate
响应等) -
AbstractAuthenticationProcessingFilter - 一个基础的
Filter
,用于身份验证。这也很好地展示了身份验证的高级流程以及各部分如何协同工作。
SecurityContextHolder
Spring Security 身份验证模型的核心是 SecurityContextHolder
. 它包含 SecurityContext.

SecurityContextHolder
是 Spring Security 存储已认证用户详细信息的地方。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 对象。Spring Security 不关心 SecurityContext 上设置的 Authentication 实现是什么类型。这里,我们使用 TestingAuthenticationToken ,因为它非常简单。更常见的生产场景是 UsernamePasswordAuthenticationToken(userDetails, password, authorities) . |
3 | 最后,我们将 SecurityContext 设置到 SecurityContextHolder 上。Spring Security 使用这些信息进行 授权. |
要获取有关已认证主体的信息,请访问 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 虚拟机中的所有线程都使用相同的安全上下文。你可以在启动时配置 SecurityContextHolder
使用某种策略来指定你希望如何存储上下文。对于独立应用程序,你会使用 SecurityContextHolder.MODE_GLOBAL
策略。其他应用程序可能希望安全线程派生的线程也继承相同的安全身份。你可以通过使用 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
来实现这一点。你可以通过两种方式更改默认的 SecurityContextHolder.MODE_THREADLOCAL
模式。第一种是设置系统属性。第二种是调用 SecurityContextHolder
上的静态方法。大多数应用程序不需要更改默认设置。但是,如果你确实需要更改,请查看 SecurityContextHolder
的 JavaDoc 以了解更多信息。
SecurityContext
SecurityContext
从 SecurityContextHolder 获取。SecurityContext
包含一个 Authentication 对象。
Authentication
Authentication
接口在 Spring Security 中有两个主要用途:
-
作为
AuthenticationManager
的输入,提供用户用于认证的凭证。在这种情况下,isAuthenticated()
返回false
. -
代表当前已认证的用户。你可以从 SecurityContext 获取当前
Authentication
.
Authentication
包含:
-
principal
: 标识用户。使用用户名/密码进行认证时,这通常是UserDetails
的一个实例。 -
credentials
: 通常是密码。在许多情况下,用户认证后会清除此信息,以确保其不被泄露。 -
authorities
:GrantedAuthority
实例是授予用户的顶级权限。两个例子是角色和作用域。
GrantedAuthority
GrantedAuthority
实例是授予用户的顶级权限。两个例子是角色和作用域。
你可以从 Authentication.getAuthorities()
方法获取 GrantedAuthority
实例。此方法提供一个 GrantedAuthority
对象集合。GrantedAuthority
顾名思义,是授予 principal 的权限。这些权限通常是“角色”,例如 ROLE_ADMINISTRATOR
或 ROLE_HR_SUPERVISOR
. 这些角色稍后会用于配置 Web 授权、方法授权和领域对象授权。Spring Security 的其他部分会解析这些权限,并期望它们存在。使用基于用户名/密码的身份验证时,GrantedAuthority
实例通常由 UserDetailsService
加载。
通常,GrantedAuthority
对象是应用程序范围内的权限。它们不特定于某个领域对象。因此,你不太可能会有一个 GrantedAuthority
来表示对员工对象编号 54 的权限,因为如果这样的权限有数千个,你很快就会耗尽内存(或者至少会导致应用程序认证用户所需时间过长)。当然,Spring Security 明确设计了处理这一常见需求的功能,但你应该转而使用该项目的领域对象安全能力来实现此目的。
AuthenticationManager
AuthenticationManager
是定义 Spring Security 的 Filter 如何执行 身份验证 的 API。AuthenticationManager
返回的 Authentication
随后由调用 AuthenticationManager
的控制器(即 Spring Security 的 Filter 实例)设置到 SecurityContextHolder 上。如果你没有集成 Spring Security 的 Filter 实例,你可以直接设置 SecurityContextHolder
,而不需要使用 AuthenticationManager
.
虽然 AuthenticationManager
的实现可以是任何类型,但最常见的实现是 ProviderManager
.
ProviderManager
ProviderManager
是 AuthenticationManager
最常用的实现。ProviderManager
委托给一个 AuthenticationProvider
列表。每个 AuthenticationProvider
都有机会表明身份验证应该成功、失败,或者表明它无法做出决定,并允许下游的 AuthenticationProvider
做出决定。如果配置的所有 AuthenticationProvider
实例都无法进行身份验证,则身份验证将失败并抛出 ProviderNotFoundException
,这是一个特殊的 AuthenticationException
,表示 ProviderManager
没有配置来支持传递给它的 Authentication
类型。

实际上,每个 AuthenticationProvider
都知道如何执行特定类型的身份验证。例如,一个 AuthenticationProvider
可能能够验证用户名/密码,而另一个可能能够验证 SAML 断言。这使得每个 AuthenticationProvider
可以执行非常特定类型的身份验证,同时支持多种身份验证类型并仅暴露一个 AuthenticationManager
bean。
ProviderManager
还允许配置一个可选的父级 AuthenticationManager
,当没有 AuthenticationProvider
可以执行身份验证时,会咨询该父级。父级可以是任何类型的 AuthenticationManager
,但它通常是 ProviderManager
的一个实例。

实际上,多个 ProviderManager
实例可以共享同一个父级 AuthenticationManager
. 这在存在多个 SecurityFilterChain
实例,并且它们有一些共同的身份验证(共享的父级 AuthenticationManager
),但也有不同的身份验证机制(不同的 ProviderManager
实例)的场景中很常见。

默认情况下,ProviderManager
会尝试清除成功身份验证请求返回的 Authentication
对象中的任何敏感凭证信息。这可以防止密码等信息在 HttpSession
中不必要地长时间保留。
|
当你在无状态应用程序中使用用户对象缓存(例如为了提高性能)时,这可能会导致问题。如果 Authentication
包含对缓存中对象(例如 UserDetails
实例)的引用,并且其凭证被移除,那么将无法再使用缓存的值进行身份验证。如果你使用缓存,你需要考虑这一点。一个明显的解决方案是,要么在缓存实现中,要么在创建返回的 Authentication
对象的 AuthenticationProvider
中,首先复制该对象。或者,你可以禁用 ProviderManager
上的 eraseCredentialsAfterAuthentication
属性。请参阅 ProviderManager
类的 Javadoc。
AuthenticationProvider
你可以将多个 AuthenticationProvider
实例注入到 ProviderManager
中。每个 AuthenticationProvider
执行特定类型的身份验证。例如,DaoAuthenticationProvider
支持基于用户名/密码的身份验证,而 JwtAuthenticationProvider
支持验证 JWT 令牌。
使用 AuthenticationEntryPoint
请求凭证
AuthenticationEntryPoint
用于发送一个向客户端请求凭证的 HTTP 响应。
有时,客户端会主动包含凭证(例如用户名和密码)来请求资源。在这种情况下,Spring Security 不需要提供请求客户端凭证的 HTTP 响应,因为凭证已经包含在请求中。
在其他情况下,客户端向他们未被授权访问的资源发出未认证请求。在这种情况下,使用 AuthenticationEntryPoint
的实现来向客户端请求凭证。AuthenticationEntryPoint
实现可能会执行 重定向到登录页,发送 WWW-Authenticate 头部,或采取其他行动。
AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter
用作认证用户凭证的基础 Filter
。在凭证被认证之前,Spring Security 通常会使用 AuthenticationEntryPoint
请求凭证。
接下来,AbstractAuthenticationProcessingFilter
可以认证提交给它的任何身份验证请求。

当用户提交凭证时,
AbstractAuthenticationProcessingFilter
从 HttpServletRequest
创建一个 Authentication
对象进行认证。创建的 Authentication
类型取决于 AbstractAuthenticationProcessingFilter
的子类。例如,UsernamePasswordAuthenticationFilter
从 HttpServletRequest
中提交的 *username* 和 *password* 创建一个 UsernamePasswordAuthenticationToken
.
接下来,
Authentication
被传递到 AuthenticationManager
中进行认证。
如果身份验证失败,则执行 失败。
-
SecurityContextHolder
被清除。 -
RememberMeServices.loginFail
被调用。如果没有配置 remember me,则这是一个无操作。请参阅 rememberme 包。 -
AuthenticationFailureHandler
被调用。请参阅AuthenticationFailureHandler
接口。
如果身份验证成功,则执行 成功。
-
SessionAuthenticationStrategy
通知新登录。请参阅SessionAuthenticationStrategy
接口。 -
Authentication
被设置到 SecurityContextHolder 上。稍后,如果你需要保存SecurityContext
以便在未来的请求中自动设置,必须显式调用SecurityContextRepository#saveContext
. 请参阅SecurityContextHolderFilter
类。 -
RememberMeServices.loginSuccess
被调用。如果没有配置 remember me,则这是一个无操作。请参阅 rememberme 包。 -
ApplicationEventPublisher
发布一个InteractiveAuthenticationSuccessEvent
. -
AuthenticationSuccessHandler
被调用。请参阅AuthenticationSuccessHandler
接口。