前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Security 从问题到解决:谈踩坑、源码调试及提交 ISSUE 全过程

Spring Security 从问题到解决:谈踩坑、源码调试及提交 ISSUE 全过程

作者头像
用户2781897
发布2019-07-10 15:56:18
1.2K0
发布2019-07-10 15:56:18
举报
文章被收录于专栏:服务端思维

张帆 | 作者

前言

今天在用spring-security的角色继承时,遇到了一个坑,通过调试源码解决了,然后发现这应该是spring-security本身的一个小问题,然后就在Spring官方的GitHub上提了一个issue。

正文

我在使用spring-security的角色继承,关键代码片段如下:

代码语言:javascript
复制
...
// 定义角色继承,两个角色继承之间用空格或and连接
roleHierarchyImpl.setHierarchy("ROLE_DEVELOPER > ROLE_SUPERVISOR and ROLE_SUPERVISOR > ROLE_ADMIN and ROLE_ADMIN > ROLE_USER and ROLE_USER > ROLE_ANONYMOUS");
...
代码语言:javascript
复制
...
// 定义需要的权限表达式
.access("hasRole('USER')")
...

上边关于角色继承的定义方式,是我在使用之前版本的spring-security获得经验,同时,通过spring-security源码的注释也可看到相关说明

代码语言:javascript
复制
/**
 * 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 &gt; ROLE_B and ROLE_B &gt; 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啊,就是这样就可以啊。然后试了改了改权限表达式,结果还是不行,然后我想了想,调试源码吧,调试源码一般都是必杀技。在调试源码的过程中,我逐渐发现了问题所在。

我先给出角色表达式解析以及角色继承解析的相关代码路径,大家可按这个路径跟踪。

角色表达式解析:

代码语言:javascript
复制
// 由上到下为执行路径,最上端是入口点
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

那么为什么没有呢,按照角色继承的定义,应该能够有才对啊,这是我们就需要看角色继承表达式生成具体角色的逻辑了,这个逻辑的代码路径是这个:

代码语言:javascript
复制
org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl#setHierarchy
org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl#buildRolesReachableInOneStepMap
org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl#buildRolesReachableInOneOrMoreStepsMap

通过跟踪这些代码,我们从中可以看出,实际上,正确的角色继承表达式应该这样定义:

代码语言:javascript
复制
...
roleHierarchyImpl.setHierarchy("ROLE_DEVELOPER > ROLE_SUPERVISOR > ROLE_ADMIN > ROLE_USER > ROLE_ANONYMOUS");
...

实际上定义角色继承表达式的规则已经变了,然而,在spring-security代码库中的RoleHierarchy这个类的注释,还保留着旧版的角色继承表达式的定义方式的说明,这应当是代码更新了但是注释未更新,我按照以往的经验以及注释的说明去写,结果掉坑里了。

总结

通过这次问题的排查,可以说明:必要的注释可以有,但是不要过分依赖于注释,要相信代码本身,此外在这次调试源码的过程中我还发现了一个调试源码的技巧:利用Drop Frame,可以倒推代码的执行路径。

20190613更新

后来我发现,在4.2.x的spring-security中,角色继承表达式不仅仅可以用”and”连接符,它用任何一种连接符都可以。以下为我在issue page上与@rwinch的对话:

Thanks for the clarification. RoleHiearchy isn’t implementation specific. Instead it is trying to convey information rather than the configuration. That said, I can see how it might lead to confusion. Can you think of a way that makes the Javadoc in RoleHiearchyread better? If you do have a better wording, would you be willing to open a PR to change the RoleHiearchy Javadoc? Or, to put it another way, perhaps the current version of “RoleHierarchyImpl” is not compatible with the definition rules of the old version of the “role inheritance” expression. Can you clarify why you believe RoleHierarchyImpl worked differently and when it did? The code has gone largely untouched for over 10 years.

I reviewed my previous code these two days and learned that I used the version 4.2.2 before. Then, I looked at the source code for 4.2.2 and found out why the “and” connector can be used in the “role inheritance” expression in this version. In fact, in this version, the connector can be any string. Let’s look at the code for details:

代码语言:javascript
复制
/**
 * 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.");
	}
}

In this code, a regular expression grouping match is used to find a group that matches the rule. In fact, the string to be matched can contain any kind of connector. Any kind of connector will not affect the result of the expression “roleHierarchyMatcher.find()” equal to true.

代码语言:javascript
复制
@Test
public void testRegexForRoleHierarchyString() {
	Pattern pattern = Pattern.compile("(\\s*([^\\s>]+)\\s*>\\s*([^\\s>]+))");
	String roleHierarchyStringSplitByAnd = "ROLE_HIGHEST > ROLE_HIGHER and ROLE_HIGHER > ROLE_LOW and ROLE_LOW > ROLE_LOWER";
	String roleHierarchyStringSplitByOr = "ROLE_HIGHEST > ROLE_HIGHER or ROLE_HIGHER > ROLE_LOW or ROLE_LOW > ROLE_LOWER";
	String roleHierarchyStringSplitBySpace = "ROLE_HIGHEST > ROLE_HIGHER ROLE_HIGHER > ROLE_LOW ROLE_LOW > ROLE_LOWER";
	String roleHierarchyStringSplitByWhatever = "ROLE_HIGHEST > ROLE_HIGHER xxx ROLE_HIGHER > ROLE_LOW xxx ROLE_LOW > ROLE_LOWER";
	List<String> roleHierarchyStrings = new LinkedList<String>();
	roleHierarchyStrings.add(roleHierarchyStringSplitByAnd);
	roleHierarchyStrings.add(roleHierarchyStringSplitByOr);
	roleHierarchyStrings.add(roleHierarchyStringSplitBySpace);
	roleHierarchyStrings.add(roleHierarchyStringSplitByWhatever);
	for (String roleHierarchyString : roleHierarchyStrings) {
		Matcher roleHierarchyMatcher = pattern.matcher(roleHierarchyString);
		if (!roleHierarchyMatcher.find()) {
			throw new RuntimeException("I'm dead. X﹏X");
		}
	}
	System.out.println("All pass");
}
代码语言:javascript
复制
All pass
Process finished with exit code 0

That is to say, in the version 4.2.2 or the adjacent version, the “RoleHierarchy” comment is neither an error nor a correct one, XD.

代码语言:javascript
复制
/**
 * 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 &gt; ROLE_B and ROLE_B &gt; 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);
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 服务端思维 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 总结
  • 20190613更新
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档