本文就来研究一下spring security的role hierarchyhtml
默认状况下,userDetailsService创建的用户,他们的权限是没有继承关系的java
@Bean @Override protected UserDetailsService userDetailsService(){ InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("demoUser1").password("123456") .authorities("ROLE_USER","read_x").build()); manager.createUser(User.withUsername("admin").password("123456") .authorities("ROLE_ADMIN").build()); return manager; }
好比这两个spring
@GetMapping("/admin") @Secured("ROLE_ADMIN") public String admin(){ return "admin"; } @GetMapping("/user") @Secured("ROLE_USER") public String user(){ return "user"; }
admin登陆只能访问/admin,访问不了/user;而user登陆只能访问/user这一般不大符合咱们的业务需求,通常admin拥有全部权限的,也就是它应该能访问/user。这个问题扩展开来就是角色权限的继承问题,role hierarchyexpress
spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/access/hierarchicalroles/RoleHierarchy.javaapp
ROLE_ADMIN > ROLE_STAFF ROLE_STAFF > ROLE_USER ROLE_USER > ROLE_GUEST
spring security提供了RoleHierarchy,能够让你去定义各种角色的层级关系
@EnableGlobalMethodSecurity( securedEnabled = true, jsr250Enabled = true, prePostEnabled = true ) @Configuration public class RoleConfig extends GlobalMethodSecurityConfiguration{ @Override protected AccessDecisionManager accessDecisionManager() { List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<AccessDecisionVoter<? extends Object>>(); ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice(); expressionAdvice.setExpressionHandler(getExpressionHandler()); // if (prePostEnabled()) { decisionVoters .add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice)); // } // if (jsr250Enabled()) { decisionVoters.add(new Jsr250Voter()); // } // decisionVoters.add(new RoleVoter()); decisionVoters.add(roleHierarchyVoter()); decisionVoters.add(new AuthenticatedVoter()); return new AffirmativeBased(decisionVoters); } @Bean public RoleHierarchyVoter roleHierarchyVoter() { return new RoleHierarchyVoter(roleHierarchy()); } @Bean public RoleHierarchy roleHierarchy(){ RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); roleHierarchy.setHierarchy( "ROLE_ADMIN > ROLE_USER\n"+ " ROLE_USER > ROLE_ANONYMOUS\n" ); return roleHierarchy; } }
这里经过重写GlobalMethodSecurityConfiguration的accessDecisionManager方法,给decisionVoters添加roleHierarchyVoter
默认是使用RoleVoter,它不支持继承关系,这里替换为roleHierarchyVoter
这样就大功告成了,admin也能够访问user权限的页面/接口
spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/access/vote/RoleHierarchyVoter.javaide
/** * Extended RoleVoter which uses a {@link RoleHierarchy} definition to determine the roles * allocated to the current user before voting. * * @author Luke Taylor * @since 2.0.4 */ public class RoleHierarchyVoter extends RoleVoter { private RoleHierarchy roleHierarchy = null; public RoleHierarchyVoter(RoleHierarchy roleHierarchy) { Assert.notNull(roleHierarchy, "RoleHierarchy must not be null"); this.roleHierarchy = roleHierarchy; } /** * Calls the <tt>RoleHierarchy</tt> to obtain the complete set of user authorities. */ @Override Collection<? extends GrantedAuthority> extractAuthorities( Authentication authentication) { return roleHierarchy.getReachableGrantedAuthorities(authentication .getAuthorities()); } }
这类继承了RoleVoter,重写了extractAuthorities,使用roleHierarchy去获取grantedAuthorities
spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/access/hierarchicalroles/RoleHierarchyImpl.javaui
/** * Set the role hierarchy and pre-calculate for every role the set of all reachable * roles, i.e. all roles lower in the hierarchy of every given role. Pre-calculation * is done for performance reasons (reachable roles can then be calculated in O(1) * time). During pre-calculation, cycles in role hierarchy are detected and will cause * a <tt>CycleInRoleHierarchyException</tt> to be thrown. * * @param roleHierarchyStringRepresentation - String definition of the role hierarchy. */ public void setHierarchy(String roleHierarchyStringRepresentation) { this.roleHierarchyStringRepresentation = roleHierarchyStringRepresentation; logger.debug("setHierarchy() - The following role hierarchy was set: " + roleHierarchyStringRepresentation); buildRolesReachableInOneStepMap(); buildRolesReachableInOneOrMoreStepsMap(); }
设置层级关系以后,经过buildRolesReachableInOneStepMap以及buildRolesReachableInOneOrMoreStepsMap这两个方法去构建映射
/** * rolesReachableInOneStepMap is a Map that under the key of a specific role name * contains a set of all roles reachable from this role in 1 step */ private Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneStepMap = null; /** * Parse input and build the map for the roles reachable in one step: the higher role * will become a key that references a set of the reachable lower roles. */ private void buildRolesReachableInOneStepMap() { Pattern pattern = Pattern.compile("(\\s*([^\\s>]+)\\s*>\\s*([^\\s>]+))"); Matcher roleHierarchyMatcher = pattern .matcher(this.roleHierarchyStringRepresentation); this.rolesReachableInOneStepMap = new HashMap<GrantedAuthority, Set<GrantedAuthority>>(); while (roleHierarchyMatcher.find()) { GrantedAuthority higherRole = new SimpleGrantedAuthority( roleHierarchyMatcher.group(2)); GrantedAuthority lowerRole = new SimpleGrantedAuthority( roleHierarchyMatcher.group(3)); Set<GrantedAuthority> rolesReachableInOneStepSet; if (!this.rolesReachableInOneStepMap.containsKey(higherRole)) { rolesReachableInOneStepSet = new HashSet<GrantedAuthority>(); this.rolesReachableInOneStepMap.put(higherRole, rolesReachableInOneStepSet); } else { rolesReachableInOneStepSet = this.rolesReachableInOneStepMap .get(higherRole); } addReachableRoles(rolesReachableInOneStepSet, lowerRole); logger.debug("buildRolesReachableInOneStepMap() - From role " + higherRole + " one can reach role " + lowerRole + " in one step."); } } private void addReachableRoles(Set<GrantedAuthority> reachableRoles, GrantedAuthority authority) { for (GrantedAuthority testAuthority : reachableRoles) { String testKey = testAuthority.getAuthority(); if ((testKey != null) && (testKey.equals(authority.getAuthority()))) { return; } } reachableRoles.add(authority); }
Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneStepMap ,这里将第一级的直连关系存到这个map当中
假设级联关系是this
A > B B > C C > D D > E D > F
那么这个map就相似于debug
A --> [B] B --> [C] C --> [D] D --> [E,F]
/** * rolesReachableInOneOrMoreStepsMap is a Map that under the key of a specific role * name contains a set of all roles reachable from this role in 1 or more steps */ private Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneOrMoreStepsMap = null; /** * For every higher role from rolesReachableInOneStepMap store all roles that are * reachable from it in the map of roles reachable in one or more steps. (Or throw a * CycleInRoleHierarchyException if a cycle in the role hierarchy definition is * detected) */ private void buildRolesReachableInOneOrMoreStepsMap() { this.rolesReachableInOneOrMoreStepsMap = new HashMap<GrantedAuthority, Set<GrantedAuthority>>(); // iterate over all higher roles from rolesReachableInOneStepMap for (GrantedAuthority role : this.rolesReachableInOneStepMap.keySet()) { Set<GrantedAuthority> rolesToVisitSet = new HashSet<GrantedAuthority>(); if (this.rolesReachableInOneStepMap.containsKey(role)) { rolesToVisitSet.addAll(this.rolesReachableInOneStepMap.get(role)); } Set<GrantedAuthority> visitedRolesSet = new HashSet<GrantedAuthority>(); while (!rolesToVisitSet.isEmpty()) { // take a role from the rolesToVisit set GrantedAuthority aRole = rolesToVisitSet.iterator().next(); rolesToVisitSet.remove(aRole); addReachableRoles(visitedRolesSet, aRole); if (this.rolesReachableInOneStepMap.containsKey(aRole)) { Set<GrantedAuthority> newReachableRoles = this.rolesReachableInOneStepMap .get(aRole); // definition of a cycle: you can reach the role you are starting from if (rolesToVisitSet.contains(role) || visitedRolesSet.contains(role)) { throw new CycleInRoleHierarchyException(); } else { // no cycle rolesToVisitSet.addAll(newReachableRoles); } } } this.rolesReachableInOneOrMoreStepsMap.put(role, visitedRolesSet); logger.debug("buildRolesReachableInOneOrMoreStepsMap() - From role " + role + " one can reach " + visitedRolesSet + " in one or more steps."); } }
Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneOrMoreStepsMap这个将间接的层级关系拉平
这里实际用了递归来完成层级的全部级联关系映射,rolesToVisitSet不断remove和有条件地add,递归终止条件是rolesToVisitSet为empty
一级的map以下code
A --> [B] B --> [C] C --> [D] D --> [E,F]
构造完以后以下
A --> [B,C,D,E,F] B --> [C,D,E,F] C --> [D,E,F] D --> [E,F]
spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/access/hierarchicalroles/RoleHierarchyImpl.java
public Collection<GrantedAuthority> getReachableGrantedAuthorities( Collection<? extends GrantedAuthority> authorities) { if (authorities == null || authorities.isEmpty()) { return AuthorityUtils.NO_AUTHORITIES; } Set<GrantedAuthority> reachableRoles = new HashSet<GrantedAuthority>(); for (GrantedAuthority authority : authorities) { addReachableRoles(reachableRoles, authority); Set<GrantedAuthority> additionalReachableRoles = getRolesReachableInOneOrMoreSteps( authority); if (additionalReachableRoles != null) { reachableRoles.addAll(additionalReachableRoles); } } if (logger.isDebugEnabled()) { logger.debug("getReachableGrantedAuthorities() - From the roles " + authorities + " one can reach " + reachableRoles + " in zero or more steps."); } List<GrantedAuthority> reachableRoleList = new ArrayList<GrantedAuthority>( reachableRoles.size()); reachableRoleList.addAll(reachableRoles); return reachableRoleList; } // SEC-863 private Set<GrantedAuthority> getRolesReachableInOneOrMoreSteps( GrantedAuthority authority) { if (authority.getAuthority() == null) { return null; } for (GrantedAuthority testAuthority : this.rolesReachableInOneOrMoreStepsMap .keySet()) { String testKey = testAuthority.getAuthority(); if ((testKey != null) && (testKey.equals(authority.getAuthority()))) { return this.rolesReachableInOneOrMoreStepsMap.get(testAuthority); } } return null; }
getReachableGrantedAuthorities方法经过以前构造好的rolesReachableInOneOrMoreStepsMap来获取全部级联层级关系这样就大功告成了