本文就來研究一下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來獲取全部級聯層級關係這樣就大功告成了