文章目录本文来说下AuthenticationManager验证原理
- AuthenticationManager概述
- AuthenticationManager相关类图
- security认证流程
- AuthenticationManager初始化流程
- AuthenticationManager的认证过程
- AuthenticationMananger与ProviderMananger
- AuthenticationProvider视角中的Authentication
AuthenticationManager概述
在 UsernamePasswordAuthenticationFilter源码分析 中,最后在类UsernamePasswordAuthenticationFilter 的验证方法 attemptAuthentication() 会将用户表单提交过来的用户名和密码封装成对象,委托给类 AuthenticationManager 的验证方法 authenticate() 进行身份验证。用户身份有关的信息UserDetails对象就是由AuthenticationManager认证管理器来管理的。
我们要做的,就是继承一个 UserDetailSevice 接口并且加入到容器中,他就是连接我们数据库和 Spring Security 的桥梁。
UserDetailService 的要求也很简单,只需要一个重写 loadUserByUsername(String username) 方法,里面的逻辑通常是从数据库查找出对应用户名的密码然后构造一个org.springframework.security.core.userdetails.UserDetails对象,Spring Security 会根据返回的这个带有正确用户信息的对象和前台传过来的用户名密码进行比对来判断是否认证通过。
因此,怎么设计数据库都可以,不管我们是用一个表还是两个表还是三个表,也不管我们是用户-授权,还是用户-角色-授权,还是用户-用户组-角色-授权,这些具体的东西 Spring Security 统统不关心,它只关心返回的那个User对象,至于怎么从数据库中读取数据,那就是我们自己的事了。
那么本文主要对 AuthenticationManager 的验证方法 authenticate() 验证原理进行源码分析。
AuthenticationManager相关类图
AuthenticationManager 验证过程涉及到的类和接口较多,先用一张类图说明各个类和接口之间的关系,如下:
核心类和接口说明
- AuthenticationManager 为认证管理接口类,其定义了认证方法 authenticate()。
- ProviderManager 为认证管理类,实现了接口 AuthenticationManager ,并在认证方法 authenticate() 中将身份认证委托给具有认证资格的 AuthenticationProvider 进行身份认证。ProviderManager中的成员变量 providers [List] 存储了一个 AuthenticationProvider 类型的 List。
- AuthenticationProvider 为认证接口类,其定义了身份认证方法 authenticate()。
- AbstractUserDetailsAuthenticationProvider 为认证抽象类,实现了接口 AuthenticationProvider 定义的认证方法 authenticate()。AbstractUserDetailsAuthenticationProvider 还定义了虚拟方法 retrieveUser() 用作查询数据库用户信息,以及虚拟方法 additionalAuthenticationChecks() 用作身份认证。
- DaoAuthenticationProvider 继承自类 AbstractUserDetailsAuthenticationProvider,实现该类的方法 retrieveUser() 和 additionalAuthenticationChecks()。DaoAuthenticationProvider 中还具有成员变量 userDetailsService [UserDetailsService] 用作用户信息查询,以及成员变量 passwordEncoder [PasswordEncoder] 用作密码的加密及验证。
security认证流程
本质上讲,Spring Security 是通过过滤器(Filter)和面向切面编程(AOP)实现应用安全控制。Spring Security 中定义和使用了很多的过滤器,针对认证过程重点讲解:AbstractAuthenticationProcessingFilter。AbstractAuthenticationProcessingFilter 用于拦截认证请求,它是基于浏览器和 HTTP 认证请求的处理器,可以理解为它就是 Spring Security 认证流程的入口。
整个认证流程如下
① AbstractAuthenticationProcessingFilter 收集用于认证的用户身份信息(通常是用户名和密码),并基于这些信息构造一个 Authentication请求对象,AbstractAuthenticationProcessingFilter 只是一个虚类,查看 Spring Security API 文档 可以看到 Spring Security 提供了几个实现类:
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- OpenIDAuthenticationFilter
- UsernamePasswordAuthenticationFilter
最常使用的应该是 UsernamePasswordAuthenticationFilter,其它类都应用于特定的场景。
② AbstractAuthenticationProcessingFilter 类将构造的 Authentication 请求对象呈现给 AuthenticationManager,AbstractAuthenticationProcessingFilter 类有以下方法设置和获取 AuthenticationManager:
protected AuthenticationManager getAuthenticationManager() public void setAuthenticationManager(AuthenticationManager authenticationManager)
③ AuthenticationManager 只是一个接口,Spring Security 提供了一个默认实现 ProviderManager。ProviderManager 在接收到 AbstractAuthenticationProcessingFilter 传递过来的 Authentication 请求对象后并不会执行认证处理,它持有一个 AuthenticationProvider 的列表,ProviderManager 委托列表中的 AuthenticationProvider 处理认证请求;
④ AuthenticationProvider 也只是接口,Spring Security 提供了很多此接口的实现,如 DaoAuthenticationProvider、LdapAuthenticationProvider、JaasAuthenticationProvider 等,现在暂时不关心这些具体实现。列表中的 AuthenticationProvider 会依次对 Authentication 请求对象进行认证处理,如果认证通过则返回一个完全填充的 Authentication 对象(后面会解释什么是“完全填充”),如果认证不通过则抛出一个异常(注意对抛出的异常有类型要求)或直接返回 null。如果列表中的所有 AuthenticationProvider 都返回 null,则 ProviderManager 会抛出 ProviderNotFoundException 异常;
⑤ 认证通过后 AuthenticationProvider 返回完全填充的 Authentication 对象给 ProviderManager,ProviderManager 继续向上返回给 AbstractAuthenticationProcessingFilter,AbstractAuthenticationProcessingFilter 会继续返回。
⑥ Spring Security 的“authentication mechanism”在接收到一个完全填充的 Authentication 对象返回后会认定认证请求有效,并将此 Authentication 对象放入 SecurityContextHolder。
⑦ SecurityContextHolder 是 Spring Security 最基础的对象,用于存储应用程序当前安全上下文的详细信息,这些信息后续会被用于授权。
至此,Spring Security 的认证流程已介绍完毕,但还缺少对两个十分常见的接口的说明:UserDetails 和 UserDetailsService。
Spring Security API 文档 对 UserDetails 的说明如下:
Implementations are not used directly by Spring Security for security purposes. They simply store user information which is later encapsulated into Authentication objects. This allows non-security related user information (such as email addresses, telephone numbers etc) to be stored in a convenient location.
从中可以看出 UserDetails 只是用于存储用户信息并最终封装到 Authentication 对象中。
Spring Security API 文档 对 UserDetailsService 的说明如下:
Core interface which loads user-specific data.It is used throughout the framework as a user DAO and is the strategy used by the DaoAuthenticationProvider.The interface requires only one read-only method, which simplifies support for new data-access strategies.
从中可以看出 UserDetailsService 只定义了一个只读方法,返回一个 UserDetails 接口对象。
UserDetails loadUserByUsername(String username)
实际上,UserDetailsService 和 UserDetails 只是构造 Authentication 对象的一个过程。UserDetailsService 可以作为 AuthenticationProvider 的一个属性,在 AuthenticationProvider 执行请求认证时调用 UserDetailsService 的 loadUserByUsername 方法返回一个 UserDetails 对象,并使用此 UserDetails 对象封装最终的 Authentication 对象,事实上这也就是 Spring Security 预置的一些AuthenticationProvider 实现类使用的方法,如 DaoAuthenticationProvider,可以查看 Spring Security API 文档 进一步了解 DaoAuthenticationProvider 实现细节。
在大多数情况下,如果需要定制认证过程,建议直接实现 AuthenticationProvider,这样做更有意义,在 AuthenticationProvider 的 authenticate 方法中直接封装 Authentication 对象,这比引用一个 UserDetailsService 实现返回一个 UserDetails 对象后再封装成最终的 Authentication 对象更直观。
AuthenticationManager初始化流程
AuthenticationManager这个接口方法非常奇特,入参和返回值的类型都是Authentication。该接口的作用是对用户的未授信凭据进行认证,认证通过则返回授信状态的凭据,否则将抛出认证异常AuthenticationException。
AuthenticationManager源码
package org.springframework.security.authentication; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; public interface AuthenticationManager { Authentication authenticate(Authentication var1) throws AuthenticationException; }
那么AbstractAuthenticationProcessingFilter中的 AuthenticationManager 是在哪里配置的呢? 看过Spring Security 实战干货系列应该知道WebSecurityConfigurerAdapter中的void configure(AuthenticationManagerBuilder auth)是配置AuthenticationManager 的地方, 我根据源码总结了一下AuthenticationManager 的初始化流程,相信可以帮助你去阅读相关的源码:
如何配置AuthenticationManager
@Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); }
AuthenticationManager的认证过程
AuthenticationManager的实现ProviderManager管理了众多的AuthenticationProvider。每一个AuthenticationProvider都只支持特定类型的Authentication,如果不支持将会跳过。另一个作用就是对适配的Authentication进行认证,只要有一个认证成功,那么就认为认证成功,所有的都没有通过才认为是认证失败。认证成功后的Authentication就变成授信凭据,并触发认证成功的事件。认证失败的就抛出异常触发认证失败的事件。
从这里我们可以看出认证管理器AuthenticationManager针对特定的Authentication提供了特定的认证功能,我们可以借此来实现多种认证并存。
AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,甚至,可能允许用户使用指纹登录(还有这样的 *** 作?没想到吧),所以说AuthenticationManager一般不直接认证,AuthenticationManager接口的常用实现类ProviderManager 内部会维护一个List列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。也就是说,核心的认证入口始终只有一个:AuthenticationManager,不同的认证方式:用户名+密码(UsernamePasswordAuthenticationToken),邮箱+密码,手机号码+密码登录则对应了三个AuthenticationProvider。这样一来四不四就好理解多了?
AuthenticationMananger与ProviderMananger
AuthenticationMananger作为整个身份验证核心最外层的封装负责与外部使用者进行交互。 AuthenticationMananger接口有且仅有一个对外的服务便是“身份验证”。这样是整个身份验证服务对外提供的服务接口。
外部使用者通过将身份验证的必要信息,比如用户名和密码封装一个Authentication传递、调用AuthenticationMananger的authenticate方法。如果没有返回异常和null值,那么验证服务便是完成。完成身份验证的Authentication不仅包含了用户的身份验证信息,比如用户名,额外还会将该用户身份下所有对应的权限列表也一并封装返回。
身份信息交互的纽带:Authentication
在整个与外部使用交互的过程中Authentication的职责有两个,第一个是封装了验证请求的参数,第二个便是封装了用户的权限信息。结合Authentication的接口设计便更加清晰了这样的设计意图:principal用于存放用户的身份标识信息,比如用户名,credentials用于存放用户的验证凭证比如密码,authorities用于存放用户的权限列表。而details则存放了除了用户名和密码其他可能会被用于身份验证的信息,比如应用限定用户的使用ip范围场景下,ip信息可能便会被存放在details做辅助的验证信息使用。
唯一的实现类:ProviderMananger
为了向外部提供身份验证服务,Spring Security中通过ProviderMananger实现了AuthenticationManager的身份验证接口。作为实现类ProviderMananger便不能和AuthenticationManager一样只关心唯一的抽象核心服务authenticate。在ProviderMananger为了管理外部输入与像外部返回的Authentication,ProviderMananger内部大致的工序如下:
- 首先,寻找可以进行验证当前外部输入Authentication形式的AuthenticationProvider;如果自身的providers中无法处理验证并且当前层次的Mananger还有父级的Mananger则向上传递,交由父层Mananger进行处理;
- 然后,因为details的信息是外部传入的,内部身份验证后的Authentication并不会从持久化或者其他数据源中携带,在返回前将details写入返回给外部的Authentication;
- 最后,如果有必要则将外部身份验证请求中的敏感擦除,比如讲请求验证的密码置空。
了解了ProviderMananger完成的三件工作,大致明白了虽然整个验证框架只有一个ProviderManager暴露在外部,但是其内部可能是有多个AuthenticationMananger和AuthenticationProvider组成的网络,并且最终进行核心身份验证的还是AuthenticationProvider。核心在叶子节点中依次寻找对验证当前Authentication形式的AuthenticationProvider。如果存在支持便将验证请求的Authentication传递给AuthenticationProvider,委托其进行验证。在处理输入的验证请求Authentication,ProviderMananger并不对其进行任何的处理,而是指在处理完后进行必要的加工和处理。
AuthenticationProvider视角中的Authentication
相对AuthenticationMananger而言AuthenticationProvider的工作更加明确:针对特定的验证数据,提供特定的验证行为。在这个语境下,Authentication的设计目的是解决验证什么(What)的问题,而authenticate方法更像是在回答怎么验证的问题(How)。
那么我们先对验证数据也就是Authentication的设计进行展开讨论。Authentication主要职责就是封装身份验证时候需要的信息数据,比如用户名场景下的用户名和密码,短信验证码下的手机号码和验证码,OAuth2场景下的ID和Code。总之每个不同验证协议使用的验证信息都需要被被封装成Authentication,更准确说在Spring Security把这种封装了用户身份验证信息的Authentication具体为了AuthenticationToken的概念,毕竟一说token更容易理解。所有Spring Security中提供的各种协议的身份验证数据的封装都继承AbstractAuthenticationToken,基于用户名和密码的UsernamePasswordAuthenticationToken,基于OAuth2的OAuth2AuthorizationCodeAuthenticationToken,基于CAS的CasAssertionAuthenticationToken。
通常我们使用用户名和密码的场景是最多,无论是使用基于数据库持久化的用户名密码方案还是基于LDAP的用户名和密码方法。虽然验证在验证协实现有细微差别,但是无论使用验证LDAP还是数据库进行身份验证比对,因为用户提交的验证身份信息几乎一致,我们便可以复用通用结构的AuthenticationToken——将username赋值到principal属性并将password赋值到cencredentials属性中。这样就意味着我们在AuthenticationToken设计上最需要考虑是数据的封装,而不是身份验证行为的实现。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)