Spring Security 的权限验证

原文链接:https://blog.gaoyuexiang.cn/2020/06/13/spring-security-authorization/
内容无差别。

在前面的文章里,我们对 Spring Security
进行权限验证的组件有了大致的了解,我们首先来回顾并探究一下细节。

本文涉及到的组件

FilterSecurityInterceptor

这是 AbstractSecurityInterceptor 的一个子类,并且实现了 Filter
接口,负责调用父类的 beforeInvocation()afterInvocatio()
finallyInvocation() 方法以及一些 Servlet 相关的工作。
真正处理权限验证的代码,其实在父类中。 它存在的意义就是为了能在 Filter
中进行权限验证。

这个 Filter 默认总是被安排在 SecurityFilterChain
的最后,因为需要保证它在所有的身份认证相关的 Filter 之后。

AbstractSecurityInterceptor

这个类实现了真正的权限验证的逻辑,它有多个子类,是为了适配不同的技术而存在的,比如上面的
FilterSecurityInterceptor 就是为了适配 Servlet Filter 而存在的。

我们可以关注一下上面提到的三个方法,这是每个子类都会调用的。

子类的实现总是下面的套路:

InterceptorStatusToken token = super.beforeInvocation(secureObject); // 1
try {
  // call target method, eg, filterChain.doFilter()
  // may get a returnedObject
} final {
  super.finallyInvocation(token);
}
super.afterInvocation(token, returnedObject);
  1. secureObject 是一个方法调用,它的类型是 Object,但一般会看到
    MethodInvocation 或者 FilterInvocation 这样的类型。

beforeInfocation 方法

这个方法的目标是调用 AccessDecisionManager.decide() 方法,完成
pre-invocation handling 操作。

在前面的概览中介绍过,AccessDecisionManager.decide()
方法有三个参数。其中的 secureObject 已经被子类传进来了。
那么在真正调用前,就会去获取 Authentication 对象和
Collection<ConfigAttribute> 集合,然后进行 pre-invocation handling
操作。

后面会介绍 ConfigAttribute

如果调用时出现 AccessDecisionException,那么他将会被
ExceptionTranslationFilter 处理。

在通过权限验证之后,就会准备一个 InterceptorStatusToken
对象作为返回值。

在创建 token 之前,会尝试使用 RunAsManager 创建一个 Authentication
对象,如果这个对象不为 null,那么就会把它放入一个
SecurityContext,替换掉 SecurityContextHolder 中原有的那个。

原有的 SecurityContext 总是会被放到 token 中。

关于 RunAsManager :这里的逻辑是替换掉 SecurityContextHolder 中的值,这样在目标方法中看到的 Authentication 对象就是这个 RunAsManager 创建的对象。在目标方法调用完成后,即 finallyInvocation 方法 中,会将原来的 SecurityContext 重新放回 SecurityContextHolder 中。

这样的目的是为了将认证与鉴权流程中的 Authentication 对象与业务方法中的区分开来。

在上面的这些步骤中,还会发出一些 ApplicationEvent,包括:
PublicInvocationEventAuthorizationFailureEvent
AuthorizedEvent

PublicInvocationEvent 只在 Collection<ConfigAttribute> 为空的时候才会发生,而且这种时候不会调用 AccessDecisionManager

afterInfocation 方法

afterInvocation 方法主要目的是为了根据 returnedObject
进行权限验证,这使用到了 AfterInvocationManager
这个接口,这是在概览里没有提到的,它被用来进行
after invocation handling。

在这个方法中,如果有必要的话,就会使用 AfterInvocationManager.decide()
方法来处理 returnedObject,得到一个新的结果作为 returntedObject

这里的有必要是指:

  1. token != null
  2. afterInvocationManager 字段不为空

finallyInfocation 方法

这个方法接收 InterceptorStatusToken 作为参数,只做一件事情:将 token
中的 SecurityContext 对象放回 SecurityContextHolder 中。

这个操作有两个判断条件:

  • token 不为 null

  • token 的 contextHolderRefreshRequiredtrue。当
    SecurityContextHolder 中的值在 beforeInvocation
    中被替换时,这个值才为 true


