SpringSecurity核心
Spring源码篇-ApplicationContext
前面通过手写IoC,DI、AOP和Bean的配置。到最后ApplicationContext的门面处理,对于Spring相关的核心概念应该会比较清楚了。接下来我们就看看在Spring源码中,对于的核心组件是如何实现的。
一、ApplicationContext
ApplicationContext到底是什么?字面含义是应用的上下文。这块我们需要看看ApplicationContext的具体的结构。
通过ApplicationContext实现的相关接口来分析,ApplicationContext接口在具备BeanFactory的功能的基础上还扩展了 应用事件发布
,资源加载
,环境参数
和 国际化
的能力。然后我们来看看ApplicationContext接口的实现类的情况。
在ApplicationContext的实现类中有两个比较重要的分支 AbstractRefreshableApplicationContext
和 GenericApplicationContext
.
二、BeanFactory
上面分析了 ApplicationContext
接口的结构。然后我们来看看 BeanFactory
在ApplicationContext中具体的实现是怎么样的
可以看到具体的实现是 DefaultListableBeanFactory .然后我们来看看他的体系结构
BeanFactory的继承体系
三、BeanDefinition
然后我们来了解下ApplicationContext是如何来加载Bean定义的。具体代码我们需要分为XML配置文件和基于注解的两种方式来看。
1.基于XML方式
我们先定义对应的application.xml文件
然后我们的测试类代码
处理的过程 解析XML --> BeanDefinition --> BeanDefinitionRegistry --> BeanFactory
2.基于注解方式
然后来看看基于注解方式的使用的情况。首先是我们的配置类
然后是我们的测试类
注解使用有两种方法:
- 配置扫描路径
- 配置@Configuration的注解类
2.1 this构造方法
在this的构造方法中会完成相关的配置处理。
首先是AnnotatedBeanDefinitionReader(this)方法。会完成核心的ConfigurationClassPostProcessor的注入。ConfigurationClassPostProcessor 会完成@Configuration相关的注解的解析
this.scanner其实就是创建了一个对应的扫描器
2.2 扫描实现
扫描就需要进入到scan方法中。
完成相关的注册
2.3 @Configuration
@Configuration的解析其实是在refresh方法中来实现的。
3.小结
通过上面的分析其实我们已经对Bean定义的扫描,解析和注册过程有了一定的了解。归纳为:
- reader解析XML,完成xml方法配置的bean定义
- scanner扫描指定包下的类,找出带有@Component注解的类,注册成Bean定义
- 通过ConfigurationClassPostProcessor对带有@Configuration注解的类进行处理,解析它上面的注解,以及类中带有@Bean 注解,加入这些的Bean的定义。
4.BeanDefinition
;然后我们来看看BeanDefinition的继承结构
继承属性访问器和元数据接口,增加了Bean定义操作,实现了数据和操作解耦。属性访问器和元数据接口接着往下看。
4.1 BeanMetadataElement
BeanMetadataElement提供了获取数据源的方式,也就是可以指导Bean是来自哪个类。
4.2 BeanMetadataAttribute元数据属性
实现了元数据接口,增加了属性的名字和值。。
4.3 AttributeAccessor属性访问器
AttributeAccessor用来给Bean定义了增删改查属性的功能
4.4 AttributeAccessorSupport属性访问抽象实现类
内部定义了1个map来存放属性。
4.5 BeanMetadataAttributeAccessor元数据属性访问器
继承AttributeAccessorSupport具备属性访问功能,实现BeanMetadataElement具备获取元数据功能。 **AbstractBeanDefinition就继承于它,使得同时具有属性访问和元数据访问的功能 **。
结合AbstractBeanDefinition.来看看他们的类图结构
5. BeanDefinition继承体系
5.1 AnnotatedBeanDefinition
增加了2个方法,获取bean所在类的注解元数据和工厂方法元数据,这些数据在进行解析处理的时候需要用到。
该注解有三个具体的实现。ScannedGenericBeanDefinition、AnnotatedGenericBeanDefinition、ConfigurationClassBeanDefinition。
5.2 AbstractBeanDefinition模板类
AbstractBeanDefinition我们可以称之为BeanDefinition的模板类。结构我们上面其实有梳理
通过上面我们可以看到AbstractBeanDefinition 具备了 Bean元数据的获取和属性相关的操作。同时AbstractBeanDefinition的继承结构
5.3 RootBeanDefinition根bean定义
它主要用在spring内部的bean定义、把不同类型的bean定义合并成RootBeanDefinition(getMergedLocalBeanDefinition方法)。没有实现BeanDefinition接口的设置获取父bean定义方法,不支持设置父子beanDefinition。
5.4 ConfigurationClassBeanDefinition
用作ConfigurationClassPostProcessor解析过程中封装配置类的bean定义。
5.5 GenericBeanDefinition
GenericBeanDefinition通用Bean的定义。
5.6 ScannedGenericBeanDefinition
@ComponentScan扫描的bean定义使用。
5.7 AnnotatedGenericBeanDefinition
深入理解HttpSecurity的设计
一、HttpSecurity的应用
在前章节的介绍中我们讲解了基于配置文件的使用方式,也就是如下的使用。
也就是在配置文件中通过 security:http 等标签来定义了认证需要的相关信息,但是在SpringBoot项目中,我们慢慢脱离了xml配置文件的方式,在SpringSecurity中提供了HttpSecurity等工具类,这里HttpSecurity就等同于我们在配置文件中定义的http标签。要使用的话方式如下。
通过代码结果来看和配置文件的效果是一样的。基于配置文件的方式我们之前分析过,是通过标签对应的handler来解析处理的,那么HttpSecurity这块是如何处理的呢?我们来详细分析下。
二、HttpSecurity的类图结构
可以看出HttpSecurity的类图结构相对比较简单,继承了一个父类,实现了两个接口。我们分别来看看他们的作用是什么?
1.SecurityBuilder接口
我们先来看看SecurityBuilder接口,通过字面含义我们就可以发现这是一个帮我们创建对象的工具类。
通过源码我们可以看到在SecurityBuilder中给我们提供了一个build()方法。在接口名称处声明了一个泛型,而build()方法返回的正好是这个泛型的对象,其实就很好理解了,也就是SecurityBuilder会创建指定类型的对象。结合HttpSecurity中实现SecurityBuilder接口时指定的泛型我们可以看出创建的具体对象是什么类型。
可以看出SecurityBuilder会通过build方法给我们创建一个DefaultSecurityFilterChain对象。也就是拦截请求的那个默认的过滤器链对象。
然后进入到doBuild()方法,会进入到AbstractConfiguredSecurityBuilder中的方法
进入到HttpSecurity中可以查看performBuild()方法的具体实现。
在构造方法中绑定了对应的请求匹配器和过滤器集合。
对应的请求匹配器则是 AnyRequestMatcher 匹配所有的请求。当然我们会比较关心默认的过滤器链中的过滤器是哪来的,这块儿我们继续来分析。
2.AbstractConfiguredSecurityBuilder
然后我们再来看看AbstractConfiguredSecurityBuilder这个抽象类,他其实是SecurityBuilder的实现,在这儿需要搞清楚他们的关系。
类型 |
作用 |
SecurityBuilder |
声明了build方法 |
AbstractSecurityBuilder |
提供了获取对象的方法以及控制一个对象只能build一次 |
AbstractConfiguredSecurityBuilder |
除了提供对对象细粒度的控制外还扩展了对configurer的操作 |
然后对应的三个实现类。
首先 AbstractConfiguredSecurityBuilder 中定义了一个枚举类,将整个构建过程分为 5 种状态,也可
以理解为构建过程生命周期的五个阶段,如下:
通过这些状态来管理需要构建的对象的不同阶段。
2.1 add方法
AbstractConfiguredSecurityBuilder中方法概览
我们先来看看add方法。
add 方法,这相当于是在收集所有的配置类。将所有的 xxxConfigure 收集起来存储到 configurers
中,将来再统一初始化并配置,configurers 本身是一个 LinkedHashMap ,key 是配置类的 class,
value 是一个集合,集合里边放着 xxxConfigure 配置类。当需要对这些配置类进行集中配置的时候,
会通过 getConfigurers 方法获取配置类,这个获取过程就是把 LinkedHashMap 中的 value 拿出来,
放到一个集合中返回。
2.2 doBuild方法
然后来看看doBuild方法中的代码
init方法:完成所有相关过滤器的初始化
configure方法:完成HttpSecurity和对应的过滤器的绑定。
3.HttpSecurity
HttpSecurity 做的事情,就是进行各种各样的 xxxConfigurer 配置
HttpSecurity 中有大量类似的方法,过滤器链中的过滤器就是这样一个一个配置的。我们就不一一介绍
了。每个配置方法的结尾都会来一句 getOrApply,这个是干嘛的?我们来看下
getConfigurer 方法是在它的父类 AbstractConfiguredSecurityBuilder 中定义的,目的就是去查看当前
这个 xxxConfigurer 是否已经配置过了。
如果当前 xxxConfigurer 已经配置过了,则直接返回,否则调用 apply 方法,这个 apply 方法最终会调
用到 AbstractConfiguredSecurityBuilder#add 方法,将当前配置 configurer 收集起来
HttpSecurity 中还有一个 addFilter 方法.
这个 addFilter 方法的作用,主要是在各个 xxxConfigurer 进行配置的时候,会调用到这个方法,
(xxxConfigurer 就是用来配置过滤器的),把 Filter 都添加到 fitlers 变量中。
小结:这就是 HttpSecurity 的一个大致工作流程。把握住了这个工作流程,剩下的就只是一些简单的重
复的 xxxConfigurer 配置了
SpringSecurity请求流转的本质
1. SpringSecurity核心源码分析
分析SpringSecurity的核心原理,那么我们从哪开始分析?以及我们要分析哪些内容?
- 系统启动的时候SpringSecurity做了哪些事情?
- 第一次请求执行的流程是什么?
- SpringSecurity中的认证流程是怎么样的?
1.1 系统启动
当我们的Web服务启动的时候,SpringSecurity做了哪些事情?当系统启动的时候,肯定会加载我们配置的web.xml文件
web.xml中配置的信息:
- Spring的初始化(会加载解析SpringSecurity的配置文件)
- SpringMVC的前端控制器初始化
- 加载DelegatingFilterProxy过滤器
Spring的初始化操作和SpringSecurity有关系的操作是,会加载介绍SpringSecurity的配置文件,将相关的数据添加到Spring容器中
SpringMVC的初始化和SpringSecurity其实是没有多大关系的
DelegatingFilterProxy过滤器:拦截所有的请求。而且这个过滤器本身是和SpringSecurity没有关系的!!!在之前介绍Shiro的时候,和Spring整合的时候我们也是使用的这个过滤器。 其实就是完成从IoC容器中获取DelegatingFilterProxy这个过滤器配置的 FileterName 的对象。
系统启动的时候会执行DelegatingFilterProxy的init方法
init方法的作用是:从IoC容器中获取 FilterChainProxy的实例对象,并赋值给 DelegatingFilterProxy的delegate属性
1.2 第一次请求
客户发送请求会经过很多歌Web Filter拦截。
然后经过系统启动的分析,我们知道有一个我们定义的过滤器会拦截客户端的所有的请求。DelegatingFilterProxy
当用户请求进来的时候会被doFilter方法拦截
invokeDelegate
所以在此处我们发现DelegatingFilterProxy最终是调用的委托代理对象的doFilter方法
FilterChainProxy
过滤器链的代理对象:增强过滤器链(具体处理请求的过滤器还不是FilterChainProxy ) 根据客户端的请求匹配合适的过滤器链链来处理请求
doFilterInternal
获取到了对应处理请求的过滤器链
SpringSecurity中处理请求的过滤器中具体处理请求的方法
主要过滤器的介绍
https://www.processon.com/view/link/5f7b197ee0b34d0711f3e955
ExceptionTranslationFilter
ExceptionTranslationFilter是我们看的过滤器链中的倒数第二个,作用是捕获倒数第一个过滤器抛出来的异常信息。
FilterSecurityInterceptor
做权限相关的内容
ExceptionTranslationFilter 处理异常的代码
当用第二次提交 http://localhost:8082/login时 我们要关注的是 DefaultLoginPageGeneratingFilter 这个过滤器
generateLoginPageHtml
第一次请求的完整的流程
页面调试也可以验证我的推论
Spring源码手写篇-手写AOP
手写IoC和DI后已经实现的类图结构。
一、AOP分析
1.AOP是什么?
AOP[Aspect Oriented Programming] 面向切面编程,在不改变类的代码的情况下,对类方法进行功能的增强。
2.我们要做什么?
我们需要在前面手写IoC,手写DI的基础上给用户提供AOP功能,让他们可以通过AOP技术实现对类方法功能增强。
3.我们的需求是什么?
提供AOP功能!,然后呢?… 没有了。关键还是得从上面的定义来理解。
二、AOP概念讲解
上面在分析AOP需求的时候,我们介绍到了相关的概念,Advice、Pointcuts和weaving等,首先我们来看看在AOP中我们会接触到的相关的概念都有哪些。
更加形象的描述
然后对于上面的相关概念,我们就要考虑哪些是用户需要提供的,哪些是框架要写好的?
思考:Advice,Pointcuts和Weaving各自的特点
三、切面实现
通过上面的分析,我们要设计实现AOP功能,其实就是要设计实现上面分析的相关概念对应的组件。
1.Advice
1.1 面向接口编程
Advice:通知,是由用户提供的,我们来使用,主要是用户提供就突出了 多变性
。针对这块我们应该怎么设计?这里有两个问题:
- 我们如何能够识别用户提供的东西呢?用户在我们写好框架后使用我们的框架
- 如何让我们的代码隔绝用户提供的多变性呢?
针对这种情况我们定义一套标准的接口,用户通过实现接口类提供他们不同的逻辑。是否可行?
这里有个重要的设计原则大家要注意: 如何应对变化,通过面向接口编程来搞定!!!
我们先定义一个空的接口,可以先思考下我们为什么定义一个空的接口呢?
1.2 Advice的特点分析
Advice的特点:可选时机,可选择在方法执行前、后、异常时进行功能的增强
结合上面的情况我们可以分析出Advice通知的几种情况
- 前置增强-Before
- 后置增强-AfterReturn
- 环绕增强-Around
- 最终通知-After
- 异常通知-Throwing
有这么多的情况我们应该要怎么来实现呢?我们可以定义标准的接口方法,让用户来实现它,提供各种具体的增强内容。那么这四种增强相关的方法定义是怎样的呢?我们一一来分析下。
1.3 各种通知分析
1.3.1 前置增强
前置增强:在方法执行前进行增强。
问题1:它可能需要的参数?
目的是对方法进行增强,应该需要的是方法相关的信息,我们使用它的时候能给如它的就是当前要执行方法的相关信息了
问题2:运行时方法有哪些信息?
- 方法本身 Method
- 方法所属的对象 Object
- 方法的参数 Object[]
问题3:前置增强的返回值是什么?
在方法执行前进行增强,不需要返回值!
1.3.2 最终通知
最终通知:在方法执行后进行增强
问题1:它可能需要的参数?
- 方法本身 Method
- 方法所属的对象 Object
- 方法的参数 Object[]
- 方法的返回值 Object 可能没有
问题2:它的返回值是什么?
这个就需要看是否允许在After中更改返回的结果,如果规定只可用、不可修改返回值就不需要返回值
1.3.3 后置通知
后置增强:在方法执行后进行增强
问题1:他可能需要的参数
- 方法本身 Method
- 方法所属的对象 Object
- 方法的参数 Object[]
- 方法的返回值 Object
问题2:它的返回值是什么?
这个就需要看是否允许在After中更改返回的结果,如果规定只可用、不可修改返回值就不需要返回值
1.3.4 环绕通知
Around环绕增强:包裹方法进行增强
问题1:他可能需要的参数
- 方法本身 Method
- 方法所属的对象 Object
- 方法的参数 Object[]
问题2:它的返回值是面试?
方法被它包裹,即方法将由它来执行,它需要返回方法的返回值
1.3.5 异常通知
异常通知增强:对方法执行时的异常,进行增强处理
问题1:它可能需要什么参数?
- 一定需要Exception
- 可能需要方法本身 Method
- 可能需要方法所属的对象 Object
- 可能需要方法的参数 Object[]
问题2:它的返回值是什么?
这个就需要看是否允许在After中更改返回的结果,如果规定只可用、不可修改返回值就不需要返回值
1.4 Advice设计
结合上面的分析,我们就可以得出Advice的体系图了
2.Pointcut
2.1 Pointcut的特点有:
- 用户性:由用户指定
- 变化性:用户可灵活指定
- 多点性:用户可以选择在多个点上进行增强
2.2 Pointcut分析
为用户提供一个东西,让他们可以灵活地指定多个方法点,而且我们还能看懂!
思考:切入点是由用户来指定在哪些方法点上进行增强,那么这个哪些方法点如何来表示能满足上面的需求呢?
分析:
- 指定哪些方法,是不是一个描述信息?
- 如何来指定一个方法?
- 如果有重载的情况怎么办?
- 123要求的其实就是一个完整的方法签名
我们还得进一步分析:如何做到多点性和灵活性,在一个描述中指定一类类的某些方法?
- 某个包下的某个类的某个方法
- 某个包下的所有类中的所有方法
- 某个包下的所有类中的do开头的方法
- 某个包下的以service结尾的类中的do开头的方法
- …
也就是我们需要有这样一个表达式能够灵活的描述上面的这些信息。
这个表达式表达的内容有:
而且每个部分的要求是怎么样的呢?
- 包名:有父子特点,要能模糊匹配
- 类名:要能模糊匹配
- 方法名:要能模糊匹配
- 参数类型:参数可以有多个
那么我们设计的这个表达式将被我们用来决定是否需要对某个类的某个方法进行增强,这个决定过程应该是怎么样的?
针对需求我们的选择是:
AspectJ官网:http://www.eclipse.org/aspectj
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号
举例:
2.3 Pointcut设计
通过分析完成我们就该对Pointcut类设计了,接口,类。
思考1:首先考虑切入点应该具有的属性—>切入点表达式
思考2:切入点应对外提供什么行为
思考3:切入点被我们设计用来做什么?
对类和方法进行匹配,切入点应该提供匹配类,匹配方法的行为
思考4:如果在我们设计的框架中要能灵活的扩展切点,我们应该如何设计?
这又是一个要支持可多变的问题,像通知一样,我们定义一套标准接口,定义好基本行为,面向接口编程,屏蔽掉具体的实现。不管哪些方案,都实现匹配类,匹配方法的接口。
案例代码
然后来看看AspectJ的实现
案例代码
3.切面Aspect
搞定了两个难点后,我们来看看用户该如何使用我们提供的东西
为此我们需要创建对应的接口来管理。
4. Advisor
为用户提供更简单的外观,Advisor(通知者)组合Advice和Pointcut。
当然扩展的形式比较多:
或者:
四、织入实现
1. 织入的分析
织入要完成的是什么?织入其实就是要把用户提供的增强功能加到指定的方法上。
思考1:在什么时候织入?
创建Bean实例的时候,在Bean初始化后,再对其进行增强。
思考2:如何确定bean要增强?
对bean类及方法挨个匹配用户配置的切面,如果有切面匹配就是要增强
思考3:如何实现织入?
代理方式
2.织入的设计
为了更好的去设计织入的实现,先整理下AOP的使用流程。
这里我们要考虑匹配、织入逻辑写到哪里?是写在BeanFactory中吗?
这时我们要考虑如果我们直接在BeanFactory中来处理,后续如果还有其他的需求是不是也要在BeanFactory中处理呢?这样操作有什么不好的地方呢?
- BeanFactory代码爆炸,不专情
- 不易扩展
那我们应该要怎么来设计呢?
我们先来回顾下Bean的生产的过程
在这个过程中, 将来会有更多处理逻辑加入到Bean生产过程的不同阶段。我们现在最好是设计出能让我们后面不用再改BeanFactory的代码就能灵活的扩展。
这时我们可以考虑用观察者模式,通过在各个节点加入扩展点,加入注册机制。
那么在这块我们就应用观察者模式来加入一个Bean的后置处理器 BeanPostProcessor
具体的我们在代码中来看看。
Spring源码-Bean的实例化
接下来我们看看Bean的实例化处理
一、BeanDefinition
首先我们来看看BeanDefinition的存放位置。因为Bean对象的实例化肯定是BeanFactory基于对应的BeanDefinition的定义来实现的,所以在这个过程中BeanDefinition是非常重要的,前面的课程讲解已经完成了BeanDefinition的定义。同时根据前面refresh方法的讲解我们知道了BeanFactory的具体实现是 DefaultListableBeanFactory
.所以BeanDefinition的相关信息是存储在 DefaultListableBeanFactory
的相关属性中的。
二、Bean实例的创建过程
然后就是Bean实例的创建过程。这块儿我们可以通过Debug的形式非常直观的看到。
按照这种步骤一个个去分析就OK了。
三、单例对象
在创建单例对象的时候是如何保存单例的特性的?这块我们需要注意下面的代码
然后进入到getSingleton方法中。
创建成功的单例对象会被缓存起来。在 addSingleton 方法中
所以singletonObjects是缓存所有Bean实例的容器
而具体创建单例Bean的逻辑会回调前面的Lambda表达式中的createBean方法
四、单例对象的销毁
然后我们先来看下一个单例Bean对象的销毁过程。定义一个案例
然后我们在测试的案例中显示的调用 close
方法
执行的时候可以看到相关的日志执行了。
进入到close方法中分析,比较核心的有两个位置。在doClose方法中。
具体销毁的代码进入destroyBeans()中查看即可。
在doClose方法中有个提示。registerShutdownHook方法
对应的在web项目中就有对应的调用
这个就是Bean实例化的过程了,当然在实例化中的DI问题我们在下篇文章中重点分析。
SpringSecurity中的权限管理
SpringSecurity是一个权限管理框架,核心是认证和授权,前面已经系统的给大家介绍过了认证的实现和源码分析,本文重点来介绍下权限管理这块的原理。
一、权限管理的实现
服务端的各种资源要被SpringSecurity的权限管理控制我们可以通过注解和标签两种方式来处理。
放开了相关的注解后我们在Controller中就可以使用相关的注解来控制了
然后在页面模板文件中我们可以通过taglib来实现权限更细粒度的控制
然后我们在做用户认证的时候会绑定当前用户的角色和权限数据
二、权限校验的原理
接下来我们看看在用户提交请求后SpringSecurity是如何对用户的请求资源做出权限校验的。首先我们要回顾下SpringSecurity处理请求的过滤器链。如下:
通过前面介绍我们请求,当一个请求到来的时候会经过上面的过滤器来一个个来处理对应的请求,最后在FilterSecurityInterceptor中做认证和权限的校验操作,
1.FilterSecurityInterceptor
我们进入FilterSecurityInterceptor中找到对应的doFilter方法
首先看看FilterInvocation的构造方法,我们可以看到FilterInvocation其实就是对Request,Response和FilterChain做了一个非空的校验。
然后进入到invoke方法中。
所以关键我们需要进入到beforeInvocation方法中
首先是obtainSecurityMetadataSource()方法,该方法的作用是根据当前的请求获取对应的需要具备的权限信息,比如访问/login.jsp需要的信息是 permitAll 也就是可以匿名访问。
然后就是decide()方法,该方法中会完成权限的校验。这里会通过AccessDecisionManager来处理。
2.AccessDescisionManager
AccessDescisionManager字面含义是决策管理器。源码中的描述是
AccessDescisionManager有三个默认的实现
2.1 AffirmativeBased
在SpringSecurity中默认的权限决策对象就是AffirmativeBased。AffirmativeBased的作用是在众多的投票者中只要有一个返回肯定的结果,就会授予访问权限。具体的决策逻辑如下:
2.2 ConsensusBased
ConsensusBased则是基于少数服从多数的方案来实现授权的决策方案。具体看看代码就非常清楚了
上面代码的逻辑还是非常简单的,只需要注意下授予权限和否决权限相等时的逻辑就可以了。决策器也考虑到了这一点,所以提供了 allowIfEqualGrantedDeniedDecisions 参数,用于给用户提供自定义的机会,其默认值为 true,即代表允许授予权限和拒绝权限相等,且同时也代表授予访问权限。
2.3 UnanimousBased
UnanimousBased是最严格的决策器,要求所有的AccessDecisionVoter都授权,才代表授予资源权限,否则就拒绝。具体来看下逻辑代码:
上面看了在SpringSecurity中的各种决策器外我们可以再来看看各种投票器AccessDecisionVoter
3.AccessDecisionVoter
AccessDecisionVoter是一个投票器,负责对授权决策进行表决。表决的结构最终由AccessDecisionManager统计,并做出最终的决策。
AccessDecisionVoter的具体实现有
然后我们来看看常见的几种投票器
3.1 WebExpressionVoter
最常用的,也是SpringSecurity中默认的 FilterSecurityInterceptor实例中 AccessDecisionManager默认的投票器,它其实就是 http.authorizeRequests()基于 Spring-EL进行控制权限的授权决策类。
进入authorizeRequests()方法
而对应的ExpressionHandler其实就是对SPEL表达式做相关的解析处理
3.2 AuthenticatedVoter
AuthenticatedVoter针对的是ConfigAttribute#getAttribute() 中配置为 IS_AUTHENTICATED_FULLY 、IS_AUTHENTICATED_REMEMBERED、IS_AUTHENTICATED_ANONYMOUSLY 权限标识时的授权决策。因此,其投票策略比较简单:
3.3 PreInvocationAuthorizationAdviceVoter
用于处理基于注解 @PreFilter 和 @PreAuthorize 生成的 PreInvocationAuthorizationAdvice,来处理授权决策的实现.
具体是投票逻辑
3.4 RoleVoter
角色投票器。用于 ConfigAttribute#getAttribute() 中配置为角色的授权决策。其默认前缀为 ROLE_,可以自定义,也可以设置为空,直接使用角色标识进行判断。这就意味着,任何属性都可以使用该投票器投票,也就偏离了该投票器的本意,是不可取的。
注意,决策策略比较简单,用户只需拥有任一当前请求需要的角色即可,不必全部拥有 。
3.5 RoleHierarchyVoter
基于 RoleVoter,唯一的不同就是该投票器中的角色是附带上下级关系的。也就是说,角色A包含角色B,角色B包含 角色C,此时,如果用户拥有角色A,那么理论上可以同时拥有角色B、角色C的全部资源访问权限.