Spring Security3学习-鉴权过滤器

鉴权过滤器FilterSecurityInterceptor是11个默认过滤器的最后一个,也是流程很复杂的一个过滤器。它的鉴权不仅仅针对web领域,我们主要讨论对web的鉴权



 这个图是大概的流程,首先有几个相关的对象

FilterSecurityInterceptor:安全过滤器

SecurityMetadataSource:资源元数据

ConfigAttribute:访问该资源的配置信息

AccessDecisionManager:访问决策管理器,可以用<http>元素的access-decision-manager-ref属性来指明一个实现了AccessDecisionManager的Spring Bean。

AuthenticationManager:认证管理器,通过getAuthorities()方法返回权限列表,这里是由ProviderManager实现的。

voter(投票器)是在授权过程中的一个重要角色,它的作用是评估以下的内容: 

  • 要访问受保护资源的请求所对应上下文(如URL请求的IP 地址); 
  • 用户的凭证信息(如果存在的话); 
  • 要试图访问的受保护资源; 
  • 系统的配置以及要访问资源本身的配置参数。

怎么配置的,我们看解析security命名空间时对过滤器的初始化方法createFilterSecurityInterceptor

    void createFilterSecurityInterceptor(BeanReference authManager) {
        //解析use-expressions  
        boolean useExpressions = FilterInvocationSecurityMetadataSourceParser.isUseExpressions(httpElt);
        //配置资源元数据对象
        BeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc);
        //访问决策器对象
        RootBeanDefinition accessDecisionMgr;
        //voter对象集合
        ManagedList<BeanDefinition> voters =  new ManagedList<BeanDefinition>(2);

        if (useExpressions) {
            voters.add(new RootBeanDefinition(WebExpressionVoter.class));
        } else {
            voters.add(new RootBeanDefinition(RoleVoter.class));
            voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
        }
        //访问决策对象默认是AffirmativeBased,即如果有任何一个投票器允许访问,请求将被立刻允许,而不管之前可能有的拒绝决定。
        accessDecisionMgr = new RootBeanDefinition(AffirmativeBased.class);
        accessDecisionMgr.getPropertyValues().addPropertyValue("decisionVoters", voters);
        accessDecisionMgr.setSource(pc.extractSource(httpElt));

        // Set up the access manager reference for http
        // 获取配置文件中决策控制器bean的id
        String accessManagerId = httpElt.getAttribute(ATT_ACCESS_MGR);

        //如果配置文件没有指定访问控制器,定义默认的
        if (!StringUtils.hasText(accessManagerId)) {
            accessManagerId = pc.getReaderContext().generateBeanName(accessDecisionMgr);
            pc.registerBeanComponent(new BeanComponentDefinition(accessDecisionMgr, accessManagerId));
        }
        //设置过滤器对象
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterSecurityInterceptor.class);
        
        builder.addPropertyReference("accessDecisionManager", accessManagerId);
        //设置authenticationManager认证对象
        builder.addPropertyValue("authenticationManager", authManager);
        //设置observeOncePerRequest属性,是否每个请求只鉴权一次
        if ("false".equals(httpElt.getAttribute(ATT_ONCE_PER_REQUEST))) {
            builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
        }

        builder.addPropertyValue("securityMetadataSource", securityMds);
        BeanDefinition fsiBean = builder.getBeanDefinition();
        String fsiId = pc.getReaderContext().generateBeanName(fsiBean);
        pc.registerBeanComponent(new BeanComponentDefinition(fsiBean,fsiId));

        // Create and register a DefaultWebInvocationPrivilegeEvaluator for use with taglibs etc.
        BeanDefinition wipe = new RootBeanDefinition(DefaultWebInvocationPrivilegeEvaluator.class);
        wipe.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(fsiId));

        pc.registerBeanComponent(new BeanComponentDefinition(wipe, pc.getReaderContext().generateBeanName(wipe)));

        this.fsi = new RuntimeBeanReference(fsiId);
    }

 下面再看默认的鉴权流程

FilterSecurityInterceptor继承了AbstractSecurityInterceptor,看他的doFilter方法

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        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) {
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }

            // 鉴权发生在这里
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                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());
        }

        // 获取此次访问的资源元数据,默认是从ExpressionBasedFilterInvocationSecurityMetadataSource,根据url和http method获取的
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);

        if (attributes == null) {
            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);
        }

        // authentication对象也不能为空
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"), object, attributes);
        }

        // 校验authentication对象,如果isAuthenticated是flase则再次校验,不通过会抛出AuthenticationException
        Authentication authenticated = authenticateIfRequired();

        // Attempt authorization
        try {
            //访问决策管理器的decide方法
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                    accessDeniedException));
            // 如果鉴权失败抛出AccessDeniedException
            throw accessDeniedException;
        }

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

        publishEvent(new AuthorizedEvent(object, attributes, authenticated));

        // Attempt to run as a different user
        // 将此资源元数据中以RUN_AS开头的角色赋予此用户
        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(authenticated, false, attributes, object);
        } else {
            if (debug) {
                logger.debug("Switching to RunAs Authentication: " + runAs);
            }

            SecurityContextHolder.getContext().setAuthentication(runAs);

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

 再看访问控制管理器的decide方法,默认的控制器是AffirmativeBased,只要有一个投票器通过就通过。

    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;
            // 不通过数+1
            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();
    }

 在鉴权不通过时都会抛出AccessDeniedException异常,整个鉴权的流程大概就是这样的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值