本文基于目前的SpringSecurity5.3.4来分析,为什么要强调最新版本呢?因为在5.0.11版本中,角色继承配置和现在不一样了。老版本的解决方案我们先不讨论,直接看最新版本是怎么处理的。1.角色继承案例先从一个简单的权限案例说起。创建一个SpringBoot项目,添加SpringSecurity依赖,创建两个测试用户,如下:@Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth.inMemoryAuthentication().withUser("javaboy").password("{noop}123")。roles("admin").and().withUser("江南小雨").password("{noop}123").roles("user");}然后准备三个测试接口,如下:@RestControllerpublicclassHelloController{@GetMapping("/hello")publicStringhello(){return"hello";}@GetMapping("/admin/hello")publicStringadmin(){return"admin";}@GetMapping("/user/hello")publicStringuser(){return"user";}}这三个测试接口,我们的规划如下:access/user/hello是只有具有用户身份的人才能访问的接口。用户可以访问的所有资源都可以由管理员访问。请注意,第四个规范意味着所有具有管理员身份的人都自动具有用户身份。接下来我们来配置权限的拦截规则。在SpringSecurity的configure(HttpSecurityhttp)方法中,代码如下:http.authorizeRequests().antMatchers("/admin/**").hasRole("admin").antMatchers("/user/**").hasRole("user").anyRequest().authenticated().and()...这里的匹配规则是Ant-stylepathmatchers,Ant-stylepathmatcher广泛应用于Spring家族,其匹配规则也很简单:通配符含义**匹配多层路径*匹配一级路径?匹配任意单个字符上面配置的意思是:如果请求路径满足/admin/**格式,则用户需要有admin角色。如果请求路径满足/user/**格式,则用户需要有user角色。其余的其他格式的请求路径需要认证(登录)后才能访问。注意代码中配置的三个规则的顺序很重要。和Shiro类似,SpringSecurity在匹配的时候也是从上到下匹配的。一旦匹配就不会继续匹配,所以拦截规则的顺序不能写错。如果使用角色继承,这个功能很容易实现。我们只需要在SecurityConfig中添加如下代码来配置角色继承关系:@BeanRoleHierarchyroleHierarchy(){RoleHierarchyImplhierarchy=newRoleHierarchyImpl();hierarchy.setHierarchy("ROLE_admin>ROLE_user");returnhierarchy;}请注意,在配置过程中,需要手动为角色添加ROLE_前缀。以上配置意味着ROLE_admin自动拥有ROLE_user的权限。接下来,我们启动项目进行测试。项目启动成功后,我们先以江南一点鱼登录:登录成功后,我们会分别访问/hello、/admin/hello、/user/hello这三个接口,其中:/hello登录后可以访问,this接口访问成功。/admin/hello需要管理员身份,因此访问失败。/user/hello需要用户身份,所以访问成功。然后以javaboy身份登录。登录成功后,我们发现javaboy也可以访问/user/hello接口,说明我们的角色继承配置没有问题!2.原理分析这里配置的核心是我们提供了一个RoleHierarchy实例,所以我们的分析就从这个类开始。RoleHierarchy是一个只有一个方法的接口:publicinterfaceRoleHierarchy{CollectiongetReachableGrantedAuthorities(Collectionauthorities);}方法参数authorities是权限的集合,方法的返回值是Accessible的权限集合.举个简单的例子,假设角色层次是ROLE_A>ROLE_B>ROLE_C,现在直接分配给用户的权限是ROLE_A,但实际上用户拥有ROLE_A、ROLE_B、ROLE_C的权限。getReachableGrantedAuthorities方法的目的是根据角色层次结构的定义解析用户实际可以到达的角色。RoleHierarchy接口有两个实现类,如下图所示:NullRoleHierarchy这是一个空的实现,它返回传递的参数不变。RoleHierarchyImpl这是我们上面使用的实现,这个会做一些解析。让我们关注RoleHierarchyImpl类。在这个类中,其实有四个方法setHierarchy、getReachableGrantedAuthorities、buildRolesReachableInOneStepMap和buildRolesReachableInOneOrMoreStepsMap,我们一一分析。首先是我们一开始调用的setHierarchy方法,用于设置角色层级:-Thefollowingrolehierarchywasset:"+roleHierarchyStringRepresentation);}buildRolesReachableInOneStepMap();buildRolesReachableInOneOrMoreStepsMap();}将用户传入的字符串变量设置到roleHierarchyStringRepresentation属性,然后通过buildRolesReachableInOneStepMap和buildRolesReachableInOneStepMap和buildRolesReachableInOneOrStepsMap完成对角色层次结构的解析方法。buildRolesReachableInOneStepMap方法用于将角色关系解析为逐层形式。我们看一下它的源码:privatevoidbuildRolesReachableInOneStepMap(){this.rolesReachableInOneStepMap=newHashMap<>();for(Stringline:this.roleHierarchyStringRepresentation.split("\n")){String[]roles=line.trim().split("\\s+>\\s+");for(inti=1;irolesReachableInOneStepSet;if(!this.rolesReachableInOneStepMap.containsKey(higherRole)){rolesReachableInOneStepSet=newHashSet<>();this.rolesReachableInOneStepMap.put(higherRole,rolesReachableInOneStepSet);}else{rolesReachableInOneStepSet=this.rolesReachableInOneStepMap.get(}rolesReachableInOneStepSet.add(lowerRole);}}}首先可以看到用户配置的多个角色级别是根据换行符来解析的,这是什么意思呢,在我们之前的案例中,我们只配置了ROLE_admin>ROLE_user。如果你需要配置更多继承关系如何配置,多个继承关系可以用\n分隔,如下ROLE_A>ROLE_B\nROLE_C>ROLE_D另一种情况,如果角色层级是连续的,ROLE_A>ROLE_B>ROLE_C>ROLE_D也可以这样配置。所以这里先用\n把多层继承关系拆成一个数组,然后遍历这个数组。在具体的遍历中,使用>将角色关系拆分成一个数组,然后对数组进行解析。上级角色作为键,下级角色作为值。代码比较简单,在rolesReachableInOneStepMap中最终解析存储的层级关系如下:假设角色继承关系为ROLE_A>ROLE_B\nROLE_C>ROLE_D\nROLE_C>ROLE_E,则Map中的数据如下:A-->BC-->[D,E]假设角色继承关系为ROLE_A>ROLE_B>ROLE_C>ROLE_D,则Map中的数据如下:A-->BB-->CC-->D这是由buildRolesReachableInOneStepMap方法解析的rolesReachableInOneStepMap集合。接下来的buildRolesReachableInOneOrMoreStepsMap方法就是再次分析rolesReachableInOneStepMap集合,将角色的继承关系扁平化。例如rolesReachableInOneStepMap中保存的角色继承关系如下:A-->BB-->CC-->D经过buildRolesReachableInOneOrMoreStepsMap方法解析后,新Map中保存的数据如下:A-->[B,C,D]B-->[C,D]C-->D分析完成后,每个字符能到达的字符一目了然。我们来看看下面的buildRolesReachableInOneOrMoreStepsMap方法的实现专辑:privatevoidbuildRolesReachableInOneOrMoreStepsMap(){this.rolesReachableInOneOrMoreStepsMap=newHashMap<>();for(StringroleName:this.rolesReachableInOneStepMap.keySet()){SetReachToroles.get(roleName));SetvisitedRolesSet=newHashSet<>();while(!rolesToVisitSet.isEmpty()){GrantedAuthoritylowerRole=rolesToVisitSet.iterator().next();rolesToVisitSet.remove(lowerRole);if(!visitedRolesSet.add(lowerRole)||!this.rolesReachableInOneStepMap.containsKey(lowerRole.getAuthority())){继续;}elseif(roleName.equals(lowerRole.getAuthority())){thrownewCycleInRoleHierarchyException();}rolesToVisitSet.addAll(this.rolesReachableInOneStepMap.get(lowerRole.getAuthority()));}this.rolesReachableInOneOrMoreStepsMap.put(roleName,visitedRolesSet);}}这个方法还比较巧妙。首先根据rolesName从rolesReachableInOneStepMap中获取对应的rolesToVisitSet。这个rolesToVisitSet是一个Set集合,遍历它,将遍历的结果添加到visitedRolesSet集合中。如果rolesReachableInOneStepMap集合的key中不包含当前读取的lowerRole,则说明lowerRole是整个角色系统中的最低层,直接continue。否则,取出rolesReachableInOneStepMap中lowerRole对应的值,继续遍历。最后将遍历结果存入rolesReachableInOneOrMoreStepsMap集合中。这个方法有点绕,小伙伴们可以打个断点自己尝尝。看完上面的分析,你可能已经发现,其实角色的继承最终是平级比较的。我们定义的角色是有等级的,但是这个等级在代码中被扁平化了,方便后续比较。最后还有一个getReachableGrantedAuthorities方法,根据传入的角度分析得出其可能包含的一些角度:publicCollectiongetReachableGrantedAuthorities(CollectionreachableRoles=newHashSet<>();SetprocessedNames=newHashSet<>();for(GrantedAuthorityauthority:authorities){if(authority.getAuthority()==null){reachableRoles.add(authority);continue;}if(!processedNames.add(authority.getAuthority())){continue;}reachableRoles.add(authority);SetlowerRoles=this.rolesReachableInOneOrMoreStepsMap.get(authority.getAuthority());if(lowerRoles==null){continue;}for(GrantedAuthorityrole:lowerRoles){if(processedNames.add(role.getAuthority())){reachableRoles.add(role);}}}ListreachableRoleList=newArrayList<>(reachableRoles.size());reachableRoleList.addAll(reachableRoles);returnreachableRoleList;}这个方法的逻辑比较简单,就是从rolesReachableInOneOrMoreStepsMap集合中查询当前角色实际可以访问的角色信息3.RoleHierarchyVoter中会调用RoleHierarchyVotergetReachableGrantedAuthorities方法。publicclassRoleHierarchyVoterextendsRoleVoter{privateRoleHierarchyroleHierarchy=null;publicRoleHierarchyVoter(RoleHierarchyroleHierarchy){Assert.notNull(roleHierarchy,"RoleHierarchymustnotbenull");this.roleHierarchy=roleHierarchy;}@OverrideCollectionextractAuthorities(Authenticationauthentication){returnroleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities());}}关于SpringSecurity投票器,那就另当别论了。宋哥下一篇会把投票器和决策器分享给小伙伴们~4.小结,今天和小伙伴们简单说一下,我们来看看角色继承的问题。有兴趣的小伙伴可以自己去试试看~如果觉得有收获记得点击观看给宋哥点个赞哦~关注一下。转载本文请联系江南一点鱼公众号。