Spring Security 学习笔记 3:认证模型

Spring Security 学习笔记 3:认证模型

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

SecurityContextHolder

SecurityContextHolder 是 Spring Security 存储已认证用户信息的地方。Spring Security 不关心 SecurityContextHolder 是如何被填充的。如果其中包含值,该值将被用作当前已认证的用户。

表示用户已认证的最简单方式是直接设置 SecurityContextHolder :

java 复制代码
SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
Authentication authentication = new TestingAuthenticationToken(
        "username", "password", "ROLE_USER");
emptyContext.setAuthentication(authentication);
SecurityContextHolder.setContext(emptyContext);

这里通过SecurityContextHolder.createEmptyContext方法创建一个新的空白 Context,而不是通过SecurityContextHolder.getContext获取,是为了避免多线程之间的资源竞争。

如果要获取认证信息:

java 复制代码
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 : 通常是一个密码。在许多情况下,用户认证完成后会清除该信息,以确保不会泄露。
  • authoritiesGrantedAuthority 实例是用户被授予的高级权限。两个例子是角色和作用域。

它还配备了一个 AdditionalRequiredFactorsBuilder ,允许你修改现有的 Authentication 实例,并可能将其与另一个实例合并。这在某些场景中非常有用,例如从一个认证步骤(如表单登录)获取权限,并将其应用到另一个认证步骤(如一次性令牌登录)中,如下所示:

java 复制代码
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 实例。此方法提供一个 CollectionGrantedAuthority 对象集合。一个 GrantedAuthority ,不出所料,是授予主体的权限。这些权限通常是"角色",例如 ROLE_ADMINISTRATORROLE_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

ProviderManagerAuthenticationManager 最常用的实现。 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 可以验证提交给它的任何身份验证请求。

  1. 当用户提交其凭证时, AbstractAuthenticationProcessingFilter 会根据 HttpServletRequest 创建一个 Authentication 用于认证。所创建的 Authentication 类型取决于 AbstractAuthenticationProcessingFilter 的子类。例如, UsernamePasswordAuthenticationFilter 会从 HttpServletRequest 提交的用户名和密码创建一个 UsernamePasswordAuthenticationToken
  2. 接下来, Authentication 被传递到 AuthenticationManager 进行认证。
  3. 如果认证失败,那么失败。
    • SecurityContextHolder 被清空。
    • RememberMeServices.loginFail 被调用。如果未配置 remember me,此操作无任何效果。请参见 rememberme 包。
    • AuthenticationFailureHandler 被调用。请参见 AuthenticationFailureHandler 接口。
  4. 如果认证成功,则 Success。
    • SessionAuthenticationStrategy 被通知有新的登录。请参见 SessionAuthenticationStrategy 接口。
    • SecurityContextHolder 中已认证的 Authentication 会被加载,并将其权限添加到返回的 Authentication 中。
    • 认证信息被设置在 SecurityContextHolder 中。之后,如果你需要保存 SecurityContext 以便在未来的请求中自动设置,必须显式调用 SecurityContextRepository#saveContext 。参见 SecurityContextHolderFilter 类。
    • RememberMeServices.loginSuccess 被调用。如果未配置 remember me,此操作无任何效果。请参见 rememberme 包。
    • ApplicationEventPublisher 发布一个 InteractiveAuthenticationSuccessEvent
    • AuthenticationSuccessHandler 被调用。请参见 AuthenticationSuccessHandler 接口。

The End.

参考资料

相关推荐
羊群智妍4 小时前
2026 AI搜索流量密码:免费GEO监测工具,优化效果看得见
笔记·百度·微信·facebook·新浪微博
阿蒙Amon5 小时前
TypeScript学习-第10章:模块与命名空间
学习·ubuntu·typescript
AI绘画哇哒哒5 小时前
【干货收藏】深度解析AI Agent框架:设计原理+主流选型+项目实操,一站式学习指南
人工智能·学习·ai·程序员·大模型·产品经理·转行
戌中横6 小时前
JavaScript——预解析
前端·javascript·学习
●VON6 小时前
React Native for OpenHarmony:2048 小游戏的开发与跨平台适配实践
javascript·学习·react native·react.js·von
山岚的运维笔记7 小时前
SQL Server笔记 -- 第18章:Views
数据库·笔记·sql·microsoft·sqlserver
ZH15455891317 小时前
Flutter for OpenHarmony Python学习助手实战:自动化脚本开发的实现
python·学习·flutter
xcLeigh7 小时前
Python入门:Python3 requests模块全面学习教程
开发语言·python·学习·模块·python3·requests
xcLeigh7 小时前
Python入门:Python3 statistics模块全面学习教程
开发语言·python·学习·模块·python3·statistics
GHL2842710907 小时前
分析式AI学习
人工智能·学习·ai编程