FilterSecurityInterceptor源码解读

FilterSecurityInterceptor源码解读

FilterSecurityInterceptor概念:

获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权限。

要实现FilterSecurityInterceptor得有三个条件

(1)决策管理器:决定这个用户能不能访问这个资源
三种类型的决策器
  • AffirmativeBased:只要有一个voter投同意票,就授权成功,一般用这个
  • ConsensusBased:只要投同意票的大于投反对票的,就授权成功
  • UnanimousBased:需要一致通过才授权成功
投票器voter
  • voter实际上是RoleVoter、AuthenticatedVoter,在决策管理器内部投票,决定权限通过认证

  • RoleVoter的投票方法:

    public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {  
        int result = ACCESS_ABSTAIN;  
        //从认证实体中获取所有的权限列表  
        Collection<GrantedAuthority> authorities = extractAuthorities(authentication);  
        //循环intercept-url配置的access权限列表  
        for (ConfigAttribute attribute : attributes) {  
            //supports方法是验证权限属性头不为空,且以ROLE_开头
            if (this.supports(attribute) {  
                result = ACCESS_DENIED;  
      
                // Attempt to find a matching granted authority  
                //循环认证实体所拥有的权限列表  
                for (GrantedAuthority authority : authorities) {  
                    if (attribute.getAttribute().equals(authority.getAuthority())) {  
                        //只要有相同的权限,直接返回成功1  
                        return ACCESS_GRANTED;  
                    }  
                }  
            }  
        }  
      
        return result;  
    }  
    
    +  voter.vote(authentication, object, configAttributes);==这三个参数也是传入决策器决策decide方法的三个参数== //返回整数,代表权限认证成功与否
    

    1.Authentication authentication 认证的用户对象,里面有账户密码,这个用户拥有的权限

    ​ 2.object是FilterInvocation fi = new FilterInvocation(request, response, chain); (封装request, response, chain,方便参数传递、增加代码阅读性)

    ​ 3.Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);

    ConfigAttribute类简单的看作是一个角色名称,在投票的时如果拥有该角色即投赞成票。//根据SecurityMetadataSource 获取配置的权限属性,即访问这个资源需要的权限(FilterSecurityInterceptor里面获得的)根据SecurityMetadataSource安全元数据 获取配置的权限属性 。这里获取的是权限列表信息,比如说有这个配置
    <security:intercept-url pattern="/index.jsp*" access=“ROLE_USER,ROLE_ADMIN”/>
    如果现在发起一个请求时index.jsp,那么根据这个请求返回的attributes集合就是分别包含ROLE_USER,ROLE_ADMIN属性的两个SecurityConfig对象

(2)认证管理器:AuthenticationManager authenticationManager

用来管理用户认证(登录)相关事宜,这里 是为了在监听器里面判断是否需要对认证实体重新认证,默认为否 Authentication authenticated = authenticateIfRequired();

(3)安全元数据:BeanDefinition securityMetadataSource
  • BeanDefinition在Spring中是用来描述Bean对象的,其不是一个bean实例,仅仅是包含bean实例的所有信息,比如属性值、构造器参数以及其他信息。Bean对象创建是根据BeanDefinitionc中描述的信息来创建的,BeanDefinitionc存在的作用是为了可以方便的进行修改属性值和其他元信息,比如通过BeanFactoryPostProcessor进行修改一些信息,然后在创建Bean对象的时候就可以结合原始信息和修改后的信息创建对象了。
  • BeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc); //根据intercept-url标签列表创建授权需要的元数据信息。后面仔细分析
  • intercept-url标签即<security:intercept-url pattern="/**" access=“ROLE_USER”/>

从生成FilterSecurityInterceptor到判断授权

