Spring之Spring Security简单配置

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来管理,极大地降低使用难度。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,200评论 19 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 47,172评论 6 342
  • 要加“m”说明是MB,否则就是KB了. -Xms:初始值 -Xmx:最大值 -Xmn:最小值 java -Xms8...
    dadong0505阅读 10,385评论 0 53
  • 在我搭建基于Spring Cloud的微服务体系应用的时候所需要或者是常用的属性配置文件,还有这些属性的用途,此配...
    StrongManAlone阅读 9,637评论 0 18
  • 关中的面数不胜数。无非面条扁圆粗细宽窄不同,汤头咸淡酸辣浓香有别;臊子面虽出自西岐,裤带面来自咸阳,但出了关中甚至...
    边走边唱A阅读 5,080评论 6 9

友情链接更多精彩内容