一、简述
我在 SpringSecurity 中做了不少的实验,但总感觉对这个框架还是有点模糊,最近在复习 SpringSecurity,进行到角色继承时,发现角色继承并没有起作用,折腾了很久最后在 SpringSecurity 的 issue 中找到了解决方法,主要问题就是 6.0.1 的版本还没有很好地兼容 RoleHierarchy 这个 Bean。
二、通常做法
通常我们做角色继承只需要自定义注入一个 RoleHierarchy
的 Bean 就可以了,就像这样:
@Bean
static RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_user");
return hierarchy;
}
三、试验
准备三个接口,其中 getAll
不设置访问权限,getUser
只允许有 User 权限的用户访问,getAdmin
只允许有 Admin 权限的用户访问。
@RequestMapping("/getAll")
public String getAll(@RequestParam("msg") String msg) {
return msg + " ALL";
}
@RequestMapping("/getUser")
@PreAuthorize("hasRole('user')")
public String getUser(@RequestParam("msg") String msg) {
return msg + " USER";
}
@RequestMapping("/getAdmin")
@PreAuthorize("hasRole('ADMIN')")
public String getAdmin(@RequestParam("msg") String msg) {
return msg + " ADMIN";
}
然后向登录接口发送请求,在这里用户 1111 的角色是管理员,111 则是普通用户,这两个账号是在保存再数据库中的,登录成功后返回用户名
之后访问 getAll
访问成功,再访问 getAdmin
也能访问到,最后再访问 getUser
看看角色继承是否有效
这里报 403 禁止访问了,为了探究是哪出的问题,我们在类 SecurityExpressionRoot
的 hasRole
方法中打个断点,之后一直步入到方法 hasAnyAuthorityName
,该方法的第一行是将方法 getAuthoritySet
的结果放到 roleSet
中,根据名字这个方法应该是获取当前用户的角色集合。
步过该方法,发现这集合里面只有 ROLE*ADMIN
方法 hasAnyAuthorityName
后面的部分则是把 haseRole
中的角色加上前缀 ROLE*,并判断这个角色是否在 roleSet
里,那这肯定是不包括的,最后返回 False,所以就访问不到这个接口了。
我们重新在发送一次请求到 getUser
,步入方法 getAuthoritySet
,
其中关键的第三行可以看到就是判断当前对象的 roleHierachy
是否为空,不为空则将返回所有可访问权限的集合
之后将权限集合转换为 Set
但在这里 roleHierachy
是 null 啊,what?我们配置的角色继承哪去了?
后面去翻了翻 SpringSecurity 的文档,它给的示例是这样的
@Bean
AccessDecisionVoter hierarchyVoter() {
RoleHierarchy hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
"ROLE_STAFF > ROLE_USER\n" +
"ROLE_USER > ROLE_GUEST");
return new RoleHierarchyVoter(hierarchy);
}
按照这个方法写了之后依旧还是不行。。。
idea 也提示类 AccessDecisionVoter
和 RoleHierarchyVoter
已经弃用了,进入这两个类都提示改用 AuthorizationManager
在 SpringSecurity 的一个 issue 中发现有关文档中给的示例无效的问题
后面的回复中有一个解决方法。
Thanks for the report, @istoony.
RoleHierarchy
bean configuration is not fully ported over as of 6.0.x. As such, I think what should be done here is add a note about that in the documentation and then update it once completed. I’ve also added #12783 detailing what needs to be done to supportRoleHierarchy
bean configuration.
In the meantime, to configureRoleHierarchy
for pre-post method security, useDefaultMethodSecurityExpressionHandler
:
@Bean
static RoleHierarchy roleHierarchy() {
RoleHierarchy hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
"ROLE_STAFF > ROLE_USER\n" +
"ROLE_USER > ROLE_GUEST");
return new RoleHierarchyVoter(hierarchy);
}
@Bean
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}
And to configure it for filter security, use the
access(AuthorizationManager)
method instead ofhasRole
, like so:
AuthorityAuthorizationManager<RequestAuthorizationContext> hasRoleUser =
AuthorityAuthorizationManager.hasRole("USER");
hasRoleUser.setRoleHierarchy(roleHierarchy);
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/needs/user/**").access(hasRoleUser)
.anyRequest().authenticated()
)
// ...
按照这个示例将角色继承的配置修改为
@Bean
static RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_user");
return hierarchy;
}
@Bean
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}
经过测试,角色为 ADMIN 的用户都可以访问到 hasRole("user")
的接口,并且 user 角色也还是访问不了 hasRole("ADMIN")
的接口,角色继承生效了!
通常做法无效的原因
在 6.1.0-SNAPSHOT 版本的文档中对应角色继承的位置有这么一个提示
RoleHierarchy
这个 Bean 还没有适配@EnableMethodSecurity 这个注解,需要等到这个 issues 修完。