Spring Security 授权过程

过滤器FilterSecurityInterceptor是一个请求处理过程中安全机制过滤器链中最后一个filter,它执行真正的HTTP资源安全控制,我们直接找到doFilter()方法。

FilterInvocation你可以看成带有HttpServletRequest,HttpServletResponse,FilterChain属性的类,也就是过滤器上下文对象。

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor 
    													implements Filter {
    
	public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
    	
		invoke(new FilterInvocation(request, response, chain));
	}

	public void invoke(FilterInvocation filterInvocation) 
        									throws IOException, ServletException {
        //1. 同一个请求的访问会直接放行
        if (isApplied(filterInvocation) && this.observeOncePerRequest) {
			filterInvocation.getChain()
                .doFilter(filterInvocation.getRequest(), 
                          filterInvocation.getResponse());
			return;
		}
		//1. 第一次处理该请求,加个标记
		if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
			filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
		} 
        //2. 过滤器传递前的操作:before
		InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
        
		try {
            // filter链表的传递
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
		}
		finally {
            // 刷新上下文,也就是SecurityContext
			super.finallyInvocation(token);
		}
        //3. 过滤器传递后的操作:after
		super.afterInvocation(token, null);
	}
}

从上面源码我们可以看出当请求过来时会发生三件事:

1、请求是不是第一次来,如果是则修改标记;如果不是则放行

2、请求向下传递前处理

3、请求向下传递后处理

我们顺着上面继续看,去父类中找到beforeInvocation()方法:

public abstract class AbstractSecurityInterceptor{
	// object == filterInvocation
	protected InterceptorStatusToken beforeInvocation(Object object) {

        // 1. 获取配置类中定义的用户信息
		Collection<ConfigAttribute> attributes = 
            				this.obtainSecurityMetadataSource().getAttributes(object);
        // 2. 如果为空
		if (CollectionUtils.isEmpty(attributes)) {
			// 广播消息
            // PublicInvocationEvent就是把filterInvocation封装进去
			publishEvent(new PublicInvocationEvent(object));
			return null; 
		}
        // 2. 没有用户信息,抛出exception
		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			credentialsNotFound(this.messages.getMessage(
                "AbstractSecurityInterceptor.authenticationNotFound",
			    "An Authentication object was not found in the SecurityContext"),
                object, attributes);
		}
        // 如果Authentication.isAuthenticated()返回false
        // 或属性alwaysReauthenticate已设置为true需要再次检查
		Authentication authenticated = authenticateIfRequired();
        
		// 3. 关键授权方法
		attemptAuthorization(object, attributes, authenticated);
        
		// 4. 如果授权成功
		if (this.publishAuthorizationSuccess) {
			publishEvent(new AuthorizedEvent(object, attributes, authenticated));
		}
        
		// Attempt to run as a different user
        // 4. 尝试不同的用户运行,权限前面如果有 "RUN_AS_"则会有结果
		Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
        // 一般都为null
		if (runAs != null) {
			SecurityContext origCtx = SecurityContextHolder.getContext();
			SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
			SecurityContextHolder.getContext().setAuthentication(runAs);
			return new InterceptorStatusToken(origCtx, true, attributes, object);
		}
		// 5. 返回结果
		return new InterceptorStatusToken(SecurityContextHolder.getContext(), 
                                          false, attributes, object);
	}
}

我们可以从上面代码看出:

1、先从配置读出定义好的权限,顺便检验下这个权限集合有没有问题,是不是为null,为null就广播一下

2、看看上下文有没有Authentication,需不需要再次认证

3、授权方法,授权成功就进行广播

4、尝试以其他用户身份运行,最后返回一个InterceptorStatusToken实例

InterceptorStatusToken里面简单封装4个属性,如下:

public class InterceptorStatusToken {
	// 上下文对象,其实也就一个Authentication属性
	private SecurityContext securityContext;
	// 权限集合
	private Collection<ConfigAttribute> attr;
	// 过滤器上下文对象,也就是FilterInvocation
	private Object secureObject;
	// 是否需要刷新
	private boolean contextHolderRefreshRequired;
    ...
}