权限验证的入口 FilterSecurityInterceptor
的介绍就到这里,接下来我们来看看 pre-invocation handling 和 after
invocation handling 的内容,也就是 AccessDecisionManager
AfterInvocationManager

AccessDecisionManager

这是在概览中介绍过的内容,这里可以快速的回顾一下。

image

AccessDecisionManager 是 pre-invocation handling 的入口。
它的三个具体实现会调用多个 AccessDecisionVoter
的实现,然后具体实现的策略来决定如何根据 voter
的结果来判断是否通过身份验证。 每一个 voter 都会根据当前的
Authentication 对象、secureObjectCollection<ConfigAttribute>
来做出是否允许访问的选择。

AccessDecisionManager 的三个实现,其实就是三种根据 voter
结果来决定最终结果的策略,分别是 AffirmativeBasedConsensusBased
UnanimousBased。策略顾名思义,就不解释了。

AfterInvocationManager

之前没有讲 after invocation handling
的部分,是觉得不重要,使用场景不多(其实是自己没遇到)。现在想讲一讲,是因为发现
spring-security-acl 使用到了 after invocation handling
的机制。那么我们就来看看 AfterInvocationManager 是怎么工作的。

acl 的部分涉及一些新的概念,准备单独写一篇。

image

通过这个图,我们可以清楚的了解到,AfterInvocationManager
也只是一个接口。 它的实现 AfterInvocationProviderManager
则是管理了“很多的” AfterInvocationProvider
来真正的执行权限验证的操作。

这里“很多的” AfterInvocationProvider 其实也就只有四个个实现,其中三个都是 acl,包括图里的这两个。

剩下的那个 PostInvocationAdviceProvider 其实也没有真正进行
authorization 操作,而是代理给了 PostInvocationAuthorizationAdvice
处理。 而这个 PostInvocationAuthorizationAdvice 也只有
ExpressionBasedPostInvocationAdvice 这一个实现,也就是基于 SpEL
表达式来进行 authorization 的实现。

而上面提到的所有的 manager 和 provider,都提供了 decide
方法用来做权限验证。 与 AccessDecisionManager.decide()
相比,这些方法多了一个 returnedObject 参数。
这既是因为它需要作为判断条件参与到决策过程中,也是因为它可能会在决策过程中被处理,然后返回一个新的
returnedObject 作为处理后的结果。

ConfigAttribute

这个类是用来存储我们的 Security 的配置的。

举个例子,下面的代码就会生成相应的 ConfigAttribute

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
      .mvcMatchers("hello")
      .hasAuthority("test")
      .anyRequest()
      .authenticated();
}

上面的代码定义了:

  • 访问 /hello 的请求需要具有 test 权限

  • 其他任意请求,需要通过身份验证(不允许匿名访问)

这样我们就能得到这样的 ConfigAttribute

image

这是 FilterSecurityInterceptor 的截图。 其中的
securityMetadataSource 存储了很多的 ConfigAttribute
AbstractSecurityInterceptor 通过子类实现的
obtainSecurityMetadataSource 方法获取到它,然后通过它获取到本次使用的
Collection<ConfigAttribute>

截图中的 requestMap 保存了 RequestMatcher
Collection<ConfigAttribute> 的关系。

当我们请求 /hello 时,就会得到第一个
Collection<ConfigAttribute>,也就是包含了 hasAuthority('test')
的那一个。 当我们请求其他接口时,就会得到第二个。

接着,这些被获取到的 ConfigAttribute 就可以被后续的验证逻辑使用到。

总结

本文介绍了 Spring Security Authorization,并着重介绍了
FilterSecurityInterceptor 如何在 SecurityFilterChain 的最后使用
AccessDecisionManagerAfterInvocationManager 来实现
pre-invocation handling 和 after invocation handling。

对于 AccessDecisionManager
AfterInfocationManager,则没有详细介绍内部的逻辑,而是介绍了它们如何利用子类和其他接口来完成权限验证的。其内部具体的细节逻辑,读者可以自己研究。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339