Spring Security授权源码分析

授权是在用户认证通过后,对访问资源的权限进行检查的过程。
Spring Security使用标准 Filter建立了对 web请求的拦截,最终实现对资源的授权访问。

在这里插入图片描述
关于授权,这里查看几个重要的 Filter 的处理逻辑。

一、SecurityContextPersistenceFilter

针对 ServletRequest进行了一次包装,使得 request具有更加丰富的 API。

查看 doFilter方法:
在这里插入图片描述

  • 通过repo.loadContext从请求中获取session,然后将 session信息保存在context中,方便后面 filter直接获取当前的用户信息。
  • 通过 SecurityContextHolder.setContext(contextBeforeChainExecution); 设置 context信息到contextHolder中(将context信息放在了ThreadLocal中,线程安全的)。

1)repo.loadContext方法
在这里插入图片描述

2)setContext方法
在这里插入图片描述

二、AnonymousAuthenticationFilter

Spring Security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。

AnonymousAuthenticationFilter过滤器是在 UsernamePasswordAuthenticationFilter等过滤器之后,如果它前面的过滤器都没有认证成功,即认证信息为空,AnonymousAuthenticationFilter会创建一个匿名用户(一个 Authenticaiton 的匿名实现类 AnonymousAuthenticationToken)存入到当前 SecurityContextHolder中。

查看 doFilter方法:
在这里插入图片描述

  • 判断 SecurityContextHolder中Authentication为否为空;
  • 如果空则为当前的 SecurityContextHolder中添加一个匿名的 AnonymousAuthenticationToken(用户名为 anonymousUser的 AnonymousAuthenticationToken)

三、ExceptionTranslationFilter

ExceptionTranslationFilter异常处理过滤器,用来处理在系统认证授权过程中抛出的异常(也就是下一个 FilterSecurityInterceptor过滤器)。

ExceptionTranslationFilter过滤器主要是拦截处理 AuthenticationExceptionAccessDeniedException异常并添加到HTTP响应中。其他异常它会捕获 Filter处理的异常,抛给下一个 Filter去处理。

查看 doFilter方法:
在这里插入图片描述
在这里插入图片描述

四、FilterSecurityInterceptor

FilterSecurityInterceptor过滤器是认证授权过滤器链中最后一个过滤器,获取所配置资源访问的授权信息,根据 SecurityContextHolder中存储的用户信息来决定其是否有权限。

该过滤器之后就交由 Spring MVC,访问到我们的controller方法。

查看 doFilter方法:

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        this.invoke(fi);
    }

	/**
	 *  doFilter实际执行的方法
	 * @param filterInvocation 封装了request response 过滤器链的对象
	 */
	public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
		// 1. 如果已经执行过该过滤器,直接放行
		if (isApplied(filterInvocation) && this.observeOncePerRequest) {
			// filter already applied to this request and user wants us to observe
			// once-per-request handling, so don't re-do security checking
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
			return;
		}
		// first time this request being called, so perform security checking
		// 2. 第一次调用这个请求,所以执行安全检查
		if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
			// 3. 在request中添加__spring_security_filterSecurityInterceptor_filterApplied = true,表示执行了该过滤器
			filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
		}
		// 4. 前置访问控制处理
		InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
		try {
			// 5. 放行
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
		} finally {
			super.finallyInvocation(token);
		}
		// 6. 后置处理
		super.afterInvocation(token, null);
	}

比较重要的方法

  • beforeInvocation
  • finallyInvocation
  • afterInvocation

1、查看 beforeInvocation方法

