美文网首页
Spring之Spring Security简单配置

Spring之Spring Security简单配置

作者: 我不吃甜食 | 来源:发表于2018-01-24 12:06 被阅读0次

    Spring Security是基于Filter实现的,能够对系统的访问权限进行细粒度的控制

    该文基于4.2.0.RELEASE展开

    如何配置

    项目中若使用Spring Security,第一步就需要在web.xml中添加如下配置:

    <filter>
        <!--filter-name是固定的,不能更改-->
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    当然还需要security用到的filter的配置,这些filter会由spring容器管理,下面是配置的一个例子:

    <security:http entry-point-ref="casEntryPoint" access-decision-manager-ref="myAccessDecisionManager">
            <security:custom-filter position="CAS_FILTER" ref="myCasAuthenticationFilter" />
            <security:custom-filter before="LOGOUT_FILTER" ref="requestSingleLogoutFilter" />
            <security:intercept-url pattern="/abc/initAllHiveTableFeature*" access="permitAll"/>
            <security:intercept-url pattern="/abc/**" access="hasRole('USER')"/>
            <security:intercept-url pattern="/def/**" 
            <security:access-denied-handler ref="accessDeniedHandler"/>
            <security:csrf disabled="true"/>
    </security:http>
    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="myCasAuthenticationProvider" />
    </security:authentication-manager>
    <bean id="accessDeniedHandler" class="com.young.MyAccessDeniedHandler">
            <property name="errorPage" value="/error"/>
            <property name="noAuthorityPage" value="/no-authority" />
    </bean>
    

    上述的配置概括起来包含如下内容:1. 自定义了两个Filter;2. 定义了一个entry point, 用于没有登录时引导到的login页面;3. 定义了需要过滤的URL及权限;4. 拒绝访问时的处理机制。一言以蔽之,就是:若没有登录,则导航到entry point页面(一般为login页面);登录但没有权限,accessDeniedHandler来处理;别的URL正常访问

    接下来简要分析一下这部分是如何解析的。如之前博客介绍, 一个name是XXX的标签一般是由XXXNamespaceHandler的parse()来解析,那么<security>标签是由SecurityNamespaceHandler来解析:
    SecurityNamespaceHandler.java

    private void loadParsers() {
            .......
            // Only load the web-namespace parsers if the web classes are available
            if (ClassUtils.isPresent(FILTER_CHAIN_PROXY_CLASSNAME, getClass()
                    .getClassLoader())) {
                parsers.put(Elements.DEBUG, new DebugBeanDefinitionParser());
                parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
                parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser());
                parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE,
                        new FilterInvocationSecurityMetadataSourceParser());
                parsers.put(Elements.FILTER_CHAIN, new FilterChainBeanDefinitionParser());
                filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();
            }
            .......
        }
    

    上述代码可以发现http子标签是由HttpSecurityBeanDefinitionParser解析

    public BeanDefinition parse(Element element, ParserContext pc) {
            ........
            //这里实际上创建了FilterChainProxy
            registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));
    
            // Obtain the filter chains and add the new chain to it
            BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(
                    BeanIds.FILTER_CHAINS);
            List<BeanReference> filterChains = (List<BeanReference>) listFactoryBean
                    .getPropertyValues().getPropertyValue("sourceList").getValue();
    
            //这里会默认创建一些Security要使用的Filter
            filterChains.add(createFilterChain(element, pc));
    
            pc.popAndRegisterContainingComponent();
            return null;
        }
    
    static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) {
            .......
            //创建了FilterChainProxy
            BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder
                    .rootBeanDefinition(FilterChainProxy.class);
            fcpBldr.getRawBeanDefinition().setSource(source);
            fcpBldr.addConstructorArgReference(BeanIds.FILTER_CHAINS);
            fcpBldr.addPropertyValue("filterChainValidator", new RootBeanDefinition(
                    DefaultFilterChainValidator.class));
            BeanDefinition fcpBean = fcpBldr.getBeanDefinition();
            pc.registerBeanComponent(new BeanComponentDefinition(fcpBean,
                    BeanIds.FILTER_CHAIN_PROXY));
            //这里将FilterChainProxy与名字springSecurityFilterChain绑定,即Spring通过这个名字去容器中找FilterChainProxy;否则就找不到。
            //所以web.xml中的配置不能改。
            pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY,
                    BeanIds.SPRING_SECURITY_FILTER_CHAIN);
        }
    

    简单分析

    DelegatingFilterProxy由名字可以判定这是一个代理类,真正的Filter是FilterChainProxy。为了方便使用,Spring提供了DelegatingFilterProxy,可以容易地将Security Filters加入到web容器的Filter Chain中。

    由上述代码可以发现FilterChainProxy是通过security:http的配置自动创建。绝大多数应用不必显示地配置FilterChainProxy的,除非想更精确地控制Filter的使用。FilterChainProxy中又定义了List<SecurityFilterChain>,用于精确控制Filter的使用,如下例子。

    <bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
          <constructor-arg>
                <util:list>
                    <security:filter-chain pattern="/do/not/filter*" filters="none"/>
                    <security:filter-chain pattern="/**" filters="filter1,filter2,filter3"/>
               </util:list>
         </constructor-arg>
    </bean>
    

    一些疑问

    • 既然FilterChainProxy也是一个代理,为何不在web.xml中直接配置这个呢?
    • 为何DelegatingFilterProxy没有直接代理FilterChain,而是又通过另一层FilterChainProxy去代理FilterChain?
    • 不使用代理,手动方式将需要的Filter配置在web.xml可以吗?

    首先要明白Tomcat中各个组件的启动顺序:Listener -> SpringContext -> Filter -> Servlet。在Tomcat读取web.xml的<filter>之前,spring context已经启动,我们配置的各种bean都已初始化完成。

    对于第一个问题,如果FilterChainProxy直接配置在<filter>标签里,那么它的初始化就由ServletContext完成(即调用init()方法),这样它的很多属性将无法初始化,包括里面的Filter List,因为FilterChainProxy的init()方法依然是abstract的。而DelegatingFilterProxy的init()已完全实现。换个角度理解,DelegatingFilterProxy是filter,而FilterChainProxy是个spring bean。
    再者,如果想用别的安全框架,比如shiro,那么这个时候DelegatingFilterProxy代理的就是shiro的那一套东东了,所以DelegatingFilterProxy是支持插拔式的。

    第二个问题,Spring Security是由自定义的filter组成,这些filter由spring初始化,那么由spring自身的bean来管理和代理这些filter会更方便。FilterChainProxy对外提供了统一的入口。当然,如果必须要DelegatingFilterProxy去代理这些filter,笔者认为也可以。

    第三个问题个人觉得是可以的,但是这样的话,filter的生命周期就会交给ServletContext了,对于开发者而言,就需要考虑这些filter的初始化参数等很多因素;而且Spring Security的filters是有一定顺序的,这就更提高了使用门槛。

    将这些filter完全交由SpringContext来管理,极大地降低使用难度。

    相关文章

      网友评论

          本文标题:Spring之Spring Security简单配置

          本文链接:https://www.haomeiwen.com/subject/xyxeoxtx.html