1.创建FilterSecurityInterceptor
//package org.springframework.security.config.http;
//这个方法源自HttpConfigurationBuilder类  
void createFilterSecurityInterceptor(BeanReference authManager) {  
    //判断是否配置了use-expressions属性  
    //<http auto-config="true"use-expressions="true">
    //使用 Spring 表达式语言配置访问控制 
    //注意下方备注
    boolean useExpressions = FilterInvocationSecurityMetadataSourceParser.isUseExpressions(httpElt);  
    //根据intercept-url标签列表创建授权需要的元数据信息。httpElt里面有要去的资源路径。根据它们得出securityMds。上面有说明。后面仔细分析  
    BeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc);  
  
    RootBeanDefinition accessDecisionMgr;  
    //创建voter列表,方便使用投票器  
    ManagedList<BeanDefinition> voters =  new ManagedList<BeanDefinition>(2);  
    //根据不同情况使用不同投票器
    //如果是使用了表达式,使用WebExpressionVoter  
    //没使用表达式,就使用RoleVoter、AuthenticatedVoter  
    if (useExpressions) {  
        voters.add(new RootBeanDefinition(WebExpressionVoter.class));  
    } else {  
        voters.add(new RootBeanDefinition(RoleVoter.class));  
        voters.add(new RootBeanDefinition(AuthenticatedVoter.class));  
    }  
    //通过改变BeanDefinition里面的信息,来改变创建对象授权的决策管理类AffirmativeBased的bean
    accessDecisionMgr = new RootBeanDefinition(AffirmativeBased.class);  
    //添加依赖的voter列表  
    accessDecisionMgr.getPropertyValues().addPropertyValue("decisionVoters", voters);  
    accessDecisionMgr.setSource(pc.extractSource(httpElt));  
  
    // Set up the access manager reference for http  
    //access-decision-manager-ref属性,可以使我们手动注入AccessDecisionManager(认证管理器),下面是详细配置
    String accessManagerId = httpElt.getAttribute(""access-decision-manager-ref"");  
    //如果未定义access-decision-manager-ref属性,就使用默认的  
     //AffirmativeBased  
    if (!StringUtils.hasText(accessManagerId)) {  
        accessManagerId = pc.getReaderContext().generateBeanName(accessDecisionMgr);  
        pc.registerBeanComponent(new BeanComponentDefinition(accessDecisionMgr, accessManagerId));  
    }  
    //创建FilterSecurityInterceptor过滤器,通过BeanDefinitionBuilder来手动注入bean,下面有提示
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterSecurityInterceptor.class);  
    //往创建FilterSecurityInterceptor过滤器添加决策管理器  
    builder.addPropertyReference("accessDecisionManager", accessManagerId);  
    //往创建FilterSecurityInterceptor过滤器添加认证管理类  
    builder.addPropertyValue("authenticationManager", authManager);  
  
    if ("false".equals(httpElt.getAttribute(ATT_ONCE_PER_REQUEST))) {  
        builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);  
    }  
    //添加授权需要的安全元数据资源  
    builder.addPropertyValue("securityMetadataSource", securityMds);  
    //得到FilterSecurityInterceptor过滤器的BeanDefinition,通过BeanDefinition创建bean
    BeanDefinition fsiBean = builder.getBeanDefinition();  
    //向ioc容器注册FilterSecurityInterceptor的bean  
    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);  
}  
  • use-expressions属性讲解

  • BeanDefinition在Spring中是用来描述Bean对象的,其不是一个bean实例,仅仅是包含bean实例的所有信息,比如属性值、构造器参数以及其他信息。Bean对象创建是根据BeanDefinitionc中描述的信息来创建的,BeanDefinitionc存在的作用是为了可以方便的进行修改属性值和其他元信息,比如通过BeanFactoryPostProcessor进行修改一些信息,然后在创建Bean对象的时候就可以结合原始信息和修改后的信息创建对象了。

  • BeanDefinition spring一开始都是使用GenericBeanDefinition类保存Bean的相关信息,在需要时,在将其转换为其他的BeanDefinition类型

  • BeanDefinitionBuilder作用是手动向BeanDefinition注入信息然后通过BeanDefinition手动Spring容器中注入Bean

  • private final Element httpElt;Element 是接口表示HTML或XML中的元素

    文件。里面有可能具有与其关联的属性;

