超简单超详细的SpringSecurity鉴权源码解析

FilterSecurityInterceptor

FilterSecurityInterceptor是一个用于授权的过滤器 位于security过滤链的最尾部
其继承了AbstractSecurityInterceptor抽象类
首先来看看FilterSecurityInterceptor的doFilter方法 由于过滤器的特性 doFilter是过滤器执行的入口

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		/**
		 * 这个FilterInvocation对象它的功能很简单,
		 * 就是将上一个过滤器传递过滤的request,response,chain复制保存到FilterInvocation里 
		 * 也有人说FilterInvocation起到了解耦作用 避免了与其他过滤器使用相同的引用变量
		 */
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        //执行方法invoke
        this.invoke(fi);
    }

public void invoke(FilterInvocation fi)

public void invoke(FilterInvocation fi) throws IOException, ServletException {
		//这里的if用作安全检查 如果当前请求不是第一次请求 且拥有_spring_security_filterSecurityInterceptor_filterApplied属性
		//则可以直接放行 不进行鉴权
        if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
        	//直接放行
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } else {
        	//否则进行安全检查 observeOncePerRequest默认为true
            if (fi.getRequest() != null && this.observeOncePerRequest) {
               //为当前请求设置属性 这里设置完以后 之后同样的请求就可以不进行鉴权直接访问了
              fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
            }
			//鉴权入口方法 调用了父类AbstractSecurityInterceptor的beforeInvocation方法 ★
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
            	//执行过滤链
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
            	//清空SecurityContext上下文内容 将原来的上下文内容恢复
            	/**
            	 * 因为在beforeInvocation里可能调用RunAsManager的方法替换了验证身份
            	 * 导致SecurityContext里的内容与原来不一致
            	 * 以上代码执行失败与否 这个方法都执行
            	 */
                super.finallyInvocation(token);
            }

            super.afterInvocation(token, (Object)null);
        }

    }

beforeInvocation

protected InterceptorStatusToken beforeInvocation(Object object) {
        ...
        //if条件仅是判断 object是不是FilterInvocation类型的对象
        if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
            //这里是抛异常代码 已省略
        } else {
        	//这里就是拿到我们配置的权限信息 如什么角色可以访问什么方法等
            Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
            if (attributes != null && !attributes.isEmpty()) {
                //如果当前security上下文中没有用户认证信息就抛异常
                if (SecurityContextHolder.getContext().getAuthentication() == null) {
                    //这里抛 认证信息没找到的异常 已省略
                }
				//authenticateIfRequired方法用于认证用户信息 如果当前用户信息还没认证 则会在这里进行认证并放入security上下文后返回认证对象
                Authentication authenticated = this.authenticateIfRequired();

                try {
                	//重点 这里就是用于鉴权的方法 如果没抛出异常则说明鉴权通过 详解请看下一小节
                    this.accessDecisionManager.decide(authenticated, object, attributes);
                } catch (AccessDeniedException var7) {
                    //略
                }
				//告诉spring容器中其他bean 鉴权成功的信息
                if (this.publishAuthorizationSuccess) {
                    this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
                }
				/**
				 * 在极少数情况下,用户可能希望使用不同的Authentication替换SecurityContext中的Authentication
				 * 这在合理的异常情况下可能很有用,例如服务层方法需要调用远程系统并呈现不同的身份。
				 */
                Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
                if (runAs == null) {
                	//返回一个没有改变过的SecurityContext
                    return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
                } else {
                    //如果替换了用户认证信息 获得原来的securityContext上下文对象
                    SecurityContext origCtx = SecurityContextHolder.getContext();
                    //然后新建一个SecurityContext上下文
                    SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
                    //在新建的上下文对象中放入替换的认证信息
                    SecurityContextHolder.getContext().setAuthentication(runAs);
                    //将原来的上下文对象及刷新上下文标志 权限list等信息返回
                    return new InterceptorStatusToken(origCtx, true, attributes, object);
                }
            //rejectPublicInvocations默认为false 当为true时将拒绝所有没有带权限list的资源的访问
            } else if (this.rejectPublicInvocations) {
                //抛异常
            } else {
				//告诉spring容器中其他bean 本次请求没有通过鉴权
                this.publishEvent(new PublicInvocationEvent(object));
                return null;
            }
        }
    }

AccessDecisionManager

AccessDecisionManager是一个接口,它负责鉴定用户是否有访问对应资源(方法或URL)的权限。
它声明了三个方法,除了第一个鉴权方法以外,还有两个是辅助性的方法,其作用都是甄别 decide方法中参数的有效性。

public interface AccessDecisionManager {
    void decide(Authentication var1, Object var2, Collection<ConfigAttribute> var3) throws AccessDeniedException, InsufficientAuthenticationException;

    boolean supports(ConfigAttribute var1);

    boolean supports(Class<?> var1);
}

AccessDecisionManager 共有三个实现类 内部采用投票的机制调用投票方法来决定当前请求是否通过鉴权
在这里插入图片描述

  • AffirmativeBased 一票赞成就可以通过 默认实现类
  • UnanimousBased 一票反对就不能通过
  • ConsensusBased 少数服从多数

现在来看看AffirmativeBased默认实现类的实现方法decide,使用此方法,将根据授权决策轮询一系列 AccessDecisionVoter 实现。

public void decide(Authentication authentication, Object object, Collection configAttributes)

public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
        int deny = 0;
        //获取所有投票器
        Iterator var5 = this.getDecisionVoters().iterator();

        while(var5.hasNext()) {
        	//通过迭代器获取一个投票类
            AccessDecisionVoter voter = (AccessDecisionVoter)var5.next();
            //调用投票方法 返回投票结果
            int result = voter.vote(authentication, object, configAttributes);
            //日志代码 略

            switch(result) {
            case -1:
                ++deny;
                break;
            case 1:
            	//如果投票结果通过 则返回
                return;
            }
        }
		//AccessDecisionManager 根据其对投票的评估来决定是否抛出 AccessDeniedException
        if (deny > 0) {
            throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        } else {
            this.checkAllowIfAllAbstainDecisions();
        }
    }

到此 鉴权结束 感谢观看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值