本文描述 Servlet 身份验证中使用的 Spring Security 的主要架构组件。
- SecurityContextHolder -
SecurityContextHolder
是 Spring Security 存储身份验证者的详细信息的位置。 - SecurityContext - 从
SecurityContextHolder
获取,包含当前已验证用户的Authentication
。 - Authentication - 可以是
AuthenticationManager
的输入,以提供用户已提供的身份验证凭据,也可以是SecurityContext
中的当前用户。 - GrantedAuthority - 在
Authentication
上授予主体的权限(即角色、范围等) - AuthenticationManager - 定义 Spring Security 的 Filters 如何执行身份验证的 API。
- ProviderManager -
AuthenticationManager
最常见的实现。 - AuthenticationProvider - 由
ProviderManager
用于执行特定类型的身份验证。 - Request Credentials with
AuthenticationEntryPoint
- 用于从客户端请求凭证(即重定向到登录页面,发送WWW-Authenticate
响应等) - AbstractAuthenticationProcessingFilter - 用于身份验证的基础
Filter
。这也提供了一个很好的想法,高层次的身份验证流程和各部分如何协同工作。
SecurityContextHolder
Spring Security 身份验证模型的核心是 SecurityContextHolder
。它包含 SecurityContext。
SecurityContextHolder
是 Spring Security 存储谁被认证的详细信息的地方。Spring Security 不关心 SecurityContextHolder
是如何填充的。如果它包含一个值,则将其用作当前经过身份验证的用户。
指示用户已通过身份验证的最简单方法是直接设置 SecurityContextHolder
:
java
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
- 我们先创建一个空的
SecurityContext
。您应该创建一个新的SecurityContext
实例,而不是使用SecurityContextHolder.getContext().setAuthentication(authentication)
,以避免多个线程之间的竞争条件。 - 接下来,我们创建一个新的
Authentication
对象。Spring Security 不关心在SecurityContext
上设置了什么类型的Authentication
实现。在这里,我们使用TestingAuthenticationToken
,因为它非常简单。更常见的生产场景是UsernamePasswordAuthenticationToken(userDetails, password, authorities)
。 - 最后,我们将
SecurityContext
设置在SecurityContextHolder
上。Spring Security 使用此信息进行授权。
默认情况下, SecurityContextHolder
使用 ThreadLocal
来存储这些细节,这意味着 SecurityContext
始终可用于同一线程中的方法,即使 SecurityContext
没有显式地作为参数传递给这些方法。如果在当前主体的请求被处理之后注意清除线程,那么以这种方式使用 ThreadLocal
是非常安全的。Spring Security 的FilterChainProxy 确保 SecurityContext
始终被清除。
有些应用程序并不完全适合使用 ThreadLocal
,因为它们使用线程的特定方式。例如,Swing客户端可能希望Java虚拟机中的所有线程使用相同的安全上下文。您可以在启动时使用策略配置 SecurityContextHolder
,以指定您希望如何存储上下文。对于独立的应用程序,您可以使用 SecurityContextHolder.MODE_GLOBAL
策略。其他应用程序可能希望安全线程派生的线程也采用相同的安全标识。你可以通过使用 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
来实现。您可以通过两种方式更改默认的 SecurityContextHolder.MODE_THREADLOCAL
模式。第一个是设置系统属性。第二个是在 SecurityContextHolder
上调用一个静态方法。大多数应用程序不需要更改默认值。
SecurityContext
从 SecurityContextHolder 获取 SecurityContext
。 SecurityContext
包含一个 Authentication 对象。
Authentication
Authentication
接口在 Spring Security 中有两个主要用途:
- 输入
AuthenticationManager
以提供用户已提供用于身份验证的凭据。在这种情况下,isAuthenticated()
返回false
。 - 表示当前经过身份验证的用户。您可以从 SecurityContext 获取当前的
Authentication
。
Authentication
包含:
principal
:识别用户。当使用用户名/密码进行身份验证时,这通常是UserDetails
的实例。credentials
:通常是密码。在许多情况下,这是在用户通过身份验证后清除的,以确保它不会泄露。authorities
:GrantedAuthority
实例是授予用户的高级权限。常见的两个权限是角色和范围。
GrantedAuthority
GrantedAuthority
实例是授予用户的高级权限。常见的两个权限是角色和范围。
您可以通过 Authentication.getAuthorities()
方法获取 GrantedAuthority
实例。此方法返回 GrantedAuthority
对象的集合。 GrantedAuthority
是授予委托人的权限,这些权限通常是"角色",例如 ROLE_ADMINISTRATOR
或 ROLE_HR_SUPERVISOR
。这些角色稍后会针对Web授权、方法授权和域对象授权进行配置。Spring Security 的其他部分解释这些权限并期望它们存在。当使用基于用户名/密码的身份验证时, GrantedAuthority
实例通常由 UserDetailsService
加载。
AuthenticationManager
AuthenticationManager
是定义 Spring Security 的 Filters 如何执行身份验证的 API。返回的 Authentication
然后由调用 AuthenticationManager
的控制器(即Spring Security的 Filters
实例)在 SecurityContextHolder 上设置。如果您没有集成 Spring Security 的 Filters
实例,则可以直接设置 SecurityContextHolder
,而不需要使用 AuthenticationManager
。
虽然
AuthenticationManager
的实现可以是任何东西,但最常见的实现是ProviderManager
。
ProviderManager
ProviderManager
是 AuthenticationManager
最常用的实现。 ProviderManager
委托给 AuthenticationProvider
实例中的 List
实例。每个 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
属性。
AuthenticationProvider
您可以将多个 AuthenticationProvider
实例注入到 ProviderManager
中。每个 AuthenticationProvider
执行特定类型的身份验证。例如, DaoAuthenticationProvider
支持基于用户名/密码的身份验证,而 JwtAuthenticationProvider
支持对JWT令牌进行身份验证。
Request Credentials with 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
以进行身份验证。 - 如果认证失败,则为failure。
- SecurityContextHolder 被清除。
RememberMeServices.loginFail
被调用。如果没有配置remember me,则此操作无效AuthenticationFailureHandler
被调用。
- 如果认证成功,则为success。
SessionAuthenticationStrategy
收到新登录的通知。- 在 SecurityContextHolder 上设置身份验证。之后,如果您需要保存
SecurityContext
以便可以在将来的请求中自动设置它,SecurityContextRepository#saveContext
则必须显式调用。 RememberMeServices.loginSuccess
被调用。如果没有配置remember me,此操作无效。ApplicationEventPublisher
发布一个InteractiveAuthenticationSuccessEvent
。
uccess` 被调用。如果没有配置remember me,此操作无效。ApplicationEventPublisher
发布一个InteractiveAuthenticationSuccessEvent
。AuthenticationSuccessHandler
被调用。