文章目录
SecurityContextHolder
SecurityContextHolder是Spring Security存储身份验证人员详细信息的地方。Spring Security认证模型的核心是SecurityContextHolder。它包含SecurityContext。
Spring Security不关心SecurityContextHolder的填充方式。如果它包含一个值,则将其用作当前经过身份验证的用户。
指示用户已通过身份验证的最简单方法是直接设置SecurityContextHolder。
// 我们首先创建一个空的SecurityContext。创建新的SecurityContext实例而不是使用SecurityContextHolder.getContext()来设置身份信息(authentication)以避免跨多个线程的竞争条件。
SecurityContext context = SecurityContextHolder.createEmptyContext();
// 接下来,我们创建一个新的身份验证对象。Spring Security不关心在SecurityContext上设置了什么类型的身份验证实现。
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
// 最后,我们在SecurityContextHolder上设置SecurityContext。Spring Security将使用此信息进行授权。
SecurityContextHolder.setContext(context);
如果希望获取有关已验证主体的信息,可以通过访问SecurityContextHolder来获取。
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
默认情况下,SecurityContextHolder使用ThreadLocal存储这些细节,这意味着SecurityContext始终可用于同一线程中的方法,即使SecurityContext没有作为参数显式传递给这些方法。如果在处理当前主体的请求后小心地清除线程SecurityContextHolder.clearContext(),那么以这种方式使用ThreadLocal是非常安全的。Spring Security的FilterChainProxy确保始终清除SecurityContext。
有些应用程序并不完全适合使用ThreadLocal,因为它们使用线程的方式非常特殊.SecurityContextHolder可以在启动时配置一个策略,以指定上下文的存储方式。
SecurityContextHolder的能力其实都依赖于SecurityContextHolderStrategy这个接口来实现,根据JVM环境的参数不同,实例化不同的存储策略,默认使用的是ThreadLocalSecurityContextHolderStrategy这个策略,一般情况下我们不需要主动去更换存储策略。
SecurityContext
SecurityContext是具体存放身份认证信息的对象,其只有一个子类SecurityContextImpl。
Authentication
在Spring Security中,身份验证有两个主要目的:
- AuthenticationManager的输入,用于提供用户提供的用于身份验证的凭据。在本场景中使用时,isAuthenticated()返回false。
- 表示当前经过身份验证的用户。当前身份验证可以从SecurityContext获得。
Authentication包含了三个部分:
- principal,代表用户。当使用用户名/密码进行身份验证时,这通常是UserDetails的一个实例。
- credentials,通常是密码。在许多情况下,这将在用户经过身份验证后清除,以确保不会泄露。
- authorities,授权权限是授予用户的高级权限。比如是角色或范围。
GrantedAuthority
GrantedAuthority是授予用户的高级权限。一些例子是角色或范围。GrantedAuthority可以从 Authentication.getAuthorities() 方法中获取授权。此方法提供GrantedAuthority对象的集合。GrantedAuthority被授予目标是用户principal。这种权限通常是“角色”,如角色管理员或角色人力资源主管。这些角色稍后将配置为web authorization, method authorization和domain object authorization。Spring Security的其他部分能够解释并验证这些权限。使用基于用户名/密码的身份验证时,授权通常由UserDetails服务加载。
通常,GrantedAuthority对象是应用程序范围的权限。它们不是特定于指定的domain object。因此,您不太可能拥有GrantedAuthority来代表编号54Employee对象的权限,因为如果有数千个这样的权限,您将很快耗尽内存(或者,至少会导致应用程序花很长时间来验证用户)。当然,Spring Security是专门为处理这一常见需求而设计的,但是您可以使用project’s domain object 安全能力来实现这一目的。
PS:读完整段,我理解到domain object是指的数据库表的行数据实例,project’s domain object指定应该就是实体类对象。
AuthenticationManager
AuthenticationManager定义了Spring Security’s Filters 用来进行认证的API。Spring Security’s Filters从SecurityContextHolder获取到Authentication后,通过使用AuthenticationManager来进行认证。如果未与Spring Security的过滤器集成,则可以直接设置SecurityContextHolder,无需使用AuthenticationManager。虽然AuthenticationManager的实现可以是任何形式,但最常见的实现是ProviderManager。
ProviderManager
ProviderManager是AuthenticationManager最常用的实现。ProviderManager将委托给AuthenticationProviders列表。每个AuthenticationProvider都有机会表明身份验证应该成功、失败,或者表明它无法做出决定,并允许下游AuthenticationProvider做出决定。如果配置的AuthenticationProviders都无法进行身份验证,则身份验证将失败,抛出ProviderNotFoundException,这是一种特殊的身份验证异常,表明ProviderManager未配置为支持传递的身份验证类型。
实际上,每个AuthenticationProvider都知道如何执行特定类型的身份验证。例如,一个AuthenticationProvider可能能够验证用户名/密码,而另一个可能能够验证SAML断言。这允许每个AuthenticationProvider执行非常特定的身份验证类型,同时支持多种类型的身份验证,并且只暴露一个AuthenticationManager bean。
ProviderManager还允许配置可选的父级AuthenticationManager,在没有AuthenticationProvider可以执行身份验证的情况下,可以咨询该父级AuthenticationManager。父级可以是任何类型的AuthenticationManager,但它通常是ProviderManager的实例。
事实上,多个ProviderManager实例可能共享同一个父AuthenticationManager。这在多个SecurityFilterChain实例具有某些共同身份验证(共享父级AuthenticationManager)但也具有不同身份验证机制(不同的ProviderManager实例)的场景中比较常见。
默认情况下,ProviderManager将尝试从成功的身份验证请求返回的身份验证对象中清除任何敏感凭据信息。这可以防止密码等信息在HttpSession中保留的时间超过必要的时间。例如,缓存用户信息来提高无状态应用程序的性能时,这可能会导致问题。如果Authentication包含对缓存中某个对象(例如UserDetails实例)的引用,并且其凭据已被删除,则将无法再根据缓存的值进行身份验证。
下面是ProviderManager的部分类注释:
AuthenticationManager通过AuthenticationProviders列表迭代身份验证请求。AuthenticationProviders通常会按顺序进行尝试,直到其中一个provider非空响应。一个非空响应代表这个provider有权决定请求的身份认证并且不需要后续provider再次尝试进行认证。
如果后续provider程序成功验证了请求,则忽略先前的身份验证异常,并将使用成功的身份验证。
如果没有后续provider程序提供非空响应或新的AuthenticationException,则将使用收到的最后一个AuthenticationException。
如果没有provider程序返回非空响应,或者表明它可以处理身份验证,ProviderManager将抛出ProviderNotFoundException。
还可以设置父AuthenticationManager,在当前AuthenticationManager没有完成身份认证时,会尝试调用父AuthenticationManager进行身份认证。不过,这是为了支持名称空间配置选项,而不是通常需要的功能。
// ......遍历当前AuthenticationManager的provider集合进行认证......
// 认证结果为空,调用父AuthenticationManager进行认证
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = e;
}
}
AuthenticationManager认证过程的例外情况是提供程序抛出AccountStatusException,在这种情况下,将不会查询列表中的其他provider程序。
AuthenticationProvider
可以将多个AuthenticationProvider注入ProviderManager。每个AuthenticationProvider执行特定类型的身份验证。例如,DaoAuthenticationProvider支持基于用户名/密码的身份验证,而JwtAuthenticationProvider支持对JWT令牌进行身份验证。
AuthenticationEventPublisher
Authentication 认证事件的发布委托于配置的AuthenticationEventPublisher,默认为不发布事件的空实现,因此如果您想要接收事件,就必须注入一个publisher bean。
标准实现是 DefaultAuthenticationEventPublisher,它将常见异常映射到事件(在身份验证失败的情况下),并在身份验证成功时发布AuthenticationSuccessEvent。
如果要发布失败事件,需要注意一点:当前ProviderManager在AuthenticationProvider集合遍历完成后,没有完成认证,会尝试调用父ProviderManager(存在的话)进行身份认证,如果当前和父ProviderManager都设置了失败事件通知,那么可能会导致事件重复的问题。
CredentialsContainer
身份验证后,如果返回的身份验证对象实现了CredentialsContainer接口,则将从中清除凭据。可以通过修改eraseCredentialsAfterAuthentication属性来控制此行为。
AuthenticationEntryPoint
AuthenticationEntryPoint用于发送从客户端请求凭据的HTTP响应。有时,客户机会主动包含用户名/密码等凭据以请求资源。在这些情况下,Spring Security不需要提供从客户端请求凭据的HTTP响应,因为它们已经包含在内。在其他情况下,客户端将向未经授权访问的资源发出未经验证的请求。在这种情况下,AuthenticationEntryPoint的实现用于从客户端请求凭据。AuthenticationEntryPoint实现可能会重定向到登录页面,并使用WWW Authenticate头进行响应,等等。
总结:AuthenticationEntryPoint就是Spring Security Filters 在认证失败时,被调用启动身份验证方案(跳转登录页、返回403等等)。
AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter用作验证用户凭据的基本Filter。在对凭据进行身份验证之前,Spring Security通常使用AuthenticationEntryPoint请求凭据。接下来,AbstractAuthenticationProcessingFilter可以对提交给它的任何身份验证请求进行身份验证。
-
当用户提交其凭据时,AbstractAuthenticationProcessingFilter会根据要进行身份验证的HttpServletRequest创建身份验证。创建的身份验证类型取决于AbstractAuthenticationProcessingFilter的子类。例如,UsernamePasswordAuthenticationFilter根据HttpServletRequest中提交的用户名和密码创建UsernamePasswordAuthenticationToken。
-
接下来,身份验证被传递到要进行身份验证的AuthenticationManager。
-
如果身份验证失败,则 :
(1)SecurityContextHolder已被清除。
(2)RememberMeServices.loginFail被调用。如果“remember me”未配置,不需要这步操作。
(3)调用AuthenticationFailureHandler。 -
如果身份验证失败,则:
(1)SessionAuthenticationStrategy会收到新登录的通知。
(2)Authentication 设置在SecurityContextHolder。后续SecurityContextPersistenceFilter将SecurityContext保存到HttpSession。
(3)RememberMeServices.loginSuccess 被调用。如果“remember me”未配置,不需要这步操作。
(4)ApplicationEventPublisher 发布 一个 InteractiveAuthenticationSuccessEvent 事件。
(5)调用AuthenticationSuccessHandler。
下面是AbstractAuthenticationProcessingFilter的部分类注释:
基于浏览器的基于HTTP的身份验证请求的抽象处理器。Filter要求您设置authenticationManager属性。需要AuthenticationManager来处理Authentication实例的请求令牌。如果请求与SetRequireAuthenticationRequestMatcher设置的RequestMatcher匹配,此Filter将拦截请求并尝试执行来自该请求的身份验证。
身份验证由attemptAuthentication方法执行,该方法必须由子类实现。