Spring Security 认证模型的核心是 SecurityContextHolder 。它包含 SecurityContext。

SecurityContextHolder
SecurityContextHolder 是 Spring Security 存储已认证用户信息的地方。Spring Security 不关心 SecurityContextHolder 是如何被填充的。如果其中包含值,该值将被用作当前已认证的用户。
表示用户已认证的最简单方式是直接设置 SecurityContextHolder :
SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
Authentication authentication = new TestingAuthenticationToken(
"username", "password", "ROLE_USER");
emptyContext.setAuthentication(authentication);
SecurityContextHolder.setContext(emptyContext);
这里通过
SecurityContextHolder.createEmptyContext方法创建一个新的空白 Context,而不是通过SecurityContextHolder.getContext获取,是为了避免多线程之间的资源竞争。
如果要获取认证信息:
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String name = authentication.getName();
Object principal = authentication.getPrincipal();
log.info("name:{},principal:{}", name, principal);
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
默认情况下, SecurityContextHolder 使用 ThreadLocal 来存储 SecurityContext。如果有特殊需要,可以通过在启动时配置 SecurityContextHolder 的模式来改变这一点。
SecurityContext
SecurityContext被包含在SecurityContextHolder中,它里边包含一个Authentication对象。
Authentication
Authentication 接口在 Spring Security 中主要有两个用途:
-
作为
AuthenticationManager的输入,以提供用户用于认证的凭证。在此场景下,isAuthenticated()返回false。 -
表示当前已认证的用户。您可以从 SecurityContext 中获取当前
Authentication。
Authentication 包含:
-
principal:标识用户。当使用用户名/密码进行认证时,这通常是UserDetails的一个实例。 -
credentials: 通常是一个密码。在许多情况下,用户认证完成后会清除该信息,以确保不会泄露。 -
authorities:GrantedAuthority实例是用户被授予的高级权限。两个例子是角色和作用域。
它还配备了一个 AdditionalRequiredFactorsBuilder ,允许你修改现有的 Authentication 实例,并可能将其与另一个实例合并。这在某些场景中非常有用,例如从一个认证步骤(如表单登录)获取权限,并将其应用到另一个认证步骤(如一次性令牌登录)中,如下所示:
Authentication lastestResult = authenticationManager.authenticate(authenticationRequest);
Authentication previousResult = SecurityContextHolder.getContext().getAuthentication();
if (previousResult != null && previousResult.isAuthenticated()) {
lastestResult = lastestResult.toBuilder()
.authorities((a) -> a.addAll(previous.getAuthorities()))
.build();
}
GrantedAuthority
GrantedAuthority 实例是用户被授予的高级权限。两个例子是角色和作用域。
你可以从 Authentication.getAuthorities() 方法中获取 GrantedAuthority 实例。此方法提供一个 Collection 的 GrantedAuthority 对象集合。一个 GrantedAuthority ,不出所料,是授予主体的权限。这些权限通常是“角色”,例如 ROLE_ADMINISTRATOR 或 ROLE_HR_SUPERVISOR 。这些角色之后会被配置用于 Web 授权、方法授权和领域对象授权。Spring Security 的其他部分会解释这些权限,并期望它们存在。当使用基于用户名/密码的认证时, GrantedAuthority 实例通常由 UserDetailsService 加载。
AuthenticationManager
AuthenticationManager 是定义 Spring Security 的 Filter 如何执行认证的 API。由调用 AuthenticationManager 的控制器(即 Spring Security 的 Filters 实例)返回的 Authentication 将被设置到 SecurityContextHolder 中。如果你不与 Spring Security 的 Filters 实例集成,你可以直接设置 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 可以是任何类型的 AuthenticationManager ,但通常是一个 ProviderManager 的实例。

事实上,多个 ProviderManager 实例可能共享同一个父 AuthenticationManager 。这在存在多个 SecurityFilterChain 实例且它们有一些共同的认证机制(共享的父 AuthenticationManager )的情况下是相当常见的,但它们也可能有不同的认证方式(不同的 ProviderManager 实例)。

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提交的用户名和密码创建一个UsernamePasswordAuthenticationToken。 -
接下来,
Authentication被传递到AuthenticationManager进行认证。 -
如果认证失败,那么失败。
-
SecurityContextHolder 被清空。
-
RememberMeServices.loginFail被调用。如果未配置 remember me,此操作无任何效果。请参见 rememberme 包。 -
AuthenticationFailureHandler被调用。请参见AuthenticationFailureHandler接口。
-
-
如果认证成功,则 Success。
-
SessionAuthenticationStrategy被通知有新的登录。请参见SessionAuthenticationStrategy接口。 -
SecurityContextHolder 中已认证的
Authentication会被加载,并将其权限添加到返回的 Authentication 中。 -
认证信息被设置在 SecurityContextHolder 中。之后,如果你需要保存
SecurityContext以便在未来的请求中自动设置,必须显式调用SecurityContextRepository#saveContext。参见SecurityContextHolderFilter类。 -
RememberMeServices.loginSuccess被调用。如果未配置 remember me,此操作无任何效果。请参见 rememberme 包。 -
ApplicationEventPublisher发布一个InteractiveAuthenticationSuccessEvent。 -
AuthenticationSuccessHandler被调用。请参见AuthenticationSuccessHandler接口。
-
The End.

文章评论