Springboot security cas源码陶冶-FilterSecurityInterceptor

前言:用户登录信息校验成功后,都会获得当前用户所拥有的全部权限,所以对访问的路径当前用户有无权限则需要拦截验证一发

Spring security过滤器的执行顺序

首先我们需要验证为啥FilterSecurityInterceptor会在UsernamePassowrdAuthenticationFilter/CasAuthenticationFilter之后,这里则可以去看下spring security包下的FilterComparator的构造函数便可以得知

FilterComparator() {
        int order = 100;
        ****
        ****
        order += STEP;
        put(CorsFilter.class, order);
        order += STEP;
        put(CsrfFilter.class, order);
        order += STEP;
        put(LogoutFilter.class, order);
        order += STEP;
        put(X509AuthenticationFilter.class, order);
        order += STEP;
        put(AbstractPreAuthenticatedProcessingFilter.class, order);
        order += STEP;
                filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",
                order);
        order += STEP;
        put(UsernamePasswordAuthenticationFilter.class, order);
        order += STEP;
        put(ConcurrentSessionFilter.class, order);
        order += STEP;
        filterToOrder.put(
                "org.springframework.security.openid.OpenIDAuthenticationFilter", order);
        order += STEP;
        ****
        ****
        order += STEP;
        put(AnonymousAuthenticationFilter.class, order);
        order += STEP;
        put(SessionManagementFilter.class, order);
        order += STEP;
        put(ExceptionTranslationFilter.class, order);
        order += STEP;
        put(FilterSecurityInterceptor.class, order);
        order += STEP;
        put(SwitchUserFilter.class, order);
    }

另外FilterComparator#compare()方法表明是按照order的从小到大排序,所以Filter的执行顺序便一目了然,重要的Filter执行顺序如下

LogoutFilter-->CasAuthenticationFilter-->
UsernamePasswordAuthenticationFilter-->
AnonymousAuthenticationFilter-->ExceptionTranslationFilter-->
FilterSecurityInterceptor

FilterSecurityInterceptor#doFilter()-执行逻辑

代码如下

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

进而查看下FilterSecurityInterceptor#invoke()方法

    //主要展示了具体的逻辑,涉及到父类方法的调用
    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        //对同一个请求的多次访问则放行
        if ((fi.getRequest() != null)
                && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
                && observeOncePerRequest) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }
        else {
            //第一次访问则需要拦截验证
            if (fi.getRequest() != null) {
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }
            /**
            **调用父类AbstractSecurityInterceptor方法进行校验
            **
            */
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                //是否需要重新设置spring的安全上下文SecurityContext
                super.finallyInvocation(token);
            }
            //处理@PostAuthorize and @PostFilter注解
            super.afterInvocation(token, null);
        }
    }

下面针对父类的方法进行分析

AbstractSecurityInterceptor#beforeInvocation-执行主要校验工作

由于代码偏长,截取重要代码片段分析

    protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        ***
        ***
        //一般通过SecurityMetadataSource对象获取当前用户访问路径对应的角色
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);
        //为空则抛异常或者返回null
        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);
        }
        //如果没有验证过则抛出AuthenticationException异常
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(messages.getMessage(
                    "AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"),
                    object, attributes);
        }
        //对于非token和非login请求
        //一般都会有默认的AnonymousAuthenticationFilter使其不再校验
        //所以此处一般不需要再次校验
        Authentication authenticated = authenticateIfRequired();

        // Attempt authorization 尝试授权
        try {
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                    accessDeniedException));
            //对于授权失败则会抛出异常,这个异常会由ExceptionTranslationFilter获取
            throw accessDeniedException;
        }
        ****
        ****

        // 默认不处理,runAs返回null
        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
                attributes);

        if (runAs == null) {
            //直接返回
            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);
        }
    }

小结

FilterSecurityInterceptor实现的作用有

  1. SecurityMetadataSource对象来获取当前访问路径对应的角色集合Collection<ConfigAttribute> attributes

  2. AccessDecisionManager对象来对获取到的角色集合进行校验,与Authentication.getAuthorities()集合进行对照

  3. 验证与授权过程中产生的异常AuthenticationExceptionAccessDeniedException会被ExceptionTranslationFilter拦截处理,从而请求casServer登录或者直接返回错误

转载于:https://www.cnblogs.com/question-sky/p/7065808.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值