AuthenticationManager
Spring Security核心组件用
AuthenticationMananger与ProviderMananger
AuthenticationMananger
作为整个身份验证核心最外层的封装负责与外部使用者进行交互。AuthenticationMananger
接口有且仅有一个对外的服务便是“身份验证”。
Authentication authenticate(Authentication authentication) throws AuthenticationException;
外部使用者通过将身份验证的必要信息,比如用户名和密码封装一个Authentication传递、调用AuthenticationMananger的authenticate方法。如果没有返回异常和null值,验证服务便是完成。完成身份验证的Authentication不仅包含用户的身份验证信息,比如用户名,还会将该用户身份下所有对应的权限列表也一并封装返回。
身份信息交互的纽带:Authentication
整个与外部使用交互的过程中Authentication
的职责有两个,第一个是封装验证请求的参数,第二个便是封装用户的权限信息。结合Authentication
的接口设计更加清晰这样的设计意图:principal用于存放用户的身份标识信息,比如用户名,credentials用于存放用户的验证凭证比如密码,authorities用于存放用户的权限列表。而details则存放除了用户名和密码其他可能会被用于身份验证的信息,比如应用限定用户的使用ip范围场景下,ip信息可能便会被存放在details做辅助的验证信息使用。
ProviderMananger
实现AuthenticationManager
的身份验证接口。在ProviderMananger
为了管理外部输入与向外部返回的Authentication
,ProviderMananger
内部大致的工序如下:- 首先,寻找可以进行验证当前外部输入
Authentication
形式的AuthenticationProvider
;如果自身的providers中无法处理验证并且当前层次的Mananger
还有父级的Mananger
则向上传递,交由父层Mananger
进行处理; - 然后,因为details的信息是外部传入的,内部身份验证后的
Authentication
并不会从持久化或者其他数据源中携带,在返回前将details写入返回给外部的Authentication
; - 最后,如果有必要则将外部身份验证请求中的敏感擦除,比如讲请求验证的密码置空。
整个验证框架只有一个ProviderManager
暴露在外部,但是其内部可能是有多个AuthenticationMananger
和AuthenticationProvider
组成的网络,并且最终进行核心身份验证的还是AuthenticationProvider
。核心在叶子节点中依次寻找对验证当前Authentication
形式的AuthenticationProvider
。如果存在支持便将验证请求的Authentication
传递给AuthenticationProvider
,委托其进行验证。在处理输入的验证请求Authentication
,ProviderMananger
并不对其进行任何的处理,而是指在处理完后进行必要的加工和处理。
AuthenticationProvider视角中的Authentication
Authentication主要职责就是封装身份验证时候需要的信息数据,比如用户名场景下的用户名和密码,短信验证码下的手机号码和验证码,OAuth2场景下的ID和Code。总之每个不同验证协议使用的验证信息都需要被被封装成Authentication,更准确说在Spring Security把这种封装用户身份验证信息的Authentication具体为的概念。所有Spring Security中提供的各种协议的身份验证数据的封装都继承,基于用户名和密码的UsernamePasswordAuthenticationToken,基于OAuth2的OAuth2AuthorizationCodeAuthenticationToken,基于CAS的CasAssertionAuthenticationToken。DaoAuthenticationProvider
DaoAuthenticationProvider为了实现外部的验证请求便需要对外部传递身份信息——用户名和密码进行验证。
从数据层获取对应用户名在数据层的数据记录;
对外部的用户名、密码与数据层的用户名、密码进行比对。
第一个任务,从数据层获取对应用户名在数据层的数据记录,我们的目标是从数据层中查找到我们需要比对的用户身份数据,但是在这个场景下我们无非控制的是数据层的实现具体是什么?是通过JDBC访问Mysql还是通过JPA访问Oracle,更或是直接通过内存访问一个存储了用户信息键值对的Map? 第二个任务,对外部的用户名、密码与数据层的用户名、密码进行比对,具体的加密算法是什么?如何实现的? 这两个问题在DaoAuthenticationProvider中都无法给出明确的实现。Spring Security便将这两种在DaoAuthenticationProvider无法确定、存在变化的行为分别委托给了DaoAuthenticationProvider两个重要组件去完成:
- 通过用户名返回数据层中的用户信息的UserDetailsService;
- 通过特定加密算法处理用户密码的PasswordEncoder。
使用UserDetailsService获取内部用户身份信息
UserDetailsService接口定位向核心组件们提供数据层的用户信息,而用户信息在这里被封装成UserDetails。
UserDetailsService中只有一个方法是loadUserByUsername方法,通过传入用户名返回数据层用户身份记录。当确定获取用户身份信息的方法之后,便可以自行扩展UserDetailsService方法,告知框架如何获取用户身份信息。这个步骤是使用Spring Security中是必须完成的工作。 以下代码用于配置使用的UserDetailsService:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //注入新的UserDetailsServiceBean @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); return manager; } }
通过UserDetailsService返回数据类型是UserDetails,UserDetails中封装用户名、密码和授权信息同时还额外包括一些过期和锁定的标识属性。不难发现UserDetails封装的数据和Authentication非常的相似。在身份验证成功后,DaoAuthenticationProvider便会将内部的UserDetails抽离必要的数据对应赋值到UsernamePasswordAuthenticationToken最终返回给外部调用者进行使用
使用PasswordEncoder来进行密码的比对
DaoAuthenticationProvider收到了外部提交的用户名和密码,同样的DaoAuthenticationProvider也查找到了对应用户名在数据库中的用户名和密码。通常情况下虽然都是密码,数据库中存储的密码通常会进行过一定的加密。DaoAuthenticationProvider便需要将外部提交的用户名和密码进行一次加密流程并进行比对。PasswordEncoder接口主要的作用就是对明文密码进行加密与比对。
Spring Security中默认向DaoAuthenticationProvider提供的PasswordEncoder是BCryptPasswordEncoder。