这章里讨论一些Spring Security中用到的基础设施。 如果这个功能跟安全没有直接关系,但是还是包含在Spring Security中了,我们就在这章里讨论它。
Spring Security支持异常信息的国际化,最终用户会很喜欢这点。 如果你的程序是为英语用户设计的,你不需要做任何事,因为默认情况下所有的Spring Security消息都是英文的。 如果你需要支持其他语言,你所需要做的就是看看这节内容。
所有的异常信息都是可以本地化的,包括验证失败和访问被拒绝(验证失败)信息。 开发者和系统发布者关注的异常和日志信息(包括不正确的属性,接口联系分隔,使用错误的构造器,开始验证册书,日志调试等级)等等,不能进行本地化,而是使用英文直接硬编码到了Spring Security代码里。
打开spring-security-core-xx.jar
,你可以找到org.springframework.security
包,里面包含了一个messages.properties
文件。 你的ApplicationContext
应用应该引用这个文件,因为Spring Security实现了Spring的MessageSourceAware
接口,希望这些消息会在application context启动的时候注入到程序中。 通常,你需要做的就是,在你的application context 里注册一个bean,来引用这些信息,比如像下面这样:
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="org/springframework/security/messages"/> </bean>
这个messages.properties
使用了标准的资源束命名方式,为Spring Security提供了默认的语言支持。 默认的语言是英文。 如果你没有注册消息源,Spring Security依然可以正常工作,但是使用的是硬编码的英文信息版本。
如果你想自定义messages.properties
文件,或支持其他语言,你应该复制这个文件,改成对应的文件名,并把它注册到上面的bean定义中。 文件里的关键信息并不多,本地化应该不是一个很大的任务。 如果你对这个文件进行了本地化,请考虑把你的工作与社区分享,记录一个JIRA任务,把你的本地化版本messages.properties
以附件形式发送上来。
围绕本地化进行讨论,就要提到Spring里的ThreadLocal
,org.springframework.context.i18n.LocaleContextHolder
。 你应该通过LocaleContextHolder
,来表现每个用户想要的Locale
. Spring Security尝试从信息源中定位信息,使用从ThreadLocal
里中获得的Locale
。 请参考Spring文档获得使用LocaleContextHolder
的更多信息,可以使用一些帮助类进行自动设置(比如AcceptHeaderLocaleResolver
, CookieLocaleResolver
, FixedLocaleResolver
, SessionLocaleResolver
等等)。
Spring Security用到了很多过滤器,参考指南的后续部分会一一提到。 如果你使用了命名空间配置,你就不用经常去明确指定过滤器bean。 有几种可能情况,你希望对安全过滤器链进行完全控制,或许因为你使用的功能没法使用命名空间进行支持,或者你使用了自己自定义版本的类。
这种情况下,你可以选择向你的web应用成立里添加哪些过滤器,这里你可以使用Spring的DelegatingFilterProxy
或 FilterChainProxy
。我们会在下面介绍它们两个。
在使用DelegatingFilterProxy
的时候,你会看到web.xml里这样的内容:
<filter> <filter-name>myFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>myFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
注意这个过滤器其实是一个DelegatingFilterProxy
,这个过滤器里没有实现过滤器的任何逻辑。 DelegatingFilterProxy
做的事情是代理Filter
的方法,从application context里获得bean。 这让bean可以获得spring web application context的生命周期支持,使配置较为轻便。 bean必须实现javax.servlet.Filter
接口,它必须和filter-name
里定义的名称是一样的。
在生命周期的问题上,要考虑在IoC容器里而不是在servlet容器里管理Filter
。 具体来说,到底是哪个容器应该调用Filter
的“启动”与“关闭”方法。 需要指出的是Filter
的初始化和销毁很容易受servlet容器的影响,如果一个Filter
依赖于较早初始化Filter
的配置,那么可能会引发一些问题。 Spring IoC容器,从另一方面讲,拥有更强大的生命周期/IoC接口(比如InitializingBean
, DisposableBean
, BeanNameAware
, ApplicationContextAware
和很多其他的),拥有更容易理解的接口协议,可预见的方法调用顺序,支持自动绑定,更可以选择不用实现Spring的接口(比如通过Spring XML中的destroy-method
属性)。 介于这些原因,只要有可能的话,我们推荐使用Spring生命周期服务,代替servlet容器的生命周期。 可以参考DelegatingFilterProxy
的Javadoc获得更多信息。
最好别用DelegatingFilterProxy
,我们强烈推荐你使用FilterChainProxy
代替它。 虽然DelegatingFilterProxy
是一个非常有用的类,问题是在需要使用几个过滤器的时候,需要在web.xml
中定义<filter>
和<filter-mapping>
入口的代码数量太多了。 为了解决这个问题,Spring Security提供了一个FilterChainProxy
类。 它绑定了一个DelegatingFilterProxy
(好像上面的例子那样),但是使用的类是org.springframework.security.util.FilterChainProxy
。 过滤器链要声明在application context里,使用下面的代码:
<bean id="filterChainProxy" class="org.springframework.security.util.FilterChainProxy"> <sec:filter-chain-map path-type="ant"> <sec:filter-chain pattern="/webServices/**" filters="httpSessionContextIntegrationFilterWithASCFalse,basicProcessingFilter,exceptionTranslationFilter,filterSecurityInterceptor"/> <sec:filter-chain pattern="/**" filters="httpSessionContextIntegrationFilterWithASCTrue,authenticationProcessingFilter,exceptionTranslationFilter,filterSecurityInterceptor"/> </sec:filter-chain-map> </bean>
你可能注意到FilterSecurityInterceptor
声明的不同方式。 它同时支持正则表达式和ant路径,并且只使用第一个出现的匹配URI。 在运行阶段FilterChainProxy
会定位当前web请求匹配的第一个URI模式,由filters
属性指定的过滤器bean列表将开始处理请求。 过滤器会按照定义的顺序依次执行,所以你可以对处理特定URL的过滤器链进行完全的控制。
你可能注意到了,我们在过滤器链里声明了两个HttpSessionContextIntegrationFilter
(ASC
是allowSessionCreation
的简写,是HttpSessionContextIntegrationFilter
的一个属性)。 因为web服务从来不会在请求里带上jsessionid
,为每个用户代理都创建一个HttpSession
完全是一种浪费。 如果你需要构建一个高等级最高可扩展性的系统,我们推荐你使用上面的配置方法。 对于小一点儿的项目,使用一个HttpSessionContextIntegrationFilter
(让它的allowSessionCreation
默认为true
)就足够了。
在有关声明周期的问题上,如果这些方法被FilterChainProxy
自己调用,FilterChainProxy
会始终根据下一层的Filter
代理init(FilterConfig)
和destroy()
方法。 这时,FilterChainProxy
会保证初始化和销毁操作只会在Filter
上调用一次,而不管它们被FilterInvocationDefinitionSource
声明了多少次。 你可以完全控制是否调用这些方法,通过代理DelegatingFilterProxy
的targetFilterLifecycle
初始化参数,。 像上面讨论的那样,默认的servlet容器生命周期调用不会被DelegatingFilterProxy
代理。
在使用命名空间配置的时候,你可以通过同样的方式使用filters = "none"
属性,来建立FilterChainProxy
。 这将从安全过滤器链中完全忽略请求的模式。 注意,任何匹配这个路径的请求,不会要求认证,不会使用验证服务,可以自由的访问。
定义在web.xml
里的过滤器的顺序是非常重要的。 不论你实际使用的是哪个过滤器,<filter-mapping>
的顺序应该像下面这样:
-
ChannelProcessingFilter
,因为它可能需要重定向到其他协议。 -
ConcurrentSessionFilter
,因为它不使用SecurityContextHolder
功能,但是需要更新SessionRegistry
来从主体中放映正在进行的请求。 -
HttpSessionContextIntegrationFilter
,这样SecurityContext
可以在web请求的开始阶段通过SecurityContextHolder
建立,然后SecurityContext
的任何修改都会在web请求结束的时候(为下一个web请求做准备)复制到HttpSession
中。 -
验证执行机制 -
AuthenticationProcessingFilter
,CasProcessingFilter
,BasicProcessingFilter, HttpRequestIntegrationFilter, JbossIntegrationFilter
等等 - 这样SecurityContextHolder
可以被修改,并包含一个合法的Authentication
请求标志。 -
SecurityContextHolderAwareRequestFilter
,如果,你使用它,把一个Spring Security提醒HttpServletRequestWrapper
安装到你的servlet容器里。 -
RememberMeProcessingFilter
,这样如果之前的验证执行机制没有更新SecurityContextHolder
,这个请求提供了一个可以使用的remember-me服务的cookie,一个对应的已保存的Authentication
对象会被创建出来。 -
AnonymousProcessingFilter
,这样如果之前的验证执行机制没有更新SecurityContextHolder
,会创建一个匿名Authentication
对象。 -
ExceptionTranslationFilter
,用来捕捉 Spring Security异常,这样,可能返回一个HTTP错误响应,或者执行一个对应的AuthenticationEntryPoint
。 -
FilterSecurityInterceptor
,保护web URI。
上面所有的过滤器,都使用了 DelegatingFilterProxy
或 FilterChainProxy
。推荐使用单独的 DelegatingFilterProxy
为每个程序代理一个单独的 FilterChainProxy
,通过这个 FilterChainProxy
定义Spring Security的所有过滤器。
如果你使用了SiteMesh,一定要确保Spring Security过滤器在SiteMesh的过滤器之前被调用。 这才可以保证为SiteMesh渲染器使用的 SecurityContextHolder
先被组装起来。
Spring Security捆绑了很多JSP标签库,提供了各种不同的服务。
所有的taglib类都包含在核心spring-security-xx.jar
文件里, security.tld
文件放在JAR的META-INF
目录下。 这意味着,对于JSP 1.2+的web容器,你可以直接把JAR放到WAR的WEB-INF/lib
目录下,然后就可以用了。 如果你使用的是JSP 1.1 容器,你需要在你的web.xml
文件里声明JSP taglib,还要把security.tld
放到WEB-INF/lib
目录下。 把下面的片段添加到web.xml
里:
<taglib> <taglib-uri>http://www.springframework.org/security/tags</taglib-uri> <taglib-location>/WEB-INF/security.tld</taglib-location> </taglib>
本文转自:http://www.family168.com/tutorial/springsecurity/html/supporting-infrastructure.html