在用户请求通过身份认证后,会在上下文中存储用户信息Authentication,其中Authorities是用户的权限
Authorities是GrantedAuthority接口实例的集合(List),GrantedAuthority接口只有一个方法getAuthority(),该方法返回结果类型为String,代表用户的权限。GrantedAuthority由AuthenticationManager管理创建,AccessDecisionManager会读取GrantedAuthority。
在SpringSecurity中只包含了一个GrantedAuthority的具体实现类,SimpleGrantedAuthority,它支持使用字符串表示访问权限。但是在某些业务复杂的场景中,如果字符串无法描述权限时,则需要自己对框架进行扩展。
AccessDecisionManager 接口会被Spring Security的AbstractSecurityInterceptor 调用,来控制访问权限,其包含的方法见以下类图:
Spring Security中包含多种AccessDecisionManager接口的实现,它们都是基于选举机制的。
AbstractAccessDecisionManager中聚合了AccessDecisionVoter ,AccessDecisionVoter 用于实现投票选举过程,来判定是否抛出AccessDeniedException 异常
AccessDecisionVoter接口包含三个方法:
Spring Security中有三种AccessDecisionManager的具体实现,实现了三种不同的投票机制:
最常用的AccessDecisionVoter的实现就是RoleVoter,RoleVoter将配置的属性ConfigAttribute视作角色名字,如果用户被分配的相应的角色就会投赞成票。
RoleVoter会在ConfigAttribute 中包含ROLE_开头的属性时进行选举投票,如果用户的角色包含在ConfigAttribute中则投赞成票,在ConfigAttribute没有就投拒绝票,如果ConfigAttribute没有ROLE_开头的属性则投弃权票
AuthenticatedVoter是用来区分anonymous, fully-authenticated 和remember-me用户认证的,例如有些网站支持一些特定访问路径支持remember-me认证,其它的路径则需要用户进行登录
AccessDecisionVoter还有其他类型,例如WebExpressionVoter用来处理web授权投票,支持表达式。也可以根据自己的需求自定义AccessDecisionVoter
尽管AbstractSecurityInterceptor会在执行被保护的对象前调用AccessDecisionManager,但一些应用需要修改被保护对象的返回值,虽然可以通过AOP组件来实现这个目的,但Spring Security也提供了一种便捷的方式。
下图为事后调用处理AfterInvocationManager的类图:
接口AfterInvocationManager只有一个实现类AfterInvocationProviderManager,AfterInvocationProviderManager聚合了AfterInvocationProvider的List,AfterInvocationProvider可以修改被保护对象的返回值。
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 |
当在访问权限配置中使用表达式时,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)")
...
);