序
本文就来研究一下spring security的role hierarchy
背景
默认情况下,userDetailsService建立的用户,他们的权限是没有继承关系的
@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;
}
比如这两个
@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 hierarchy
RoleHierarchy
spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/access/hierarchicalroles/RoleHierarchy.java
ROLE_ADMIN > ROLE_STAFF
ROLE_STAFF > ROLE_USER
ROLE_USER > ROLE_GUEST
spring security提供了RoleHierarchy,可以让你去定义各类角色的层级关系
config
@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权限的页面/接口
RoleHierarchyVoter
spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/access/vote/RoleHierarchyVoter.java
/**
* 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.java
/**
* 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这两个方法去构建映射
buildRolesReachableInOneStepMap
/**
* 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当中
假设级联关系是
A > B
B > C
C > D
D > E
D > F
那么这个map就类似于
A --> [B]
B --> [C]
C --> [D]
D --> [E,F]
buildRolesReachableInOneOrMoreStepsMap
/**
* 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如下
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]
RoleHierarchyImpl
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来获取所有级联层级关系这样就大功告成了
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。