spring security 用户授权原理

目录

1、授权步骤

2、基于Filter的授权流程

3、授权源码分析

4、AccessDecisionManager 的三种实现


1、授权步骤

(1)URL 权限控制:调用 HttpSecurity#authorizeRequests() 方法,开始配置 URL 的权限控制。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin();//表单提交
        // 授权 -> 认证拦截
        http.authorizeRequests()
                .antMatchers("/login.html", "/error.html", "/main.html").permitAll()
                .antMatchers("/admin/test").hasAnyAuthority("admin")
                .anyRequest().authenticated();// 所有请求都必须认证
        http.csrf().disable(); //关闭csrf防护
    }

(2)方法权限控制:修改 WebSecurityConfig配置类,增加 @EnableGlobalMethodSecurity 注解,开启对 Spring Security 注解的方法,进行权限验证。

@Configuration // 标记为注解类
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
}

2、基于Filter的授权流程

FilterSecurityInterceptor 进行资源拦截,当拦截资源为方法时,还会进入到拦截器MethodSecurityInterceptor,它们的继承关系如下图

授权流程如下

1.拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中的FilterSecurityInterceptor 的子类拦截。

2. 获取资源访问策略,FilterSecurityInterceptor 会从 SecurityMetadataSource 的子类DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限Collection。SecurityMetadataSource其实就是读取访问策略的抽象,读取的内容就是我们配置的访问规则。

3. 最后,FilterSecurityInterceptor 会调用 AccessDecisionManager 进行授权决策,若决策通过,则允许访问资源,否则将禁止访问。

3、授权源码分析

进入 FilterSecurityInterceptor,进入 FilterSecurityInterceptor#doFilter 方法

    public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		FilterInvocation fi = new FilterInvocation(request, response, chain);
        // 从 invoke 开始
		invoke(fi);
	}

然后进入 invoke 方法

    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 && observeOncePerRequest) {
				fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
			}
            // 通过这个方法进行权限验证
			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());
		}
        // 1-获取访问资源的权限->资源是authenticated,还是permitAll
		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);
		}
        // 2-获取登陆用户的权限
		Authentication authenticated = authenticateIfRequired();

		// Attempt authorization
		try {
            // 3-核心投票方法:尝试进行权限验证
			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);
		}
	}

进入权限验证决策方法,AccessDecisionManager#decide,AccessDecisionManager 有三个实现,默认使用 AffirmativeBased,其中两种决策方式,文章后边也会介绍。此处,我们先看第一个

进入 AffirmativeBased#decide 方法,我们去看它的决策实现

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; // 弃权 -> 继续下一次循环
			}
		}
        // 如果统计拒绝次数 > 0
		if (deny > 0) {
			throw new AccessDeniedException(messages.getMessage(
					"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}
        // 如果每个投票者都选择放弃,默认也会抛出 AccessDeniedException
        // private boolean allowIfAllAbstainDecisions = false; 是否允许放弃决策,默认flase
		checkAllowIfAllAbstainDecisions();
	}

至此,投票决策逻辑分析完成。

4、AccessDecisionManager 的三种实现

AccessDecisionManager 采用投票的方式来确定是否能够访问受保护资源。 AccessDecisionManager 中包含的一系列 AccessDecisionVoter 将会被用来对Authentication是否有权访问受保护对象进行投票,AccessDecisionManager根据投票结果,做出最终决策。

// 做出最终的访问控制(授权)决策。
public interface AccessDecisionManager {
	// ~ Methods
	// 为传递的参数解析访问控制决策。
    // authentication 调用方法的调用者(非空)
	// object 对象被调用的受保护对象
	// configAttributes 与被调用的受保护对象关联的配置属性
	void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
			InsufficientAuthenticationException;

    // 指示此AccessDecisionManager是否能够处理传入的ConfigAttribute。
	boolean supports(ConfigAttribute attribute);

    // 指示AccessDecisionManager实现是否能够为指定的受保护对象类型提供访问控制决策。
	boolean supports(Class<?> clazz);
}

AffirmativeBased

如果有 AccessDecisionVoter 的投票为 ACCESS_GRANTED 则同意用户进行访问,但如果统计到反对票,则将抛出AccessDeniedException。

    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"));
		}
		// 全体弃票,抛出异常
		checkAllowIfAllAbstainDecisions();
	}

Spring security默认使用的是AffirmativeBased。

ConsensusBased(少数服从多数)

如果赞成票多于反对票则表示通过,如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions 的值为true,则表示通过,否则将抛出异常AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认为true。

public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
		int grant = 0;
		int deny = 0;
		for (AccessDecisionVoter voter : getDecisionVoters()) {
            // 1-获取投票结果
			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) { // 2-比较同意次数和拒绝次数
			return;
		}
		if (deny > grant) {
			throw new AccessDeniedException(messages.getMessage(
					"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}
		if ((grant == deny) && (grant != 0)) { // 3-判断是否允许两个次数相等的情况
			if (this.allowIfEqualGrantedDeniedDecisions) {
				return;
			}
			else {
				throw new AccessDeniedException(messages.getMessage(
						"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
			}
		}
		// 4-对弃权票的处理
		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;
				}
			}
		}
		// To get this far, there were no deny votes
		if (grant > 0) { // 没有拒绝票
			return;
		}
		// To get this far, every AccessDecisionVoter abstained
		checkAllowIfAllAbstainDecisions();
	}

至此,AccessDecisionManager 的三种实现分析完毕。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值