《Spring Security3》第六章第七部分翻译(认证事件处理与小结)
认证事件处理
有一个重要的功能只能通过基于bean的配置就是自定义处理认证事件。认证事件使用了Spring的时间发布机制,它基于o.s.context.ApplicationEvent事件模型。Spring事件模型使用并不广泛,却能够很有用处——特别在认证系统中——如当你想绑定特定行为到认证领域的行动上去的时候。
事件是典型的订阅-发布模式,通知订阅者是Spring运行环境自己处理的。比较重要的一点是,在默认情况下Spring的事件模型是同步的,所以有任何订阅监听的运行时会直接影响产生事件请求的性能。
在ApplicationContext初始化的时候,Spring将会检查所有配置的bean是否存在o.s.context.ApplicationListener接口。这些bean的引用将会被o.s.context.event.ApplicationEventMulticaster持有,它会在o.s.context.ApplicationEventPublisher发布事件时,管理运行时事件的发布。这个设施已经存在很长时间了(从Spring1.1),所以你若想更深入了解Spring的这个领域,有很多文档可查。
下图阐述了事件发布流程是如何组织在一起的:
因为在Spring Security内部没有广泛使用认证事件(实际上,唯一明显用的地方就是我们本章前面讨论的session并发跟踪),自定义认证事件的监听器是一种实现审计、管理报警甚至复杂用户行为追踪的便利方式。
让我们了解一下配置简单安全事件监听的过程。
配置认证事件的监听器
使用简短的security命名空间配置,你不能配置认证事件监听器——它必须使用基于Spring bean的方式因为ApplicationEventPublisher的实现类默认不会启用,必须织入到AuthenticationManager中。
声明需要的bean依赖
我们首先声明ApplicationEventPublisher的实现类,如下:
<bean id="defaultAuthEventPublisher" class="org.springframework.security.authentication .DefaultAuthenticationEventPublisher"/>
接下来,我们将会把它织入到使用的AuthenticationManager中:
<bean id="customAuthenticationManager" class="org.springframework.security.authentication.ProviderManager"> <property name="authenticationEventPublisher" ref="defaultAuthEventPublisher"/> <property name="providers"> <list> <ref local="daoAuthenticationProvider"/> <ref local="rememberMeAuthenticationProvider"/> </list> </property> </bean>
这就是全部需要的配置。如果此时你重启应用,你将会什么也看不到。这是因为我们还没有创建bean来监听发布的时间。现在,我们就做这件事。
构建自定义的应用事件监听器
ApplicationListener的实现类很简单,并且使用Spring 3加入的强大Java泛型功能支持类型安全。我们的ApplicationListener只是简单记录收到的事件到标准输出中,但是在后面的练习中我们将会体验一个更有趣的例子。
自定义的ApplicationListener如下:
package com.packtpub.springsecurity.security; // imports omitted @Component public class CustomAuthenticationEventListener implements ApplicationListener<AbstractAuthenticationEvent> { @Override public void onApplicationEvent(AbstractAuthenticationEvent event) { System.out.println("Received event of type: "+event.getClass().getName()+": "+event.toString()); } }
你会发现我们这里使用了@Component注解,但是我们也可以在XML配置文件中简单声明一个Spring bean。
记住,ApplicationListener的实现类必须注明对什么类型的事件感兴趣,在Spring 3中通过在ApplicationListener接口引用中声明泛型来标注。Spring的ApplicationEventMulticaster使用一些巧妙的方法来检查类的接口实现声明并确保正确的事件到达正确的类中。
【巧妙的注解。你如果对复杂的注解处理和运行时检查注解感到好奇,毫无疑问那你应该查看使用Spring的o.s.context.event.GenericApplicationListenerAdapter分发ApplicationEvent事件的巧妙代码。看一下并学习一些Java反射的新技巧。】
重启应用,然后进行一些常用的行为如登录、退出以及登录失败。你能看到,当这些行为执行时,适当的时间被触发并打印在控制台上。
尽管我们声明了自己的ApplicationListener来接受所有的认证事件,但是在大多数场景下这并不实用,根据系统使用情况的,在一个小时内可能会有数千个时间触发。通过修改类implements关键词上的泛型标示,我们能够使得实现类至监听一种类型的事件。
内置的ApplicationListeners
Spring Security提供了两个ApplicationListener的实现类,它们绑定了在Spring Security中使用的Apache Commons Logging日志。两个ApplicationListener实现类,一个是负责认证事件,一个负责授权时间。你可以像以下代码那样配置它们:
<bean id="authenticationListener" class="org.springframework.security .authentication.event.LoggerListener"/> <bean id="authorizationListener" class="org.springframework.security .access.event.LoggerListener"/>
这两个监听器将会输出Commons Logging日志以对应的类命名。一个示例记录AbstractAuthenticationFailureEvent大致如下:
WARN - Authentication event AuthenticationFailureBadCredentialsEvent: adb; details: org. springframework.security.web.authentication.WebAuthenticationDetails@2 55f8: RemoteIpAddress: 127.0.0.1; SessionId: B20510F25464B109CE3AE94D9 FBF981E; exception: Bad credentials
如果你要实现类似的ApplicationListener来记录有用的事件,这些类可以作为模板。
大量的应用事件
Spring Security提供了很多的事件,其试图在用户认证请求的所有点上给出有用的信息。你的应用可以监听可用的各种事件,这个范围可以很广泛(所有认证失败)也可以很窄小(一个用户通过提供完整的凭证认证成功)。完整的事件列表在附录:参考资料中。一些其它的关于异常处理和事件监听的注意事项如下:
l 授权事件和框架抛出异常的匹配关系可以通过DefaultAuthenticationEventPublisher的exceptionMappings属性配置;
l 记住,正如我们在本章前面看到的,跟踪HttpSession的生命周期是通过web.xml配置的变化,而不直接是Spring。
你可以看到Spring Security的异常和事件处理很强大,允许在你的安全系统中进行很多场景的跟踪和对活动的响应。
构建一个自定义实现的SpEL表达式处理器
我们将会阐述一个扩展基本SpEL表达式处理器的简单例子,提供一个表达式如果当前日期的分钟数为偶数将会允许访问。尽管这是一个很牵强的例子,但是它描述了实现自定义SpEL表达式方法的所有步骤。
让我们创建一个类com.packtpub.springsecurity.security.CustomWebSecurityExpressionRoot以建立自定义扩展的WebSecurityExpressionRoot。
public class CustomWebSecurityExpressionRoot extends WebSecurityExpressionRoot { public CustomWebSecurityExpressionRoot (Authentication a, FilterInvocation fi) { super(a, fi); } public boolean isEvenMinute() { return (Calendar.getInstance().get(Calendar.MINUTE) % 2) == 0; } }
接下里,我们需要一个实现WebSecurityExpressionHandler的类。我们扩展了DefaultWebSecurityExpressionHandler并重写一个方法在com.packtpub.springsecurity.security.CustomWebSecurityExpressionHandler类中建立自己的CustomWebSecurityExpressionRoot。
public class CustomWebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler { public EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi) { StandardEvaluationContext ctx = (StandardEvaluationContext) super.createEvaluationContext(authentication, fi); SecurityExpressionRoot root = new CustomWebSecurityExpressionRoot(authentication, fi); ctx.setRootObject(root); return ctx; } }
最后,当建立Voter时需要重新配置bean引用,如下:
<bean class="com.packtpub.springsecurity.security.CustomWebSecurityExpressionHandler" id="customExpressionHandler"/> <bean class="org.springframework.security.web.access.expression.WebExpressionVoter" id="expressionVoter"> <property name="expressionHandler" ref="customExpressionHandler"/> </bean>
现在,我们可以使用这个表达式来根据时间的分钟数是偶数还是奇数进行限制访问。
<security:intercept-url pattern="/*" access="evenMinute"/>
很显然,这是一个简单的例子,但是阐述了实现自定义SpEL属性的基本步骤,你可以使用这种方式来控制对应用特定部分的访问。
【配置自定义SpEL Voter的技术可能在使用security命名空间的时候也会用到,只需使用access-decision-manager-ref属性定义一个自定义的AccessDecisionManager,就像我们在第二章见过的那样。】
小结
在本章中,我们介绍了Spring Security标准配置的功能并实现了一些高级的自定义功能。我们涉及到以下的内容:
l 实现自定义的servlet过滤器来处理基于IP和角色的过滤以及基于HTTP头的SSO请求;
l 添加一个自定义的AuthenticationProvider及支持类,从而实现HTTP请求头的SSO;
l 了解session固化防护和session并发处理的配置及好处,包括一些间接的功能以允许用户进行session报告;
l 配置自定义的访问控制拒绝处理并了解何时及为何AccessDeniedException会被抛出,还有怎样适当地响应它;
l 替换Spring Security的自动化配置为手动声明所有需要的参与类,这借助于标准的Sping bean XML配置技术;
l 了解一些基于Spring bean配置的高级功能,包括session管理和事件发布;
l 实现自定义和内置的ApplicationListener来响应Spring Security框架发布的特定事件;
l 实现标准SpEL表达式处理器的自定义扩展以允许个性化的URL访问表达式。
多有趣的一章!我们已经很习惯Spring Security,并进行了一些高级的扩展和自定义。
在第七章中,我们将会进行高级配置的旅程,通过使用访问控制列表使得实现复杂认证变得可能。