进入父类 AbstractSecurityInterceptor的 beforeInvocation方法进行处理,最终返回一个 InterceptorStatusToken对象,它就是 Spring Security处理鉴权的入口。

    protected InterceptorStatusToken beforeInvocation(Object object) {
		// 1. 判断object是不是FilterInvocation
        if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
            throw new IllegalArgumentException(...);
        } else {
            // 2. 获取配置的访问控制规则 any request => authenticated ,没有配置,return null
            Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);

            ...

                // 3. 获取Authentication对象
                Authentication authenticated = this.authenticateIfRequired();

                try {
                    // 4. 进行授权判断(重点)
                    this.accessDecisionManager.decide(authenticated, object, attributes);
                } catch (AccessDeniedException var7) {
                    this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));
                    throw var7;
                }

                if (debug) {
                    this.logger.debug("Authorization successful");
                }
				// 5. 发布授权成功
                if (this.publishAuthorizationSuccess) {
                    this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
                }
				// 6. 对Authentication进行再处理,这里没有处理,直接返回null
                Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
                if (runAs == null) {
                    if (debug) {
						this.logger.debug("RunAsManager did not change Authentication object");
                    }
				// 7. 返回 InterceptorStatusToken
                    return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
                } else {
                    if (debug) {
                        this.logger.debug("Switching to RunAs Authentication: " + runAs);
                    }

                    SecurityContext origCtx = SecurityContextHolder.getContext();
                    SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
                    SecurityContextHolder.getContext().setAuthentication(runAs);
                    // 7. 返回 InterceptorStatusToken
                    return new InterceptorStatusToken(origCtx, true, attributes, object);
                }
            } else if (this.rejectPublicInvocations) {
                throw new IllegalArgumentException(...);
            } else {
                if (debug) {
                    this.logger.debug("Public object - authentication not attempted");
                }

                this.publishEvent(new PublicInvocationEvent(object));
                return null;
            }
        }
    }

1.1 AffirmativeBased授权处理器

AccessDecisionManager 是如何授权的?
Spring Security默认使用 AffirmativeBased实现 AccessDecisionManager 的 decide 方法来实现授权。它会调用授权管理器进行决策,当失败发生异常时,会爆出异常。
在这里插入图片描述
调用 AccessDecisionVoter 进行vote(投票)

  • 只要有投通过(ACCESS_GRANTED)票,则直接判为通过。
  • 如果没有投通过则 ++deny ,
  • 最后判断if(deny>0 抛出AccessDeniedException(未授权)

在这里插入图片描述

1.2 WebExpressionVoter.vote()

这里简单查看 WebExpressionVoter.vote()方法:

    public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {
        assert authentication != null;

        assert fi != null;

        assert attributes != null;

        // 1. 获取http配置项
        WebExpressionConfigAttribute weca = this.findConfigAttribute(attributes);
        // 2. 没有配置规则,弃权
        if (weca == null) {
            return 0;
        } else {
            // 3. 对EL表达式进行处理
            EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, fi);
            ctx = weca.postProcess(ctx, fi);
            // 5. 符合条件,则投赞成票,否则反对票
            return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? 1 : -1;
        }
    }

投票完成,回到 beforeInvocation方法,最后返回 InterceptorStatusToken对象。

2、查看 finallyInvocation方法

授权成功处理:没有抛出异常,则认为授权通过,FilterSecurityInterceptor会进入 finallyInvocation方法。
这个方法主要是判断需不需要重新设置 SecurityContext内容,这里没有配置,直接跳过。
在这里插入图片描述

3、查看 afterInvocation方法

接下来进入 afterInvocation方法,再次调用了 finallyInvocation方法,然后查询是否还有决策后置处理器,如果有,再次进行决策。
最后的最后,才代表授权成功,就交由 Spring MVC,访问到我们的 controller方法了。

    protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
        if (token == null) {
            return returnedObject;
        } else {
            this.finallyInvocation(token);
            if (this.afterInvocationManager != null) {
                try {
                    returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(), token.getSecureObject(), token.getAttributes(), returnedObject);
                } catch (AccessDeniedException var5) {
                    AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(), token.getSecurityContext().getAuthentication(), var5);
                    this.publishEvent(event);
                    throw var5;
                }
            }

            return returnedObject;
        }
    }

– 求知若饥,虚心若愚。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值