Spring Security学习(三)授权管理器

4 篇文章 0 订阅
3 篇文章 0 订阅
本文深入剖析Spring Security中的授权机制,重点介绍了AccessDecisionManager及其子类如何通过AccessDecisionVoter进行投票决策,包括AffirmativeBased、ConsensusBased和UnanimousBased三种决策方式。同时,详细解释了RoleVoter和WebExpressionVoter的工作原理。
摘要由CSDN通过智能技术生成

第一篇的授权部分,有分析到

授权主要由AbstractSecurityInterceptor及其子类完成

具体到实际代码中其实是

			// 用户通过了认证,基于当前用户信息,和目标对象的安全属性配置,进行相应的权限检查
			this.accessDecisionManager.decide(authenticated, object, attributes);

AccessDecisionManager

AccessDecisionManager 是一个决策管理器接口,主要有下面三个方法:

void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
			InsufficientAuthenticationException;

该方法是投票过程, 有三个参数 :

  1. Authentication authentication代表访问者当事人,是访问者的认证令牌,包含了访问者的权限
  2. Object object表示目标安全对象
  3. Collection<ConfigAttribute> configAttributes表示访问目标安全对象所需要的权限
boolean supports(ConfigAttribute attribute);

该方法用于检测ConfigAttribute attribute是否是当前 AccessDecisionManager 支持的 ConfigAttribute 类型。

boolean supports(Class<?> clazz);

检测Class clazz是否是当前 AccessDecisionManager 支持的安全对象。

AccessDecisionManager 实现

Spring Security内置的 AccessDecisionManager 的实现是抽象类 AbstractAccessDecisionManager

