spring security初探之授权原理源码解析

说说授权

授权的定义是:判断某用是否拥有访问某资源的权限。我们这里说的资源被定义为URL(当然也可以是方法,但这不在我们讨论的范围内),权限我们这里定义为角色,那么对授权的定义我们就转化成,判断用户是否拥有访问某条URL的角色。

<!-- 下面直接开始授权原理的源码剖析 -->

<!-- 先利用强大的互联网工具,大致了解授权的原理,以方便我们解读源码 -->

<!-- 关于授权的方法大佬已经说得十分清楚了 -->

<!-- 下面是原文链接 -->

<!-- http://www.jmatrix.org/spring/spring-security/121.html -->

<!-- 相信看了他的文章,基本对于授权也没有疑问了 -->

<!-- 对大牛工作的总结:首先,我们明确一点,处理授权的默认filter是FilterSecurityInterceptor -->

<!-- FilterSecurityInterceptor继承AbstractFilterSecurityInterceptor,而实际上很多工作都由其父类搞定了 -->

<!-- AbstractFilterSecurityInterceptor负责三个工作 -->

<!-- 从SecurityMetadataSource获取与资源相关的权限信息列表 -->

<!-- 从AuthenticationManager中获取与用户相关的角色信息 -->

<!-- 送到DecisionManager中进行决策 -->

<!-- 关于下面继续调用doFilter和afterInvocation我暂时没什么头绪,我们暂时选择性忽略 -->

<!-- 现在看一下源码,直接就看FilterSecurityInte(该类位于org.springframework.security.web.access.intercept) -->

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        //封装请求
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        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);
            }
            //我们暂时只关心这一句,所有的授权逻辑都在这里进行
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                super.finallyInvocation(token);
            }
            //应该是进行了将token加载进Authentication对象的工作
            super.afterInvocation(token, null);
        }
    }

<!-- 因为FilterSecurityInterceptor大部分的代码已经封装到父类AbstractFilterSecurityInterceptor(该类位于org.springframework.security.access.intercept),所以这个类目前而言稍显单薄 -->

<!-- 相对的,要定制的话也相当简单,我们只需要让我们的filter继承AbstractFilterSecurityInterceptor,插入到原filter链中该filter的前面,这样我们的filter就先与原filter被调用(未实践) -->

<!-- 我们能够想象这一步是授权,授权成功返回一个token对象,token对象应该会被保存在Authentication对象中,表明之后它都有权访问该资源,而不用重新授权 -->

<!-- 那么接下来然我们继续看看beforeInvocation做了什么吧 -->

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

        //判断是否为FilterInvocation类,不是则不处理,这里可以看出spring security委派任务时候采取的方式
        //基本都是基于这种父类的判断,这也是接口代理带来的方便
        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());
        }

        //调用metadataSource的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);
        }

        //如其名,如果需要,认证并获得认证对象
        //内部实现比较简单,就简单说一下
        //首先从上下文获取Authentication对象
        //判断是否通过认证,通过则直接return该对象
        //未通过则调用AuthenticationManager进行认证并返回认证对象,认证失败直接抛异常,用户捕获异常跳转到登陆页面或者其他页面
        Authentication authenticated = authenticateIfRequired();

        // Attempt authorization
        try {
            //核心语句,授权
            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);
        }
    }

<!-- 虽然对于获取认证对象的方法不用深究,毕竟之前探究过,但是对于getAttributes方法我们不得不深究了 -->

<!-- 下面是getAttributes的某个实现 -->

    public Collection<ConfigAttribute> getAttributes(Object object) {
        final HttpServletRequest request = ((FilterInvocation) object).getRequest();
        for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
                .entrySet()) {
            if (entry.getKey().matches(request)) {
                return entry.getValue();
            }
        }
        return null;
    }

<!-- 这个方法很有趣,我们可以猜想到,容器一开始就将URL和容器的对应关系以key-value的关系放进了requestMap里 -->

<!-- 然后这对请求的URL进行匹配,匹配到则返回对应的角色 -->

