Web 服务实现中的概念分离
作者 Tieu Luu 09/06/2006
翻译 Judy01/29/2007
概念分离是面向服务体系结构的一项核心原则。 不幸的是,在实现 SOA 服务时,这项原则总是被忽略。 经常可以看到一个很长的类具有多重概念,例如安全、事务管理和登录等全部与业务逻辑混杂在一起。 使用 Spring 框架和 Aspect Oriented Programming (AOP) 准则,可以将概念分离灌输到服务实现中去。
在这篇文章中, 我将介绍如何使用 Apache Axis 和 Spring 开发 Web 服务,以及使用 Acegi Security 保证服务的安全 -- 从始至终都可以保持概念可以很好地分离。
动机与设计
本文使用的示例服务称作 FundsTransferService
,使用此服务银行可以将资金从一个帐户转移到另一个帐户。 服务的 WSDL、所有的源代码、配置文件和生成文件已在本文的资源部分给出。 我们有意选择一个简单的服务,目的是把重点放在文章的其他方面。 在此服务的实现过程中,将遇到三个概念:
- Web 服务引流,将其功能作为服务公开
- 业务逻辑,以执行转移资金
- 确保安全性,只有授权者可以执行资金转移
一个实际的系统还极有可能处理其他的概念,如事务管理、登录等。
服务实现的设计目标是,需要将特定的代码应用于特定的概念,而且概念与概念相互独立。 对于 Web 服务引流,我们使用 Axis 将其功能公开为一个服务。 跨帐户的资金转移这一业务逻辑,则将其封装在一组传统纯 Java 对象 (Plain Old Java Object,POJO) 中。 而安全性将由 Acegi Security 框架提供。 我们将使用 Spring 框架及其 AOP 功能将所有内容捆扎在一起,使实现此 Web 服务的代码间的依赖性降低。
实现的设计如图 1 所示。黄色是我们将要实现的对象。 蓝色由 Axis 提供,粉色由 Acegi 提供,绿色由 Spring 提供。 FundsTransferService
WSDL 中设计的服务的接口。 为简化此图,我们将所有 Axis 类视为组件,称为 Axis Engine。 BasicHandler
也是一个 Axis 类,但是由于它对设计的重要性我们还需要特殊对待(稍后详细讨论)。 FundsTransferServiceSoapBindingImpl
是 Axis 生成的类,也是我们需要加以实现以便提供服务的功能。 它将通过 Spring 间接为业务逻辑 POJO AccountMgrImpl
对象服务。(稍后也将详加讨论。) AccountMgrImpl
和 AccountMgr
接口包装在一起,因为这是一个很好的做法,还因为这允许 Spring 发挥它的神奇作用(还有另外一种方法是在不使用接口的情况下使用 Spring)。
图 1. 实现资金转移服务的设计
现在回到 BasicHandler
。 需要它的原因是它关系到我们已选择的提供安全保障的方式。 在此示例中,我们将处理两个层次上的安全性:Web 服务的安全性,和应用程序代码的安全性,例如,POJO。 对于概念分离的思想,我们已经提出了一个方案。 Axis 允许使用自定义的事务处理程序,以便截取提供其他如安全性等功能的请求和响应消息。 因此,我们将创建一个自定义的事务处理程序,称之为AcegiBridgeAuthenticationHandler
,用于负责处理 Web 服务安全。 它扩展自 Axis 的 BasicHandler
类,因此可以将其插入 Axis 处理程序的框架。 Acegi 将用于提供应用程序级别的安全,例如,提供对 POJO 的访问控制。
要使二者协同无缝地工作,我们需要将 Web 服务安全上下文过渡到 Acegi 安全上下文,因此将自定义的 Axis 处理程序类命名为 AcegiBridgeAuthenticationHandler
。 它不仅会处理 Web 服务安全的流程,还负责将从该流程中获取的安全上下文过渡到 Acegi 环境中,以便 Acegi 来决定授权,是否允许访问 POJO。 这是通过从 Web 服务请求消息中提取安全性设置完成的,Acegi 将验证请求,然后创建一个授权标识,即 UsernamePasswordAuthenticationToken
,因为在本示例中我们已经选择使用“用户名/密码”式的授权方式。 然后,将授权标识添加到 Acegi 的 SecurityContext
中,这样一来请求中的授权信息和权限在 Acegi 控制业务逻辑的 POJO 时就可供使用了。
现在开始解释如何使用 Spring 将所有内容捆扎在一起。 其妙处就在于图 1 中名为 AOP proxy 的那个绿色对象,它是由 Spring 生成的。 这个对象实现了 AccountMgr
接口,行为上相当于业务逻辑 POJO AccountMgrImpl
的代理。 这允许 Spring 插入 Acegi 的 AccountMgrImpl
来执行访问控制,以检查何时在 POJO 上发生方法调用。 当 FundsTransferServiceSoapBindingImpl
向 POJO 发出 Web 服务请求时,它实际上是向一个 AOP 代理的实例发出的而不是直接针对 AccountMgrImpl
。 因为 FundsTransferServiceSoapBindingImpl
扩展了 ServletEndpointSupport
,所以它可以访问 Spring 应用程序上下文来获取一个对 AOP 代理(已经使用合适的接口、拦截器和目标类 AccountMgrImpl
进行了配置)的引用。
图 2 的序列图中展示了当客户端调用 FundsTransferService
时将发生的流程。 Axis Engine 将负责接收请求消息,然后激活 AcegiBridgeAuthenticationHandler
。 AcegiBridgeAuthenticationHandler
验证授权信息,然后创建一个 UsernamePasswordAuthenticationToken
。 接下来,它将该标识写入 SecurityContext
以便 Acegi 将来使用。 AcegiBridgeAuthenticationHandler
成功返回后,Axis Engine 就会调用 FundsTransferServiceSoapBindingImpl
上的 transferFunds()
方法。 FundsTransferServiceSoapBindingImpl
将它委托给 AOP proxy,该代理是 FundsTransferServiceSoapBindingImpl
早在从 Spring Web 应用程序上下文进行初始化时就已经获取的。 AOP proxy
调用 Acegi MethodSecurityInterceptor
以便执行安全性检查。 MethodSecurityInterceptor
从 SecurityContext
处获取授权标识并检查是否被授权。 然后,使用授权标识中的信息,来查看客户端是否被允许调用 AccountMgrImpl
上的 transferFunds()
方法。 如果许可访问,则 MethodSecurityInterceptor
允许调用该方法进行 AccountMgrImpl
。 AccountMgrImpl
最终处理请求并返回结果,结果最终被传送到客户端程序。