2.创建元数据资源的bean
了spring的路径匹配工具类static BeanDefinition createSecurityMetadataSource(List<Element> interceptUrls, Element elt, ParserContext pc) {  
    //创建Url处理类,有两个实现:AntUrlPathMatcher、RegexUrlPathMatcher是spring的路径匹配工具类
    UrlMatcher matcher = HttpSecurityBeanDefinitionParser.createUrlMatcher(elt);  
    boolean useExpressions = isUseExpressions(elt);  
    //解析intercept-url标签,构造所有需要拦截url的map信息  
     //map中的key:RequestKey的bean定义,value:SecurityConfig的bean定义  
    ManagedMap<BeanDefinition, BeanDefinition> requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap(  
            interceptUrls, useExpressions, pc);  
    BeanDefinitionBuilder fidsBuilder;  
  
    if (useExpressions) {  
        //定义表达式处理类的bean  
        Element expressionHandlerElt = DomUtils.getChildElementByTagName(elt, Elements.EXPRESSION_HANDLER);  
        String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref");  
  
        if (StringUtils.hasText(expressionHandlerRef)) {  
            logger.info("Using bean '" + expressionHandlerRef + "' as web SecurityExpressionHandler implementation");  
        } else {  
            BeanDefinition expressionHandler = BeanDefinitionBuilder.rootBeanDefinition(DefaultWebSecurityExpressionHandler.class).getBeanDefinition();  
            expressionHandlerRef = pc.getReaderContext().generateBeanName(expressionHandler);  
            pc.registerBeanComponent(new BeanComponentDefinition(expressionHandler, expressionHandlerRef));  
        }  
        //定义表达式类型的FilterInvocationSecurityMetadataSource  
        fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExpressionBasedFilterInvocationSecurityMetadataSource.class);  
        //通过构造函数注入依赖  
        fidsBuilder.addConstructorArgValue(matcher);  
        fidsBuilder.addConstructorArgValue(requestToAttributesMap);  
        fidsBuilder.addConstructorArgReference(expressionHandlerRef);  
    } else {  
        //定义非表达式类型的FilterInvocationSecurityMetadataSource  
        fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);  
        //通过构造函数注入依赖  
        fidsBuilder.addConstructorArgValue(matcher);  
        fidsBuilder.addConstructorArgValue(requestToAttributesMap);  
    }  
  
    fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);  
    fidsBuilder.getRawBeanDefinition().setSource(pc.extractSource(elt));  
  
    return fidsBuilder.getBeanDefinition();  
}  
3.FilterSecurityInterceptor执行
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)  
        throws IOException, ServletException {  
    //封装request, response, chain,方便参数传递、增加代码阅读性  
    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) {  
        if (fi.getRequest() != null) {  
            fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);  
        }  
        //执行父类beforeInvocation,类似于aop中的before  
        InterceptorStatusToken token = super.beforeInvocation(fi);  
  
        try {  
            //filter传递  
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());  
        } finally {  
            //执行父类的afterInvocation,类似于aop中的after  
            super.afterInvocation(token, null);  
        }  
    }  
}  
4.父类的beforeInvocation方法
protected InterceptorStatusToken beforeInvocation(Object object) {  
    //参数Object object是 FilterInvocation fi 封装request, response, chain,方便参数传递、增加代码阅读性  
    //根据SecurityMetadataSource安全元数据获取配置的权限属性  
    Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);  
    //省略……  
     //认证管理器生效,判断是否需要对认证实体重新认证,默认为否  
    Authentication authenticated = authenticateIfRequired();  
  
    // Attempt authorization  
    try {  
        //决策管理器开始决定是否授权,如果授权失败,直接抛出AccessDeniedException  
        this.accessDecisionManager.decide(authenticated, object, attributes);  
    }  
    catch (AccessDeniedException accessDeniedException) {  
        publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,  
                accessDeniedException));  
  
        throw accessDeniedException;  
    }  
}  
增加说明
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);  

这里获取的是权限列表信息,比如说有这个配置
<security:intercept-url pattern="/index.jsp*" access=“ROLE_USER,ROLE_ADMIN”/>
如果现在发起一个请求时index.jsp,那么根据这个请求返回的attributes集合就是分别包含ROLE_USER,ROLE_ADMIN属性的两个SecurityConfig对象

5.决策管理器是AffirmativeBased,接着看AffirmativeBased的决策过程
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)  
        throws AccessDeniedException {  
    int deny = 0;  
    //循环voters,实际上是RoleVoter、AuthenticatedVoter  
    for (AccessDecisionVoter voter : getDecisionVoters()) {  
        //把具体的决策任务交给voter处理  
          //voter只返回-1、0、1,只有为1才算授权成功  
        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;  
        }  
    }  
    //只要有一个voter拒绝了,则直接抛出访问拒绝异常  
    if (deny > 0) {  
        throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied",  
                "Access is denied"));  
    }  
  
    // To get this far, every AccessDecisionVoter abstained  
    checkAllowIfAllAbstainDecisions();  
}  
6.RoleVoter投票
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {  
    int result = ACCESS_ABSTAIN;  
    //从认证实体中获取所有的权限列表  
    Collection<GrantedAuthority> authorities = extractAuthorities(authentication);  
    //循环intercept-url配置的access权限列表  
    for (ConfigAttribute attribute : attributes) {  
        if (this.supports(attribute)) {  
            result = ACCESS_DENIED;  
  
            // Attempt to find a matching granted authority  
            //循环认证实体所拥有的权限列表  
            for (GrantedAuthority authority : authorities) {  
                if (attribute.getAttribute().equals(authority.getAuthority())) {  
                    //只要有相同的权限,直接返回成功1  
                    return ACCESS_GRANTED;  
                }  
            }  
        }  
    }  
  
    return result;  
}  

由于RoleVoter在list列表(在createFilterSecurityInterceptor创建voter列表,方便使用投票器 ManagedList voters = new ManagedList)中的位置处于AuthenticatedVoter前面,只要RoleVoter通过,就不会再执行AuthenticatedVoter了。

参考博客

Spring Security3源码分析(10)-FilterSecurityInterceptor分析

上一篇:Spring-依赖注入对象


下一篇:简单Ioc