一、前言
近期看了spring security相关的介绍,再加上项目所用若依框架的底层安全模块也是spring security,所以想从源码的角度加深下对该安全模块的理解(看源码之前,我们要先有个意识,那就是spring security安全模块主要是通过filter实现安全功能的)。为了防止迷失在源码的追踪中,我们首先列出自己感兴趣的点,然后带着问题去阅读源码:
1)spring security的入口是哪个类
2)spring security工作流程是什么样的
3)spring security用户名密码的认证逻辑是什么样的,密码是加密后比对吗
4)spring security针对每个请求都要去查一次用户信息吗,
5)spring security匿名访问是如何跨过认证授权的
6)spring security权限认证逻辑是什么
接下来我们正式进入源码阶段。
二、spring security入口类
我们在登录接口加上断点,查看方法栈,可以发现,如果以进入spring security模块为入口,那么spring security的入口是DelegatingFilterProxy类:
三、spring security工作流程
DelegatingFilterProxy的doFilter方法最后会反射调用FilterChainProxy过滤器链代理类的doFilter方法。FilterChainProxy过滤器链代理类是spring security模块过滤器链的代理类。我们接着到invokeDelegate方法中看一下:
因为delegate是FilterChainProxy对象,所以这里本质上执行的是FilterChainProxy的doFilter方法,所以我们进入该方法查看:
可以看到过滤器链代理类里面会调用getFilters方法获取一系列的过滤器,接着我们看下其获取过滤器的具体逻辑:
可以看到其会遍历所有实现SecurityFilterChain接口的过滤器链(这属于一个扩展功能,我们可以添加自定义实现的过滤器链),spring security默认有一个实现类就是DefaultSecurityFilterChain,后面会调用该链的匹配方法,如果当前请求与过滤器链匹配,则获取过滤器链中包含的所有过滤器。我们接着到DefaultSecurityFilterChain中看下具体实现:
可以看到DefaultSecurityFilterChain默认会匹配所有请求,接着我们看下其返回的过滤器有哪些:
可以看到一共返回13个过滤器。我们接着向下走:
可以看到其会将获取的过滤器列表以及请求等封装成一个虚拟过滤器链,并调用该链的doFilter方法。接下来我们到该方法中看下具体情况:
这里主要留意两个亮点,一是封装的13个过滤器执行完后会调用ApplicationFilterChain过滤器链。另一个是过滤器处理方法中会接收当前对象,这样在过滤器处理结束时再调用该对象的doFilter的方法,就可以达到遍历过滤器方法的目的。
以上就是spring security功能实现的主流程,因为过滤器比较多,我们不一个个去看,我们后面可以直接看感兴趣的过滤器的处理方法逻辑。接下来我们根据前面的兴趣点一个个查看。
四、spring security用户名密码校验逻辑(基于若依框架)
用户名密码校验一般是再登录方法中进行的处理,这里我们直接到login接口中进行查看:
可以看到校验是调用authenticationManager的处理方法,我们接着进里面查看:
通过断点可以知道authenticationManager的实现类是WebSecurityConfigurerAdapter中的内部,所以我们进入看下处理逻辑:
这里先简单说明一个概念,就是用户名密码的认证基本都是通过provider进行执行的,providerManager则是管理provider的类。我们接着看上一步的逻辑,因为providerManager中包含的AnonymousAuthenticationProvider不支持该认证,所以会调用其父类的处理方法:
这里的parent还是providerManager对象(注意这个providerManager跟当前的providerManager不是同一个对象,可以看它们的对象标识后缀是不同的,其只是逻辑上的parent,而不是继承关系)。我们接着进入providerManager的authenticate方法:
可以看到这次的provider是DaoAuthenticationProvider,并且支持当前用户名密码校验,我们进入看下详细的校验逻辑:
因为DaoAuthenticationProvider继承AbstractUserDetailsAuthenticationProvider类,且没有重写authenticate方法,所以实际进入的是后者的处理逻辑中,我们接着向下看:
这里我们先看下如何从数据库拿用户信息:
这里可以看到,具体如何拿用户信息的逻辑是开放给用户的,需要我们自己去实现这块的逻辑。接着我们看拿到用户信息后如何进行校验:
首先是前置校验,其主要是校验用户的一些属性是否符号要求,这块的代码也要求用户自己实现。通过debug可以看到,调用的都是UserDetails自己的处理方法,而UserDetails是一个接口,其实现类是用户自己编辑的LoginUser。我们接着看下面的校验方法:
在这终于可以看到密码的校验逻辑了,其将登录传入的密码和自定义实现UserDetailsService接口查询出的用户信息进行比较,以此进行验证。
注意,spring secutiry用户名密码校验的过程中,我们需要参与的有两点:一是实现UserDetailsService接口,其包含根据用户名获取用户信息的接口。二是实现UserDetails接口,其包含了查出的基本用户信息,以及一些自定义的校验方法。
五、总结
1、spring security的入口是DelegatingFilterProxy
2、spring security工作流实现是通过过滤器链实现的
3、spring security认证过程是根据用户名从数据库查密码,然后跟页面传入的密码进行比对。
4、认证过程主要是基于若依框架的逻辑进行,如果不是该框架,可能登录认证的过程与之不一样
5、因为后面还有很多要写的,所以这里分两篇文章编辑。