文章目录
Authorization架构
用户权限Authorities
在用户请求通过身份认证后,会在上下文中存储用户信息Authentication,其中Authorities是用户的权限
Authorities是GrantedAuthority接口实例的集合(List),GrantedAuthority接口只有一个方法getAuthority(),该方法返回结果类型为String,代表用户的权限。GrantedAuthority由AuthenticationManager管理创建,AccessDecisionManager会读取GrantedAuthority。
在SpringSecurity中只包含了一个GrantedAuthority的具体实现类,SimpleGrantedAuthority,它支持使用字符串表示访问权限。但是在某些业务复杂的场景中,如果字符串无法描述权限时,则需要自己对框架进行扩展。
前置调用处理
AccessDecisionManager
AccessDecisionManager 接口会被Spring Security的AbstractSecurityInterceptor 调用,来控制访问权限,其包含的方法见以下类图:
- decide方法实现了权限判定,传入参数包括Authentication authentication用户信息,Object secureObject被保护的对象,例如一个方法调用,Collection attrs开发人员根据业务进行的权限配置信息。当用户的权限不符合被保护对象的要求时,会抛出AccessDeniedException异常
- supports(ConfigAttribute attribute): 会在启动时被调用,判断权限配置信息能否支持
- supports(Class clazz) :判断目标类能否支持
基于选举机制的AccessDecisionManager
Spring Security中包含多种AccessDecisionManager接口的实现,它们都是基于选举机制的。
AbstractAccessDecisionManager中聚合了AccessDecisionVoter ,AccessDecisionVoter 用于实现投票选举过程,来判定是否抛出AccessDeniedException 异常
AccessDecisionVoter接口包含三个方法:
- vote:返回int结果,其值有三个ACCESS_GRANTED = 1,ACCESS_ABSTAIN = 0,ACCESS_DENIED = -1,当AccessDecisionVoter不能判断权限时返回ACCESS_ABSTAIN(弃权),否则就要返回ACCESS_GRANTED(授权)或ACCESS_DENIED(拒绝)
- boolean supports(ConfigAttribute attribute) :功能同AccessDecisionManager
- boolean supports(Class clazz):功能同AccessDecisionManager
Spring Security中有三种AccessDecisionManager的具体实现,实现了三种不同的投票机制:
- ConsensusBased:所有投票者通过则授权,所有投票者拒绝则拒绝
- AffirmativeBased:当有一个投票者同意则授权,所有投票者拒绝才决绝
- UnanimousBased :当所有投票者同意才授权,有一个投票者拒绝则拒绝
以上三种实现都提供了参数配置,控制所有投票者都弃权的情况下该如何处理。如果以上都不能满足业务需求,可以自己实现AccessDecisionManager接口进行扩展。
RoleVoter
最常用的AccessDecisionVoter的实现就是RoleVoter,RoleVoter将配置的属性ConfigAttribute视作角色名字,如果用户被分配的相应的角色就会投赞成票。
RoleVoter会在ConfigAttribute 中包含ROLE_开头的属性时进行选举投票,如果用户的角色包含在ConfigAttribute中则投赞成票,在ConfigAttribute没有就投拒绝票,如果ConfigAttribute没有ROLE_开头的属性则投弃权票
AuthenticatedVoter
AuthenticatedVoter是用来区分anonymous, fully-authenticated 和remember-me用户认证的,例如有些网站支持一些特定访问路径支持remember-me认证,其它的路径则需要用户进行登录
其它AccessDecisionVoter
AccessDecisionVoter还有其他类型,例如WebExpressionVoter用来处理web授权投票,支持表达式。也可以根据自己的需求自定义AccessDecisionVoter
后置调用处理
尽管AbstractSecurityInterceptor会在执行被保护的对象前调用AccessDecisionManager,但一些应用需要修改被保护对象的返回值,虽然可以通过AOP组件来实现这个目的,但Spring Security也提供了一种便捷的方式。
下图为事后调用处理AfterInvocationManager的类图:
接口AfterInvocationManager只有一个实现类AfterInvocationProviderManager,AfterInvocationProviderManager聚合了AfterInvocationProvider的List,AfterInvocationProvider可以修改被保护对象的返回值。
HTTP请求访问控制 FilterSecurityInterceptor
FilterSecurityInterceptor提供HTTPServletRequest的访问控制服务,FilterSecurityInterceptor会被编排进SecurityFilterChain中。
(1)FilterSecurityInterceptor从SecurityContextHolder中提取Authentication信息
(2)FilterSecurityInterceptor创建FilterInvocation,FilterInvocation的构造方法参数为HttpServletRequest, HttpServletResponse, FilterChain,即过滤器的入参
(3)将FilterInvocation 传递给SecurityMetadataSource而获取ConfigAttribute
(4)将Authentication, FilterInvocation, ConfigAttributes传递给AccessDecisionManager
(5)如果访问授权为拒绝,会抛出AccessDeniedException异常,ExceptionTranslationFilter会对此异常进行处理
(6)如果访问授权通过,FilterSecurityInterceptor 会继续调用doFilter方法,继续执行过滤链
Spring Scurity默认会对所有请求进行访问控制,其默认的配置类似以下:
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
);
}
也可以根据自身需求进行客户化配置:
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize ①
.mvcMatchers("/resources/**", "/signup", "/about").permitAll() ②
.mvcMatchers("/admin/**").hasRole("ADMIN") ③
.mvcMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") ④
.anyRequest().denyAll() ⑤
);
}
① authorizeRequests方法可以配置多条访问控制规则
② 任意用户可以访问以"/resources/****“开头, 等于”/signup", 等于"/about"的URL**
③ 以"/admin/**“开头的URL可以由拥有“ROLE_ADMIN”角色的用户访问
④ 以”/db/“开头的URL可以由同时拥有"ROLE_ADMIN” and "ROLE_DBA"的用户访问
⑤ 其他没有赔匹配的URL都不允许访问,这是一个推荐的配置策略,以防用户忘记配置控制规则
表达式访问控制规则
常见的表达式方法
Expression | Description |
---|---|
hasRole(String role) | 如果当前用户有指定的角色则返回true,例如hasRole(‘admin’) |
hasAnyRole(String… roles) | 如果当前用户拥有指定用户的任意一个则返回true,例如hasAnyRole(‘admin’, ‘user’) |
hasAuthority(String authority) | 如果当前用户有指定的权限则返回true,例如hasAuthority(‘read’) |
hasAnyAuthority(String… authorities) | 如果当前用户有指定的权限中的任意一个则返回true,例如hasAnyAuthority(‘read’, ‘write’) |
principal | 获取principal对象,代表当前用户 |
authentication | 从SecurityContext提取Authentication 对象 |
permitAll | 返回true |
denyAll | 返回false |
isAnonymous() | 如果当前用户是匿名用户则返回true |
isRememberMe() | 如果当前用户是remember-me用户 |
isAuthenticated() | 如果当前用户是非匿名用户则返回true |
Web Security Expressions
当在访问权限配置中使用表达式时,AccessDecisionManager会调用 WebExpressionVoter来进行控制权限的选举投票。
(1) 在Web Security Expressions中引用bean,使用@beanname
http
.authorizeRequests(authorize -> authorize
.antMatchers("/user/**").access("@webSecurity.check(authentication,request)")
...
)
(2) Web Security Expressions Path Variables
http
.authorizeRequests(authorize -> authorize
.antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
...
);