<!-- 这里存在一个问题,匹配到的可能是单个角色,如果是多个角色呢?那么这里就存在一种可能性————requestMap中的键值对是URL-ROLEList的关系 -->

<!-- 这个可能性在decide中能得到验证,所以接下来我们看AbstractAccessDecisionManager的实现类AffirmativeBased(该类位于org.springframework.security.access.vote)的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;
            }
        }

        if (deny > 0) {
            throw new AccessDeniedException(messages.getMessage(
                    "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        }

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

<!-- 看到AbstractDecisionManager的实现类AffirmativeBean实际并不授权,而是将授权工作交给voter,自己主要负责判断授权结果 -->

<!-- 根据switch方法我们能够大概判断vote方法应该返回一个整数代表授权结果 -->

<!-- 这里抛出一个疑问————多次授权,授权成功则直接return结束授权过程,授权失败则deny++,最后判断deny是否大于0,大于则授权失败,不允许访问,那么等于0的时候是什么意思呢?既不返回授权成功也不返回授权失败的时候意味着什么呢? -->

<!-- 对疑问的猜想:授权结果的三种情况————授权成功,授权失败因为未认证,授权失败因为通过认证但用户权限不足 -->

<!-- 不能统一处理,因为我们知道,授权成功会直接打开资源页面,未认证会跳转到登陆界面(一般论),那么用户权限不足,一般跳转到提示界面或者获取积分开启权限页面或者充值买会员页面 -->

<!-- voter我们主要看的是RoleVoter,那么接下来我们看RoleVoter(该类位于org.springframework.security.access.vote)的vote方法 -->、

    public int vote(Authentication authentication, Object object,
            Collection<ConfigAttribute> attributes) {
        if(authentication == null) {
            return ACCESS_DENIED;
        }
        int result = ACCESS_ABSTAIN;
        Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);

        for (ConfigAttribute attribute : attributes) {
            //this,一般是Voter对象调用,如果所需的权限继承Voter或者实现Voter的接口则直接授权失败?
            if (this.supports(attribute)) {
                result = ACCESS_DENIED;

                // Attempt to find a matching granted authority
                for (GrantedAuthority authority : authorities) {
                    //核心语句
                    if (attribute.getAttribute().equals(authority.getAuthority())) {
                        return ACCESS_GRANTED;
                    }
                }
            }
        }

        return result;
    }

    Collection<? extends GrantedAuthority> extractAuthorities(
            Authentication authentication) {
        return authentication.getAuthorities();
    }

<!-- 关于上面那个问题,且先放放,我们要继续前进!不过我们也已经达到目的了。 -->

<!-- 总结:负责捕获授权请求的Filter是FilterSecurityInterceptor,它的父类是AbstractFilterSecurityInterceptor,授权时候主要调用了父类的beforeInvocation方法,在这个方法里面做了三件事 -->

<!-- 1.获得用户的的权限列表;2.获得资源相关的权限列表;3。调用AccessDecisionManager的decide方法进行授权。-->

<!-- AccessDecisionManager的实现是AffirmativeBean(为什么不是其他两个呢?I dont know!不过迟早会了解的。现在刚开始不打算把所有东西的弄得十分透彻!因为那样时间成本大于收获!)。在这个类中它实际是把授权的工作交给了Voter来实现。然后自己只做了判断的工作,授权成功则返回null。授权失败抛出异常。-->

<!-- Voter我们的实现我们主要看RoleVoter(理由同上)它真正处理了授权,把资源需要的权限和用户所拥有的的权限取出来一一比对,返回授权结果。 -->

AccessDecisionManager的三种实现有何区别?

原来Spring Security内部对授权的实现采用投票机制,而AffirmativeBean、ConsensusBean和UnanimousBean是对Spring Security投票机制的三种实现,其中我们常用的AffirmativeBean是一票通过制+不否决等于通过制,而UnanimousBean是一票否决制,弃权问仲裁制。当然我们也可以自己实现我们自己的投票制度,这取决于业务。

发布了35 篇原创文章 · 获赞 4 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/jiujiuming/article/details/67637088