认证事件处理
有一个重要的功能只能通过基于 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 ,并进行了一些高级的扩展和自定义。
在第七章中,我们将会进行高级配置的旅程,通过使用访问控制列表使得实现复杂认证变得可能。