对于最新的稳定版本,请使用 Spring Security 6.4.1! |
Servlet 身份验证体系结构
本讨论扩展了 Servlet 安全性:大局 来描述 Servlet 身份验证中使用的 Spring Security 的主要体系结构组件。 如果您需要具体的流程来解释这些部分如何组合在一起,请查看 身份验证机制 特定部分。
-
SecurityContextHolder - 该
SecurityContextHolder
是 Spring Security 存储谁经过身份验证的详细信息的地方。 -
SecurityContext - 从
SecurityContextHolder
并包含Authentication
当前经过身份验证的用户。 -
身份验证 - 可以是
AuthenticationManager
提供用户提供的凭证进行身份验证,或提供SecurityContext
. -
GrantedAuthority - 授予委托人的
Authentication
(即角色、范围等) -
AuthenticationManager - 定义 Spring Security 的过滤器如何执行身份验证的 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 instance 而不是使用SecurityContextHolder.getContext().setAuthentication(authentication) 以避免跨多个线程的争用条件。 |
2 | 接下来,我们创建一个新的Authentication 对象。
Spring Security 并不关心什么类型的Authentication implementation 在SecurityContext .
在这里,我们使用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
使用 strategy on startup,以指定您希望如何存储上下文。
对于独立应用程序,您将使用SecurityContextHolder.MODE_GLOBAL
策略。
其他应用程序可能希望安全线程生成的线程也采用相同的安全身份。
您可以通过使用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
.
您可以从默认模式更改SecurityContextHolder.MODE_THREADLOCAL
以两种方式。
第一种是设置系统属性。
第二种是在SecurityContextHolder
.
大多数应用程序不需要更改默认值。
但是,如果您这样做,请查看 JavaDoc 以获取SecurityContextHolder
了解更多信息。
SecurityContext
这SecurityContext
是从SecurityContextHolder获取的。
这SecurityContext
包含一个 Authentication 对象。
认证
这Authentication
interface 在 Spring Security 中有两个主要用途:
-
对
AuthenticationManager
提供用户提供的用于身份验证的凭据。 在此方案中使用时,isAuthenticated()
返回false
. -
表示当前经过身份验证的用户。 您可以获取当前的
Authentication
从 SecurityContext 中。
这Authentication
包含:
-
principal
:标识用户。 使用用户名/密码进行身份验证时,这通常是UserDetails
. -
credentials
:通常是密码。 在许多情况下,在用户通过身份验证后会清除此漏洞,以确保其不会泄露。 -
authorities
:这GrantedAuthority
实例 是授予用户的高级权限。 两个示例是 roles 和 scopes。
授予权限
GrantedAuthority
实例 是授予用户的高级权限。
两个示例是 roles 和 scopes。
您可以获得GrantedAuthority
实例Authentication.getAuthorities()
方法。
此方法提供了一个Collection
之GrantedAuthority
对象。
一个GrantedAuthority
毫不奇怪,是授予委托人的权力。
这些权限通常是 “角色”,例如ROLE_ADMINISTRATOR
或ROLE_HR_SUPERVISOR
.
这些角色稍后将配置为 Web 授权、方法授权和域对象授权。
Spring Security 的其他部分解释这些权威并期望它们存在。
使用基于用户名/密码的身份验证时GrantedAuthority
实例通常由UserDetailsService
.
通常,GrantedAuthority
对象是应用程序范围的权限。
它们并不特定于给定的域对象。
因此,您不太可能拥有GrantedAuthority
表示对Employee
对象编号 54,因为如果有数千个这样的权限,您很快就会耗尽内存(或者,至少会导致应用程序需要很长时间来验证用户)。
当然, Spring Security 是专门为处理这个常见需求而设计的,但是您应该改用项目的域对象安全功能来实现此目的。
身份验证管理器
AuthenticationManager
是定义 Spring Security 的过滤器如何执行身份验证的 API。
这Authentication
,然后由控制器在 SecurityContextHolder 上设置(即,通过Spring Security 的Filters
实例),它调用了AuthenticationManager
.
如果您没有与 Spring Security 的Filters
实例中,您可以设置SecurityContextHolder
直接使用,并且不需要使用AuthenticationManager
.
虽然AuthenticationManager
可以是任何东西,最常见的实现是ProviderManager
.
提供者管理器
ProviderManager
是最常用的AuthenticationManager
.ProviderManager
delegates 传递给List
之AuthenticationProvider
实例。
每AuthenticationProvider
有机会指示身份验证应该成功、失败或指示它无法做出决策并允许下游AuthenticationProvider
来决定。
如果未配置任何AuthenticationProvider
实例可以进行身份验证,身份验证失败,并显示ProviderNotFoundException
,这是一个特殊的AuthenticationException
,这表明ProviderManager
未配置为支持Authentication
这被传递到了它。

在实践中,每个AuthenticationProvider
知道如何执行特定类型的身份验证。
例如,一个AuthenticationProvider
可能能够验证用户名/密码,而另一个人可能能够验证 SAML 断言。
这样,每个AuthenticationProvider
执行非常特定类型的身份验证,同时支持多种类型的身份验证,并且只公开单个AuthenticationManager
豆。
ProviderManager
还允许配置可选的父级AuthenticationManager
,如果AuthenticationProvider
可以执行身份验证。
父级可以是任何类型的AuthenticationManager
,但它通常是ProviderManager
.

事实上,多个ProviderManager
实例可能共享相同的父实例AuthenticationManager
.
这在存在多个SecurityFilterChain
具有一些共同身份验证的实例(共享父级AuthenticationManager
),以及不同的身份验证机制(不同的ProviderManager
实例)。

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

当用户提交其凭证时,
AbstractAuthenticationProcessingFilter
创建一个Authentication
从HttpServletRequest
进行身份验证。
的类型Authentication
created 依赖于AbstractAuthenticationProcessingFilter
.
例如UsernamePasswordAuthenticationFilter
创建一个UsernamePasswordAuthenticationToken
从 HttpServletRequest
.
接下来,
Authentication
传递到AuthenticationManager
进行身份验证。
如果身份验证失败,则为 Failure。
-
RememberMeServices.loginFail
被调用。 如果未配置 Remember me,则为 no-op。 请参阅rememberme
包。 -
AuthenticationFailureHandler
被调用。 请参阅AuthenticationFailureHandler
接口。
如果身份验证成功,则为 Success。
-
SessionAuthenticationStrategy
收到新登录的通知。 请参阅SessionAuthenticationStrategy
接口。 -
Authentication在SecurityContextHolder上设置。 稍后,如果您需要保存
SecurityContext
这样就可以在将来的请求中自动设置它,SecurityContextRepository#saveContext
必须显式调用。 请参阅SecurityContextHolderFilter
类。 -
RememberMeServices.loginSuccess
被调用。 如果未配置 Remember me,则为 no-op。 请参阅rememberme
包。 -
ApplicationEventPublisher
发布InteractiveAuthenticationSuccessEvent
. -
AuthenticationSuccessHandler
被调用。 请参阅AuthenticationSuccessHandler
接口。