2023年6月25日发(作者:)
授权系统源码_(⼗)SpringSecurity授权和鉴权原理⼀、SpringSecurity框架中的授权源码分析⼀个完整的权限系统总体上来说可以分为两个⽅⾯:1、认证(Authentication)认证(Authentication)2、授权(Authorization)授权(Authorization)上篇⽂章中我们已经⾃定义了认证流程的⾻架,实现了让框架帮我们管理会话以及持久化登录信息到Session的功能。不过默认情况下,所有⽤户对于所有url,只需要登录就能够访问,这可不是我们想要的。⼀个完整的系统,应该将⽤户以某种维度进⾏分类,不同的⽤户具有不同的权限,从⽽能够访问不同的URL。所以授权的关注点主要有两个:1、⽤户和权限的映射2、权限和Url的映射1和2结合最终实现⽤户和可访问的Url的映射回顾上节的的⾃定义配置中有这么⼀段代码:要想了解SpringSecurity是如何进⾏授权的,就从这段代码⼊⼿。authorizeRequests函数中向HttpSecurity中添加了⼀个配置类,ExpressionUrlAuthorizationConfigurerauthorizeRequestsExpressionUrlAuthorizationConfigurer,关于SecurityConfigurerSecurityConfigurer和SecurityBuilderSecurityBuilder的设计模式我再之前已经介绍了,如果有兴趣可以去看⼀下,能够加深对框架的理解:landexiang:(⼋)从框架设计的⾓度来看按照框架设计的原理,分析⼀个SecurityConfigurer要从四个关键点⼊⼿:继承结构继承结构、构造函数构造函数、init⽅法init⽅法、configure⽅法configure⽅法。1、继承结构从类图上可以直观的发现,授权配置相关功能也是⼀个很复杂设计。有的读者可能没看过类图,这⾥稍微解释⼀下:红⾊的线表⽰内部类,蓝⾊箭头表⽰继承,⽩⾊实线箭头表⽰组合关系(可以理解为A有⼀个属性为B,B作为A这个整体的⼀部分),⽩⾊虚线表⽰依赖关系,图上的依赖关系体现为创建关系(⽐如A中new了类型为B的对象)。从类图中观察,授权功能相关配置⼜可以分为两个部分:ConfigurerConfigurer和RegistryRegistry1.1 授权相关的ConfigurerConfigurer指的就是SecurityConfigurer,Registry是Configurer的内部定义,具体是什么请请继续往后看。AbstractHTTPConfigurer就不⽤了解了,HttpSecurity中使⽤的SecurityConfigurer⼏乎都继承于这个抽象类,提供了两个辅助性的功能,其最主要的功能还是体现在语义层次上,即表⽰⼦类是服务于HttpSecurity的。所以图上的类我们从AbstractInterceptUrlConfigurerAbstractInterceptUrlConfigurer开始着⼿。。先来看下这个抽象类的注释:从注释中可以了解到AbstractInterceptUrlConfigurer主要是给FilterSecurityInterceptor、其他的SecurityConfigurer的sharedObject、AuthenticationManager提供⽀持。AbstractInterceptUrlConfigurer有两个实现类从HttpSecurity的配置可以看出,框架中默认使⽤的实现类为ExpressURLAuthorizationConfigurer。ExpressURLAuthorizationConfigurer。通过注释可以了解到ExpressURLAuthorizationConfigurer在抽象⽗类AbstractInterceptUrlConfigurer的基础之上额外提供了——基于SPEL表达式的授权功能,并且⾄少有⼀个@RequestMapping注解所代表的url映射到⼀个ConfigAttribute,ExpressURLAuthorizationConfigurer才有意义。从注释中可以了解到两个信息:1、SpringSecurity框架默认是使⽤SPEL表达式来对URL进⾏授权的2、ConfigAttribute对象和@RequestMapping映射的URL有⼀定联系另外可以发现ExpressURLAuthorizationConfigurer中还有⼏个内部类,⼀些是⾃⼰的,⼀些是定义在抽象⽗类⾥的。1.2 授权相关的Registry从类图上来看,registry的顶级类为抽象类AbstractRequestMatcherRegistryAbstractRequestMatcherRegistry,从注释来看主要是⽤来注册RequestMatcher类,RequestMatcher的提供了url匹配的功能,包括请求⽅法,请求路径等匹配⽅式。AbstractConfigAttributeRequestMatcherRegistry作为AbstractRequestMatcherRegistry的抽象⼦类,在⽗类功能的基础之AbstractConfigAttributeRequestMatcherRegistry上,额外提供了UrlMapping和ConfigAttribute相关的操作。如下图所⽰前⾯已经提到过ExpressURLAuthorizationConfigurer注释表明@RequestMapping映射的url和ConfigAttribute有⼀定的联系,从这就可以得出结论,对于已授权的url会被封装成⼀个UrlMapping对象,其中既包含了匹配url的RequestMatcher对象⼜包含了表⽰url权限信息的ConfigAttribute对象,不过⽬前只是猜测,具体是不是还要继续看源码。抽象⼦类AbstractInterceptUrlRegistryAbstractInterceptUrlRegistry⼜继承于AbstractConfigAttributeRequestMatcherRegistry,定义在抽象配置⽗类AbstractInterceptUrlConfigurerAbstractInterceptUrlConfigurer中从代码中可以看出AbstractInterceptUrlRegistry提供了AccessDecisionManager类的set功能。AccessDecisionManager提供了请求鉴权的功能。ExpressionInterceptUrlRegistry继承于AbstractInterceptUrlRegistry,从源码看,除了具有⽗类特性外,提供了两个功能ExpressionInterceptUrlRegistry1、创建请求的url授权的结果类AuthorizatedUrl和MvcMatchersAuthorizedUrl2、设置处理SPEL表达式的handler从源码来看,每个类所提供的功能都⽐较简单,但是由于继承链⽐较长和复杂,所以理解起来还是会有些晦涩。所以下⾯就来从框架实际的运⽤来进⾏源码解析。2、SecurityConfigurer的构造函数分析⼀个SecurityConfigurer到底做了哪些事,在粗略了解了相关类的继承结构之后,⾸先应该看得是构造函数。在构造函数中创建了ExpressionInterceptUrlRegistry对象,这个registry的功能在前⾯已经简单叙述到了。不过有⼀点需要注意:在HttpSecurity对象中设置授权相关配置时,创建了ExpressionUrlAuthorizationConfigurer配置后,返回值是ExpressionInterceptUrlRegistry,也就是authorizeRequests()⽅法的返回值是ExpressionInterceptUrlRegistry,所以后⾯的.anyRequest().authenticated()都是对于ExpressionInterceptUrlRegistry的设置。刚好通过这个设置来看下Registry相关类的详细功能:ANY_REQUEST表⽰⼀个matches始终返回true的RequestMatcher类,也就是匹配任何url。requestMatchers是AbstractRequestMatcherRegistryAbstractRequestMatcherRegistry的⽅法,chainRequstMatchers是抽象⼦类AbstractConfigAttributeRequestMatcherRegistryAbstractConfigAttributeRequestMatcherRegistry中实现的。⽽chainRequestMatcherInternal则是⼦类ExpressionUrlAuthorizationConfigurer中实现的,最终创建了⼀个AuthorizedUrl对象:所以.anyRequests()⽅法的返回值为AuthorizedUrl,后⾯的authenticated()是AuthorizedUrl提供的⽅法。从注释来看ticated⽅法的功能为指定当前对象中所有requestMatchers所能匹配的url对于所有已认证的⽤户开放。从源码来看,SpringSecurity框架是将表⽰匹配所有请求是AnyRequestMatcher和表⽰已认证权限的"authenticated"字符串作为ConfigurerAttribute构造了⼀个UrlMapping对象来将url和权限聚合,然后添加到ExpressionInterceptUrlRegistryExpressionInterceptUrlRegistry对象中,这和我们在上⾯根据类的注释信息推测出的结果⼀致。⽽且从源码的属性定义来看,SpringSecurity的权限应该有固定的6种。通过上⾯的源码分析,我们可以知道的是对于Url的访问权限设置是AuthorizedUrl的⼯作,⽽AuthorizedUrl是在AbstractRequestMatcherRegistryAbstractRequestMatcherRegistry抽象类中创建的。如果我们想给url进⾏权限细化,则还需要看下这个类中还提供了哪些⽅法:从源码中可以看出还有额外的三种url映射设置⽅式,不过url匹配使⽤的都是ant风格的表达式。这三种设置分别是:1、固定请求⽅法,但匹配所有url的AuthorizedUrl2、固定请求⽅法,固定url的AuthorizedUrl3、只固定url的AuthorizedUrl⽽且1其实就是使⽤2来进⾏创建的。现在我们已经知道了使⽤antMatchers()antMatchers()⽅法就可以⾃定义拦截Url创建AuthorizedUrl,但是授权除了authenticated,还有哪些⽅式呢?当然是来看下AuthorizedUrl还提供了哪些授权⽅法。not表⽰不具有xxx权限上⾯⼏种是框架提供的默认权限,同来进⾏粗粒度的授权⼯作可以发现框架中除了提供可选的6种默认权限之外,还提供了两种⾃定义的设置⽅式:1、hasRolehasRole2、hasAuthorityhasAuthority通过源码可以看出,其实返回的就是⼀个SPEL表达式,role和authority的不同之处在于如果使⽤hasRole则表⽰使⽤的是url⾓⾊,注册时不能带ROLE_前缀,框架会给你⾃动补上这个前缀。使⽤authority表⽰使⽤的是url权限,则没有任何限制。也就是说通过框架提供的这种特性我们可以对于同⼀个url进⾏两种授权设置,⼀种基于⾓⾊,⼀种基于权限。⽐如我们想设置"/user"所有⼦路径必须具有ROLE_USER⾓⾊或者MODEL_USER_REQUEST权限才能进⾏访问则可以像下⾯这样设置:2、SecurityConfigurer的init⽅法通过上⾯的源码分析,我们已经知道了如何使⽤框架提供的功能去⾃定义url授权,但是我们仍不了解框架是如何鉴权的。所以还需要继续看下相关的源码,才能够进⾏我们⾃⼰的权限系统的设计。之前的⽂章中已经介绍了SpringSecurity的基本设计模式,所以SecurityConfigurer的init⽅法是我们阅读源码时必须了解的。但是通过源码可以看到ExpressURLAuthorizationConfigurer以及其所有⽗类都没有重写init⽅法,根据init⽅法的初衷,可以知道ExpressURLAuthorizationConfigurer是⼀个功能相对⽐较独⽴的模块,因为他没有经过init⽅法设置共享变量。3、SecurityConfigurer的configure⽅法init⽅法是⽤来设置共享变量到SecurityBuilder中的,⽽configure⽅法则是⽤来处理SecurityBuilder的直接属性。ExpressURLAuthorizationConfigurer中并没有重写抽象⽗类AbstractInterceptUrlConfigurerdAbstractInterceptUrlConfigurerd的configure⽅法:metadataSource是之后⽤来鉴权的的元数据,可以看⼀下他有哪些数据,了解⼀下鉴权需要⽤到的信息:从源码来看,metadataSource中只有⼀个处理SPEL表达式的handler和我们授权的url的信息。不过从源码来看,如果设置了多个相同的RequestMatcher,只有最后的会⽣效,前⾯设置的会被覆盖掉,所以我们设想的给同⼀个url即授予⾓⾊⼜授予权限是在默认情况下是⾏不通了~如图所⽰,我们定义了三个antMatcher,但最终元数据中只有两条,因为有两个requestMatcher重复了。configure中只有这个需要特别关注,下⾯就是创建filter了,没什么特别的地⽅。⼆、SpringSecurity中的鉴权源码分析通过上⾯的⼏个关键点,现在我们已经知道如何⾃定义url的授权,也知道了我们定义的授权哪些是有效的,⽽最终的鉴权则是FilterSecurityInterceptorFilterSecurityInterceptor来完成的。。FilterSecurityInterceptor作为⼀个filter,所以鉴权相关肯定是在doFilter⽅法中进⾏的。从源码来看对于同⼀个请求只需要鉴权⼀次,所以对于请求的转发,是不是可以越权访问呢?⽹上搜了⼀下,好像没发现相关资料,因为请求的转发还算做同⼀次请求,所以我也是猜测,以后抽空测验⼀下~。鉴权操作主要是⽗类来做的,核⼼代码只有这⼀⾏。authenticated是我们的登录信息,attributes则是从metadataSource中取出当前访问url所需要的权限信息:⽐如我们访问根⽬录"/",则对应着authenticated这条权限。如果没有找到匹配当前访问url的授权定义,默认情况下不做拦截。不过这⾥有⼀点需要我们注意要我们注意我们来看下attributes是如何取出来的:不是取出所有匹配项,⽽是取出第⼀条匹配项!所以对于同⼀个url,如果定义了不同的antPath都匹配到了,结果是只有定义在前⾯的授定义在前⾯的授权⽣效所以下⾯的定义是错误的权⽣效正确的定义⽅式应该是:将anyRequest的授权放在最后⾯。accessDecisionManager就是⽤来进⾏鉴权的类。框架默认的AccessDecisionManager为:AffirmativeBasedAffirmativeBased框架中⼀共定义了三种decisionManager。我们还是先了解⼀下框架默认⾏为是否满⾜我们的需求:从注释来看AffirmativeBased的鉴权其实是委托给AccessDecisionVoter来做的,并且只要其内部的任意⼀个AccessDecisionVoter鉴权通过,则算作当前请求有权限访问。框架中虽然定义了很多voter,但默认⽤到的只有⼀个,即WebExpressionVoter。我们定义的SPEL表达式权限就是交给这个voter进⾏鉴定的,鉴权⽅法为vote:WebExpressionVoter鉴权有三种结果:弃权,肯定,否定。当返回肯定的时候则表⽰鉴权通过,当返回弃权和否定的时候,将鉴权交给下⼀个voter进⾏。WebExpressionVoter最主要的鉴权是上⾯这段代码,即将⽤户的登陆信息中的权限和SPEL表达式进⾏对⽐:鉴权的代码⼗分复杂,通过debug发现原来是通过反射调⽤了SecurityExpressionRoot类的hasRole⽅法:hasRole⽅法的参数就是我们给url授予的权限信息:从源码可以看到,框架认为系统所具有的权限为从登陆信息中的authorities字段,是⼀个GrantedAuthority类型的List。⽽框架中默认使⽤的GrantedAuthority则是SimpleGrantedAuthority,其实就是⼀个字符串的包装类,现在我们已经知道如何去给⽤户给⽤户赋予权限了了,只需要将权限字符串设置到其登陆信息的authorities字段即可。赋予权限了鉴权很简单,只要⽤户所具有的⾓⾊和url中授予的⾓⾊任⼀匹配,则表⽰鉴权通过。不过有⼀点可能你会奇怪,为什么这⾥是调⽤的hasRole⽽不是hasAuthority函数呢?回过头来看下我们的配置内容:根据前⾯的分析,我们已经知道了对于相同的url,后⾯的配置会覆盖前⾯的所以只有hasRole⽣效了。如果我们来调换⼀下这两个配置的位置:将hasRole放到前⾯,再来看⼀下结果:不出所料,使⽤的是authority进⾏鉴权。三、总结通过本篇⽂章我们知道了框架授权和鉴权的原理。授权:在ExpressionInterceptURLRegistry中注册AuthorizedUrl对象,AuthorizedUrl对象包含了匹配url的RequestMatcher以及表⽰授权权限的AttributeConfigure。权限分为authority和role两种,对于相同的url,后⾯定义的授权会覆盖前⾯的授权。⽽对于不相同但是被多个antPath都匹配到的url,则前⾯的定义会覆盖掉后⾯的定义,也就是说对于覆盖范围越⼴的授权定义,越要放在配置的后⾯。鉴权:使⽤⽤户登录信息中的authorities字段所表⽰的权限和配置中对url的授权进⾏匹配,匹配通过则表⽰该⽤户具有访问权限,匹配不鉴权通过则表⽰该⽤户没有访问权限。使⽤role授权则不能以ROLE作为字符串的开头,⽽且框架会在鉴权时给字符串加上ROLE_前缀。⽽使⽤authority时则没有限制,鉴权时是字符串的完全匹配。所以我们要向⾃定义权限系统其实只需要考虑两个⽅⾯:1、使⽤authoriy还是role进⾏授权2、什么时候将权限设置到⽤户的authorities字段下⼀节就让我们结合前⾯的所有内容来⾃定义⼀个权限系统。
发布者:admin,转转请注明出处:http://www.yc00.com/web/1687680848a31073.html
评论列表(0条)