随着公司业务的发展,细分的业务系统随之增加,每个系统都有自己独立的登录权限模块,而且功能都是相似的,为此呢,特地研究了下SSO这块的内容,经典的应该是通过CAS处理,目前我们所有业务系统都已经微服务化,曾经的痛,只有切身经历了才能体会到。因为很早之前就接触过OAuth2,所以自然想到用它来实现该功能。网上简单的搜了下,很多基于spring boot 来实现的,但却没有能够真正入手的demo,于是自己花了点时间做了下调研,结合最终实现的demo,记录下一些心得吧:
为便于理解,先上图,理解下整个授权流程:

图一
在OAuth2中使用了Security 做了登录及授权的处理,之前一直以为Security是Shiro的变体,也就没有特地去关注它,直到目前为止也只是风闻有它,所以本篇内容会先介绍下它,介绍整体的框架流程,一言以概之,Security是责任链设计模式的经典实现,它本质也就是一条很长的过滤链。
在我使用的版本中,一共有12个过滤器(DefaultSecurityFilterChain类中):

图二

图三
每一个过滤器的作用有兴趣的自行研究~看类名应该也能知道个大概。图二是默认启用的过滤器链,图三是因为我使用的是通过UsernamePasswordAuthenticationFilter来验证登录及授权的,我自定义实现了UserDetailsService。这也是我接下来要介绍的重点,用户名密码验证流程:
AbstractAuthenticationProcessingFilter.doFilter()调用UsernamePasswordAuthenticationFilter中的attemptAuthentication方法,

图四
UsernamePasswordAuthenticationFilter类中方法:
图五

图六
通过该类可以知道:
(1)可以看到它默认的登录请求url是"/login",并且只允许POST方式的请求
(2)obtainUsername()方法点进去发现它默认是根据参数名为"username"和"password"来获取用户名和密码的
(3)通过构造方法实例化一个UsernamePasswordAuthenticationToken对象,此时调用的是UsernamePasswordAuthenticationToken的两个参数的构造函数,如图:

图七
其中super(null)调用的是父类的构造方法,传入的是权限集合,因为目前还没有认证通过,所以不知道有什么权限信息,这里设置为null,然后将用户名和密码分别赋值给principal和credentials,同样因为此时还未进行身份认证,所以setAuthenticated(false)
(4)setDetails(request, authRequest)是将当前的请求信息设置到UsernamePasswordAuthenticationToken中
(5)通过调用getAuthenticationManager()来获取AuthenticationManager,通过调用它的authenticate方法来查找支持该token(UsernamePasswordAuthenticationToken)认证方式的provider,然后调用该provider的authenticate方法进行认证
AuthenticationManager是用来管理AuthenticationProvider的接口,通过查找后进入,然后查看它的继承关系,找到ProviderManager实现类,它实现了AuthenticationManager接口,查看它的authenticate方法,它里面有段这样的代码:

图八
接下来看AuthenticationProvider接口,就是进行身份认证的接口,它里面有两个方法:authenticate认证方法和supports是否支持某种类型token的方法,查看继承关系,找到AbstractUserDetailsAuthenticationProvider抽象类,它实现了AuthenticationProvider接口,它的supports方法如下:

图九
由图可知支持UsernamePasswordAuthenticationToken类型的AuthenticationProvider

图十
查看retrieveUser的方法:

是个抽象方法,查看它的实现类:DaoAuthenticationProvider,重点来了:

图十一
UserDetailsService是个接口,我们自己实现了该接口,重写了方法,从我们自己的数据库去查找用户信息。
流程一直走到现在,聪明的你应该会提问了,为啥没有验证用户名和密码是否匹配的过程,到底是怎么实现的呢?show me your code:

还是在AbstractUserDetailsAuthenticationProvider类里的authenticate的方法里,做了一些前置检查,后置检查,最后通过验证。这里面调用了additionalAuthenticationChecks 该方法,

查看其实现:

至此完整的验证流程已经结束了。最后再留一个问题给大家,用户认证成功之后,又是怎么保存认证信息的呢,在下一次请求过来是如何判断该用户是否已经认证了呢?有兴趣的同学可以查看SecurityContextPersistenceFilter的实现,里面还涉及到线程ThreadLocal.
最后先上一个流程图对今天的内容做下总结:





