Spring Security权限控制源码分析

流程图

在这里插入图片描述
在这里插入图片描述

类和接口介绍

  • FilterSecurityInterceptor:是整个权限判断流程的入口,包含着请求的相关信息;
  • AccessDecisionManager:是一个接口,有一个抽象实现(AbstractAccessDecisionManager)和三个具体实现(AffirmativeBased、ConsensusBased和UnanimousBased)
  • AbstractAccessDecisionManager: 该实现中维护者一组AccessDecisionVoter接口;
  • AccessDecisionVoter:对每个权限请求进行投票(Spring Security3之前的voter);
  • AffirmativeBased:积极的策略,即对于一个权限判断,不管有多少个Voter投不通过,只要有一个投票通过,该权限就通过;该策略是spring默认的判断策略;
  • ConsensusBased:共识的策略,即比较投票通过和不通过的票数,以票数最多的为准;
  • UnanimousBased:全部通过策略,即对于一个权限判断,不管有多少个投票通过,只要有一个投票不通过,该权限就不通过;
  • SecurityConfig:Spring Security的配置文件,用于配置哪些请求放行,哪些请求要经过过滤器以及tokenStoke、enhance等;
  • ConfigAttribute:FilterSecurityInterceptor会从SecurityConfig中读取配置信息,并封装成一组ConfigAttribute对象,每个ConfigAttribute对象代表着一个URL它所需要的权限;
  • Authentication:封装着当前用户所拥有的权限信息;
  • WebExpressionVoter: Spring Security3新增的voter,在web服务中它包含了所有的voter,即它投票过就通过、不过就不通过;

该流程中一共包含三组信息:1. FilterSecurityInterceptor所携带的用户请求信息,2. SecurityConfig配置的权限信息,3. Authentication当前用户所拥有的权限信息。 将这三组权限信息传递给AbstractAccessDecisionManager,它通过AccessDecisionVoter对当前请求是否拥有相应权限进行投票,spring根据设定的投票策略判断当前用户是否拥有他所请求资源的权限。

源码分析

FilterSecurityInterceptor:doFilter方法
public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		FilterInvocation fi = new FilterInvocation(request, response, chain);
		
		// 参考下方:invoke方法
		invoke(fi);
	}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
		// 判断当前请求是否已经经过当前过滤器
		if ((fi.getRequest() != null)
				&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
				&& observeOncePerRequest) {
			// filter already applied to this request and user wants us to observe
			// once-per-request handling, so don't re-do security checking
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		}
		else {
			// first time this request being called, so perform security checking
			if (fi.getRequest() != null) {
				fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
			}

			// 在调用后面的过滤器之前,调用AbstractSecurityInterceptor的beforeInvocation方法
			// 参考下方:beforeInvocation方法
			InterceptorStatusToken token = super.beforeInvocation(fi);

			try {
				fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
			}
			finally {
				super.finallyInvocation(token);
			}

			super.afterInvocation(token, null);
		}
	}

AbstractSecurityInterceptor: beforeInvocation方法

protected InterceptorStatusToken beforeInvocation(Object object) {
		Assert.notNull(object, "Object was null");
		final boolean debug = logger.isDebugEnabled();

		if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
			throw new IllegalArgumentException(
					"Security invocation attempted for object "
							+ object.getClass().getName()
							+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
							+ getSecureObjectClass());
		}

		// 获取当前请求应该具有的权限信息
		// 参考下方getAttributes方法
		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);

		if (attributes == null || attributes.isEmpty()) {
			if (rejectPublicInvocations) {
				throw new IllegalArgumentException(
						"Secure object invocation "
								+ object
								+ " was denied as public invocations are not allowed via this interceptor. "
								+ "This indicates a configuration error because the "
								+ "rejectPublicInvocations property is set to 'true'");
			}

			if (debug) {
				logger.debug("Public object - authentication not attempted");
			}

			publishEvent(new PublicInvocationEvent(object));

			return null; // no further work post-invocation
		}

		if (debug) {
			logger.debug("Secure object: " + object + "; Attributes: " + attributes);
		}

		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			credentialsNotFound(messages.getMessage(
					"AbstractSecurityInterceptor.authenticationNotFound",
					"An Authentication object was not found in the SecurityContext"),
					object, attributes);
		}

		// 获取当前用户所拥有的权限信息
		// 参考下方:authenticateIfRequired方法
		Authentication authenticated = authenticateIfRequired();

		// Attempt authorization
		try {
			// 调用AffirmativeBased的decide方法
			// authenticated: 封装了当前用户所拥有的权限信息
			// object: 封装了当前请求所携带的信息
			// attributes: 封装了当前请求应该具有的权限信息
			// 参考下方:decide方法
			this.accessDecisionManager.decide(authenticated, object, attributes);
		}
		catch (AccessDeniedException accessDeniedException) {
			publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
					accessDeniedException));

			throw accessDeniedException;
		}

		if (debug) {
			logger.debug("Authorization successful");
		}

		if (publishAuthorizationSuccess) {
			publishEvent(new AuthorizedEvent(object, attributes, authenticated));
		}

		// Attempt to run as a different user
		Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
				attributes);

		if (runAs == null) {
			if (debug) {
				logger.debug("RunAsManager did not change Authentication object");
			}

			// no further work post-invocation
			return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
					attributes, object);
		}
		else {
			if (debug) {
				logger.debug("Switching to RunAs Authentication: " + runAs);
			}

			SecurityContext origCtx = SecurityContextHolder.getContext();
			SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
			SecurityContextHolder.getContext().setAuthentication(runAs);

			// need to revert to token.Authenticated post-invocation
			return new InterceptorStatusToken(origCtx, true, attributes, object);
		}
	}

DefaultFilterInvocationSecurityMetadataSource: getAttributes方法

public Collection<ConfigAttribute> getAttributes(Object object) {
		final HttpServletRequest request = ((FilterInvocation) object).getRequest();
		// 1. SecurityConfig 配置的URL权限信息都会在requestMap中记录
		// 2. 判断当前请求中是否有与配置信息匹配,如果匹配的话获取到当前请求应有的权限
		for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
				.entrySet()) {
			if (entry.getKey().matches(request)) {
				return entry.getValue();
			}
		}
		return null;
	}

AbstractSecurityInterceptor: authenticateIfRequired方法

private Authentication authenticateIfRequired() {
		Authentication authentication = SecurityContextHolder.getContext()
				.getAuthentication();

		if (authentication.isAuthenticated() && !alwaysReauthenticate) {
			if (logger.isDebugEnabled()) {
				logger.debug("Previously Authenticated: " + authentication);
			}

			return authentication;
		}

		authentication = authenticationManager.authenticate(authentication);

		// We don't authenticated.setAuthentication(true), because each provider should do
		// that
		if (logger.isDebugEnabled()) {
			logger.debug("Successfully Authenticated: " + authentication);
		}

		SecurityContextHolder.getContext().setAuthentication(authentication);

		return authentication;
	}

AffirmativeBased:decide方法

public void decide(Authentication authentication, Object object,
Collection 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"));
	}

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

}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值