public abstract class AbstractAccessDecisionManager implements AccessDecisionManager,
		InitializingBean, MessageSourceAware {
		
	protected final Log logger = LogFactory.getLog(getClass());

	// 通过这一组AccessDecisionVoter来投票表决完成授权
	private List<AccessDecisionVoter<?>> decisionVoters;

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	private boolean allowIfAllAbstainDecisions = false;

	protected AbstractAccessDecisionManager(
			List<AccessDecisionVoter<?>> decisionVoters) {
		Assert.notEmpty(decisionVoters, "A list of AccessDecisionVoters is required");
		this.decisionVoters = decisionVoters;
	}
	
	public void afterPropertiesSet() {
		Assert.notEmpty(this.decisionVoters, "A list of AccessDecisionVoters is required");
		Assert.notNull(this.messages, "A message source must be set");
	}

	protected final void checkAllowIfAllAbstainDecisions() {
		if (!this.isAllowIfAllAbstainDecisions()) {
			throw new AccessDeniedException(messages.getMessage(
					"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}
	}

	public List<AccessDecisionVoter<?>> getDecisionVoters() {
		return this.decisionVoters;
	}

	public boolean isAllowIfAllAbstainDecisions() {
		return allowIfAllAbstainDecisions;
	}

	public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) {
		this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions;
	}

	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	public boolean supports(ConfigAttribute attribute) {
		for (AccessDecisionVoter voter : this.decisionVoters) {// 只要有一个匹配,返回true
			if (voter.supports(attribute)) {
				return true;
			}
		}

		return false;
	}
	
	public boolean supports(Class<?> clazz) {
		for (AccessDecisionVoter voter : this.decisionVoters) {// 只要有一个不匹配,返回false
			if (!voter.supports(clazz)) {
				return false;
			}
		}

		return true;
	}
}

如上述注释所说AbstractAccessDecisionManager通过一组AccessDecisionVoter来投票表决完成授权,具体实现放在其子类之中。

先简单介绍一下AccessDecisionVoter

AccessDecisionVoter是一个接口,返回结果只有三种:

拒绝 : ACCESS_DENIED
弃权 : ACCESS_ABSTAIN
允许 : ACCESS_GRANTED

AbstractAccessDecisionManager 有三个内置实现:

AffirmativeBased -----------一票通过制

public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
		int deny = 0;

		for (AccessDecisionVoter voter : getDecisionVoters()) {
			int result = voter.vote(authentication, object, configAttributes);

			if (logger.isDebugEnabled()) {
				logger.debug("Voter: " + voter + ", returned: " + result);
			}

			switch (result) {
			// 允许访问
			case AccessDecisionVoter.ACCESS_GRANTED:
			// 方法直接结束
				return;

			// 拒绝访问
			case AccessDecisionVoter.ACCESS_DENIED:
				deny++;

				break;

			default:
				break;
			}
		}

		if (deny > 0) {
		// 全部拒绝抛出异常
			throw new AccessDeniedException(messages.getMessage(
					"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}

		// 所有投票器都弃权,根据属性allowIfAllAbstainDecisions进行处理
		checkAllowIfAllAbstainDecisions();
	}

ConsensusBased ----------少数服从多数制


public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
		int grant = 0;
		int deny = 0;

		for (AccessDecisionVoter voter : getDecisionVoters()) {
			int result = voter.vote(authentication, object, configAttributes);

			if (logger.isDebugEnabled()) {
				logger.debug("Voter: " + voter + ", returned: " + result);
			}

			switch (result) {
			// 允许访问
			case AccessDecisionVoter.ACCESS_GRANTED:
				grant++;

				break;
			// 拒绝访问
			case AccessDecisionVoter.ACCESS_DENIED:
				deny++;

				break;

			default:
				break;
			}
		}

		// 允许的多于拒绝的,直接方法直接结束
		if (grant > deny) {
			return;
		}
		
		// 拒绝的对于允许的,则抛出无法访问的异常
		if (deny > grant) {
			throw new AccessDeniedException(messages.getMessage(
					"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}
		// 遇到相等的情况则根据allowIfEqualGrantedDeniedDecisions 判断是允许还是拒绝
		if ((grant == deny) && (grant != 0)) {
			if (this.allowIfEqualGrantedDeniedDecisions) {
				return;
			}
			else {
				throw new AccessDeniedException(messages.getMessage(
						"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
			}
		}

		// 所有投票器都弃权,根据属性allowIfAllAbstainDecisions进行处理
		checkAllowIfAllAbstainDecisions();
	}

UnanimousBased ----------全票通过制


	public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> attributes) throws AccessDeniedException {

		int grant = 0;

		List<ConfigAttribute> singleAttributeList = new ArrayList<>(1);
		singleAttributeList.add(null);

		for (ConfigAttribute attribute : attributes) {
			singleAttributeList.set(0, attribute);

			for (AccessDecisionVoter voter : getDecisionVoters()) {
				int result = voter.vote(authentication, object, singleAttributeList);

				if (logger.isDebugEnabled()) {
					logger.debug("Voter: " + voter + ", returned: " + result);
				}

				switch (result) {
				// 允许,计数加一
				case AccessDecisionVoter.ACCESS_GRANTED:
					grant++;

					break;
				// 拒绝,则抛出拒绝访问的异常
				case AccessDecisionVoter.ACCESS_DENIED:
					throw new AccessDeniedException(messages.getMessage(
							"AbstractAccessDecisionManager.accessDenied",
							"Access is denied"));

				default:
					break;
				}
			}
		}

		// 方法执行到这则全部通过
		if (grant > 0) {
			return;
		}

		// To get this far, every AccessDecisionVoter abstained
		checkAllowIfAllAbstainDecisions();
	}
	

AccessDecisionVoter

通过上面的分析,得知AccessDecisionManager是通过AccessDecisionVoter投票来决定是否允许访问的,那么接下来就看看AccessDecisionVoter。

AccessDecisionVoter 是一个投票器接口定义了三个方法:


public interface AccessDecisionVoter<S> {

	int ACCESS_GRANTED = 1;
	int ACCESS_ABSTAIN = 0;
	int ACCESS_DENIED = -1;
	// 判断是否支持目标ConfigAttribute 
	boolean supports(ConfigAttribute attribute);
	// 判断是否支持目标class
	boolean supports(Class<?> clazz);
	// 投票
	int vote(Authentication authentication, S object,
			Collection<ConfigAttribute> attributes);
}

spring security 内置的voter 有:

名称支持Secure Object类型支持ConfigAttribute类型
WebExpressionVoterFilterInvocationweb表达式
Jsr250VoterMethodInvocationJsr250SecurityConfig
PreInvocationAuthorizationAdviceVoterObjectPreInvocationAttribute
AuthenticatedVoterObject可返回属于特定集合的字符串的ConfigAttribute
RoleVoterObject可返回带有特定前缀(缺省ROLE_)的字符串的ConfigAttribute

这里重点了解一下两种voter
RoleVoter与WebExpressionVoter

RoleVoter 将ConfigAttribute简单的看作是一个角色名称,在投票的时如果拥有该角色即投赞成票

public class RoleVoter implements AccessDecisionVoter<Object> {
	private String rolePrefix = "ROLE_";
	
	public String getRolePrefix() {
		return rolePrefix;
	}

	public void setRolePrefix(String rolePrefix) {
		this.rolePrefix = rolePrefix;
	}

	// 如果ConfigAttribute是以“ROLE_”开头的,则将使用RoleVoter进行投票
	public boolean supports(ConfigAttribute attribute) {
		if ((attribute.getAttribute() != null)
				&& attribute.getAttribute().startsWith(getRolePrefix())) {
			return true;
		}
		else {
			return false;
		}
	}

	public boolean supports(Class<?> clazz) {
		return true;
	}

	public int vote(Authentication authentication, Object object,
			Collection<ConfigAttribute> attributes) {
		if (authentication == null) {
		// 用户没通过认证,直接不允许访问
			return ACCESS_DENIED;
		}
		int result = ACCESS_ABSTAIN;
		// 获取用户全部权限
		Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);

		for (ConfigAttribute attribute : attributes) {
			if (this.supports(attribute)) {
				result = ACCESS_DENIED;

				// Attempt to find a matching granted authority
				for (GrantedAuthority authority : authorities) {
					if (attribute.getAttribute().equals(authority.getAuthority())) {
					// 权限匹配,则允许访问
						return ACCESS_GRANTED;
					}
				}
			}
		}

		return result;
	}

	Collection<? extends GrantedAuthority> extractAuthorities(
			Authentication authentication) {
		return authentication.getAuthorities();
	}
}

WebExpressionVoter,表达式投票器用于对类似hasAnyRole('normal','admin')之类的表达式进行解析投票

public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {
	private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();

	public int vote(Authentication authentication, FilterInvocation fi,
			Collection<ConfigAttribute> attributes) {
		assert authentication != null;
		assert fi != null;
		assert attributes != null;
		// 获取表达式配置
		WebExpressionConfigAttribute weca = findConfigAttribute(attributes);

		if (weca == null) {
			// 没有相关配置则弃权
			return ACCESS_ABSTAIN;
		}
		// 创建上下文
		EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
				fi);
		ctx = weca.postProcess(ctx, fi);
		// 执行表达式解析
		return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
				: ACCESS_DENIED;
	}

	private WebExpressionConfigAttribute findConfigAttribute(
			Collection<ConfigAttribute> attributes) {
		for (ConfigAttribute attribute : attributes) {
			if (attribute instanceof WebExpressionConfigAttribute) {
				return (WebExpressionConfigAttribute) attribute;
			}
		}
		return null;
	}

	public boolean supports(ConfigAttribute attribute) {
		return attribute instanceof WebExpressionConfigAttribute;
	}

	public boolean supports(Class<?> clazz) {
		return FilterInvocation.class.isAssignableFrom(clazz);
	}

	public void setExpressionHandler(
			SecurityExpressionHandler<FilterInvocation> expressionHandler) {
		this.expressionHandler = expressionHandler;
	}
}

经过一轮调用最后反射到SecurityExpressionRoot 的 hasAnyAuthorityName 方法进行解析:

...
private boolean hasAnyAuthorityName(String prefix, String... roles) {
		// 获取权限列表
		Set<String> roleSet = getAuthoritySet();

		for (String role : roles) {
			// 匹配角色
			String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
			if (roleSet.contains(defaultedRole)) {
				return true;
			}
		}

		return false;
	}

...
private Set<String> getAuthoritySet() {
		if (roles == null) {
			Collection<? extends GrantedAuthority> userAuthorities = authentication
					.getAuthorities();

			if (roleHierarchy != null) {
				userAuthorities = roleHierarchy
						.getReachableGrantedAuthorities(userAuthorities);
			}

			roles = AuthorityUtils.authorityListToSet(userAuthorities);
		}

		return roles;
	}


...
private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
		if (role == null) {
			return role;
		}
		if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) {
			return role;
		}
		if (role.startsWith(defaultRolePrefix)) {
			return role;
		}
		return defaultRolePrefix + role;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值