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