今天在用spring-security的角色继承时,遇到了一个坑,通过调试源码解决了,然后发现这应该是spring-security本身的一个小问题,然后就在Spring官方的GitHub上提了一个 issue
我在使用spring-security的角色继承,关键代码片段如下:
... // 定义角色继承,两个角色继承之间用空格或and连接 roleHierarchyImpl.setHierarchy("ROLE_DEVELOPER > ROLE_SUPERVISOR and ROLE_SUPERVISOR > ROLE_ADMIN and ROLE_ADMIN > ROLE_USER and ROLE_USER > ROLE_ANONYMOUS"); ...
... // 定义需要的权限表达式 .access("hasRole('USER')") ...
上边关于角色继承的定义方式,是我在使用之前版本的spring-security获得经验,同时,通过spring-security源码的注释也可看到相关说明
/** * The simple interface of a role hierarchy. * * @author Michael Mayr */ public interface RoleHierarchy { /** * Returns an array of all reachable authorities. * <p> * Reachable authorities are the directly assigned authorities plus all authorities * that are (transitively) reachable from them in the role hierarchy. * <p> * Example:<br> * Role hierarchy: ROLE_A > ROLE_B and ROLE_B > ROLE_C.<br> * Directly assigned authority: ROLE_A.<br> * Reachable authorities: ROLE_A, ROLE_B, ROLE_C. * * @param authorities - List of the directly assigned authorities. * @return List of all reachable authorities given the assigned authorities. */ public Collection<? extends GrantedAuthority> getReachableGrantedAuthorities( Collection<? extends GrantedAuthority> authorities); }
但是,当我实际跑起来后,发现根本不行,角色继承没生效。我就很纳闷了,原来用过spring-security啊,就是这样就可以啊。然后试了改了改权限表达式,结果还是不行,然后我想了想,调试源码吧,调试源码一般都是必杀技。在调试源码的过程中,我逐渐发现了问题所在。
我先给出角色表达式解析以及角色继承解析的相关代码路径,大家可按这个路径跟踪。
角色表达式解析:
// 由上到下为执行路径,最上端是入口点 org.springframework.security.web.FilterChainProxy.VirtualFilterChain#doFilter org.springframework.security.web.access.intercept.FilterSecurityInterceptor#doFilter org.springframework.security.access.intercept.AbstractSecurityInterceptor#beforeInvocation org.springframework.security.web.access.intercept.FilterSecurityInterceptor#invoke org.springframework.security.access.AccessDecisionManager#decide org.springframework.security.access.vote.AffirmativeBased#decide org.springframework.security.web.access.expression.WebExpressionVoter#vote org.springframework.security.access.expression.ExpressionUtils#evaluateAsBoolean org.springframework.expression.spel.standard.SpelExpression#getValue(org.springframework.expression.EvaluationContext, java.lang.Class<T>) org.springframework.expression.spel.ast.SpelNodeImpl#getTypedValue org.springframework.expression.spel.ast.MethodReference#getValueInternal(org.springframework.expression.spel.ExpressionState) org.springframework.expression.spel.ast.MethodReference#getCachedExecutor org.springframework.expression.spel.support.ReflectiveMethodExecutor#execute java.lang.reflect.Method#invoke org.springframework.security.access.expression.SecurityExpressionRoot#hasRole org.springframework.security.access.expression.SecurityExpressionRoot#hasAnyRole org.springframework.security.access.expression.SecurityExpressionRoot#hasAnyAuthorityName
注意上边执行路径中的 java.lang.reflect.Method#invoke
实际上,权限控制表达式内部的原理是是用反射去执行对应的用于判断是否有权限方法的,也就是执行 org.springframework.security.access.expression.SecurityExpressionRoot#hasRole
执行到下图中这里后,返回的是 false
也就是授权未通过,没有对应角色,当前拥有的角色是从 org.springframework.security.access.hierarchicalroles.RoleHierarchy#getReachableGrantedAuthorities
获得的,里面并没有需要的角色”ROLE”,因此自然就是 false
那么为什么没有呢,按照角色继承的定义,应该能够有才对啊,这是我们就需要看角色继承表达式生成具体角色的逻辑了,这个逻辑的代码路径是这个:
org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl#setHierarchy org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl#buildRolesReachableInOneStepMap org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl#buildRolesReachableInOneOrMoreStepsMap
通过跟踪这些代码,我们从中可以看出,实际上,正确的角色继承表达式应该这样定义:
... roleHierarchyImpl.setHierarchy("ROLE_DEVELOPER > ROLE_SUPERVISOR > ROLE_ADMIN > ROLE_USER > ROLE_ANONYMOUS"); ...
实际上定义角色继承表达式的规则已经变了,然而,在spring-security代码库中的 RoleHierarchy
这个类的注释,还保留着旧版的角色继承表达式的定义方式的说明,这应当是代码更新了但是注释未更新,我按照以往的经验以及注释的说明去写,结果掉坑里了。
通过这次问题的排查,可以说明: 必要的注释可以有,但是不要过分依赖于注释,要相信代码本身
,此外在这次调试源码的过程中我还发现了一个调试源码的技巧:利用 Drop Frame
,可以倒推代码的执行路径。