好,我们再继续往下看,我们的关键点在于attemptAuthorization()方法。

	private void attemptAuthorization(Object object, 
                                      Collection<ConfigAttribute> attributes, 
                                      Authentication authenticated) {
		try {
			this.accessDecisionManager.decide(authenticated, object, attributes);
		}
		catch (AccessDeniedException ex) {
            
			publishEvent(new AuthorizationFailureEvent(object, attributes, 
            		                                   authenticated, ex));
			throw ex;
		}
	}

再往下寻找,知道AccessDecisionManager接口, 它常用的实现类是AffirmativeBased,我们发现它的父类存在一个投票的集合List<AccessDecisionVoter<?>> decisionVoters

public class 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);
			switch (result) {
			case AccessDecisionVoter.ACCESS_GRANTED:
				return;
			case AccessDecisionVoter.ACCESS_DENIED:
				deny++;
				break;
			default:
				break;
			}
		}
		if (deny > 0) {
            // 这里是个重点,因为我们常常去自定义它的处理器,也就是AccessDeniedHandler
			throw new AccessDeniedException(	
                this.messages.getMessage(
                    "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}
		checkAllowIfAllAbstainDecisions();
	}
}

decide()中的功能实现比较简单,就遍历集合、调用投票类的投票方法。

我们再继续往下看,AccessDecisionVoter接口-投票类的顶层接口,它在其中定义了三种状态(如下) ,它常用的实现有RoleVoter、WebExpressionVoter、RoleHierarchyVoter,vote()方法中的object是上下文对象。

public interface AccessDecisionVoter<S> {
    // 赞成 
	int ACCESS_GRANTED = 1;
	// 弃权
	int ACCESS_ABSTAIN = 0;
	// 反对
	int ACCESS_DENIED = -1;
}

先来看下RoleVoter类:就很简单

public class RoleVoter  {
	public int vote(Authentication authentication, Object object,     
                                           Collection<ConfigAttribute> attributes) {
		if (authentication == null) {
			return ACCESS_DENIED;
		}
        // 默认为 0
		int result = ACCESS_ABSTAIN;
        
		Collection<? extends GrantedAuthority> authorities = 
                                               extractAuthorities(authentication);
        // 遍历权限集合
		for (ConfigAttribute attribute : attributes) {
            // 看权限是不是ROLE_开头的
			if (this.supports(attribute)) {
				result = ACCESS_DENIED;
                // 再遍历认证好的用户信息中,看是否匹配
				for (GrantedAuthority authority : authorities) {
					if (attribute.getAttribute().equals(authority.getAuthority())) {
						return ACCESS_GRANTED;
					}
				}
			}
		}
		return result;
	}
}

再来看下WebExpressionVoter类:这里主要是基于表达式的权限认证,它的语法都是SpEL,不知道的同学可以点击这里,它分两步走:先根据attributes来创建WebExpressionConfigAttribute对象,再根据Authentication来创建EvaluationContext对象,最后再调用方法看是否匹配。

	public int vote(Authentication authentication, FilterInvocation filterInvocation,
			Collection<ConfigAttribute> attributes) {
		// 
		WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes);
        
		if (webExpressionConfigAttribute == null) {
			return ACCESS_ABSTAIN;
		}
        
		EvaluationContext ctx = webExpressionConfigAttribute.
            postProcess(this.expressionHandler.
            createEvaluationContext(authentication, filterInvocation), filterInvocation);
        
		boolean granted = ExpressionUtils.
                              evaluateAsBoolean(webExpressionConfigAttribute.
                               getAuthorizeExpression(), ctx);
		if (granted) {
			return ACCESS_GRANTED;
		}

		return ACCESS_DENIED;
	}

好了,到这里,权限认证的源码的流程也结束了,权限认证的流程其实很简单:去配置获取权限集合,拿到Authentication,将这两个东西交个投票器进行投票,就这样。

上一篇:spring security (史上最全)


下一篇:通过jquery定位元素