关注点分离(separation of concerns)是面向服务的架构(ServiceOriented ArchitecturesSOA)的核心原则令人遗憾的是该原则在实现SOA服务时常常起不到作用我们通常会看到带有多个关注点(如安全事务管理)的巨大的实现类使用业务逻辑记录所有混合在一起的关注点使用Spring Framework和Aspect Oriented Programming (AOP)原则我们可以将关注点分离以用于服务实现
本文中我们将演示如何使用Apache Axis和Spring来开发Web service并使用Acegi Security对其进行保护——同时保持关注点很好地分离
动机和设计
本文中我们将使用的示例是名为FundsTransferService的服务银行使用该服务将资金从一个账户转移到另一个账户可以在本文的参考资料部分找到该服务的WSDL及所有源代码配置文件和构建文件我们有意让该服务保持非常简单以便集中讨论本文更有意义的方面在本服务的实现中我们将涉及三个关注点:
Web service管道用来公开作为服务的功能 用于转移资金的业务逻辑 安全性用于保证只有经授权的用户才能执行资金转移 而真正的系统很可能是必须处理其他的关注点如事务管理日志等
我们想设计这样一种实现处理每个关注点的代码与其他的代码完全分离对于Web service管道我们将使用Axis来公开作为服务的功能用于将资金从一个账户转移到另一个账户的业务逻辑将封装在一组POJO(Plain Old Java Object)中将通过Acegi Security框架来提供安全性我们将使用Spring Framework及其AOP工具把各方面联系起来使构成该Web service实现的所有代码之间的依赖性减到最小
该实现的设计如图所示以黄色表示的对象就是我们需要实现的Web service以蓝色表示的对象来自Axis;以粉红色表示的对象来自Acegi;以绿色表示的对象来自SpringFundsTransferService是WSDL中所定义的服务接口为了简化图表我们将所有Axis类显示为名为Axis Engine的组件BasicHandler也是Axis类但由于它对于设计比较重要(稍后详述)所以单独显示出来FundsTransferServiceSoapBindingImpl是Axis的生成类需要实现它来提供服务功能将直接通过Spring委派业务逻辑POJO AccountMgrImpl(稍后也会对此进行详细解释)AccountMgrImpl与AccountMgr接口捆绑在一起是不错的做法因为这样就允许我们插入Spring以发挥其作用(尽管有其他方法可以不带接口使用Spring)
)thisstylewidth=;>
图FundsTransferService实现的设计
现在回到BasicHandler需要返回的原因涉及到如何选择以提供安全在本例中将在两个不同级别上处理安全性:Web service的安全性及应用程序代码的安全性如POJO按照关注点分离的理念我们提出允许进行分离的设计Axis允许插入定制的处理程序侦听请求和响应消息以提供其他功能(如安全性)因此我们将创建名为AcegiBridgeAuthenticationHandler的定制处理程序负责处理Web service安全将扩展Axis类BasicHandler以便可以将其插入Axis处理程序框架Acegi将用于提供应用程序级的安全如提供对POJO的访问控制
为了使这些方面无缝工作需要将Web service安全上下文连接到Acegi安全上下文——因此定制的Axis处理程序类的名称为AcegiBridgeAuthenticationHandler它不仅将处理Web service安全性还负责将从该过程获取的安全上下文连接到Acegi环境这样Acegi就可以决定是否授权访问POJO方法是从Web service请求消息中提取安全声明进行验证然后创建认证令牌因为我们已经为本例选择了用户名/密码认证所以为UsernamePasswordAuthenticationToken然后将该认证令牌设置到Acegi SecurityContext中这样稍后在控制访问业务逻辑POJO时Acegi可以使用请求方的证书和权限
下面将解释如何使用Spring把各方面联系起来法宝就是图中名为AOP proxy的小绿色对象由Spring生成该对象实现了AccountMgr接口并担当业务逻辑POJO AccountMgrImpl的代理这允许Spring插入Acegi的MethodSecurityInterceptor当有人尝试在POJO上调用方法时会执行访问控制检查当FundsTransferServiceSoapBindingImpl将Web service请求委派给POJO时实际上是委派给AOP代理的实例而不是直接委派给AccountMgrImpl由于FundsTransferServiceSoapBindingImpl扩展了ServletEndpointSupport因此它可以访问Spring应用程序上下文以获得配置了正确接口侦听器和目标类的AOP代理的引用AccountMgrImpl
图中的序列图演示了客户端调用FundsTransferService时将发生的处理流程请求消息由Axis Engine接收然后调用AcegiBridgeAuthenticationHandlerAcegiBridgeAuthenticationHandler将验证认证信息然后创建UsernamePasswordAuthenticationToken接着将该令牌设置到Acegi的SecurityContext中用于稍后使用AcegiBridgeAuthenticationHandler成功返回后Axis Engine将在FundsTransferServiceSoapBindingImpl上调用transferFunds()方法FundsTransferServiceSoapBindingImpl将其委派给AOP代理在初始化过程中可以较早从Spring web应用程序上下文获取AOP proxy将调用Acegi MethodSecurityInterceptor这样就可以进行它的安全检查MethodSecurityInterceptor从SecurityContext获取认证令牌然后检查是否已经对其进行了认证接下来将使用认证令牌中的信息来确定是否应该授权客户端的访问在AccountMgrImpl上调用transferFunds()方法如果允许客户端进行访问那么MethodSecurityInterceptor将允许调用该方法然后进入AccountMgrImpl最后AccountMgrImpl处理该请求并返回结果最终传送回客户端程序
image onmousewheel=javascript:return big(this) height= alt=Web Service实现的关注点分离图 src=http://imgeducitycn/img_///gif width= onload=javascript:if(thiswidth>)thisstylewidth=;>
图演示服务实现处理流程的序列图
业务逻辑实现和配置
我们的讨论将从解释业务逻辑类的实现和配置开始因为他们是最简单的AccountMgr接口和AccountMgrImpl类的源代码参见参考资料从源代码中可以发现事实上实现并没有做任何事情所以可以保持其简单因为本文并不是关于如何编写转移资金代码的文章
下面是Spring配置文件的部分代码(可在参考资料部分得到整个配置文件)这些代码表明了如何配置业务逻辑的Spring beans以便使用Spring的AOP工具第一个bean条目是为AccountMgrImpl类创建bean第二个bean条目是如何施展前面讨论的全部AOP代理魔法使用从ProxyFactoryBean获取的id accountMgr来创建bean当FundsTransferServiceSoapBindingImpl类向Spring请求具有该id的bean时ProxyFactoryBean将返回AOP代理对象的实例对它进行配置来实现AccountMgr接口这样客户端程序会认为他们只是在使用业务逻辑对象使用名为interceptorNames的第二个属性创建名为securityInterceptor的bean(稍后将进行解释)可以侦听方法调用以执行安全检查这允许我们在业务逻辑代码中插入不带任何依赖性的Acegi安全机制最后将目标设置到accountMgrTarget bean这样方法调用将最终传送到实际的业务逻辑类AccountMgrImpl
<beans>
<bean id=accountMgrTarget
class=logicAccountMgrImpl/>
<bean id=accountMgr
class=orgspringframeworkaopframework
ProxyFactoryBean>
<property name=proxyInterfaces>
<list>
<value>
logicAccountMgr
</value>
</list>
</property>
<property name=interceptorNames>
<list>
<value>
securityInterceptor
</value>
</list>
</property>
<property name=target>
<ref bean=accountMgrTarget/>
</property>
</bean>
</beans>
Web service实现和配置
FundsTransferServiceSoapBindingImpl类是Web service实现其源代码请参见参考资料部分该类的框架由Axis生成我们仅仅填写方法来提供实现请注意该类扩展了ServletEndpointSupport这是Spring提供的便利类可以用于JAXRPC Web service实现来获取Spring应用程序上下文的引用通过扩展该类FundsTransferServiceSoapBindingImpl类可以访问Spring上下文来获取先前所描述的accountMgr bean的引用由于FundsTransferServiceSoapBindingImpl类由Axis管理我们无法使用Spring的依赖性注入工具来自动获取该bean的引用因此必须在onInit()方法中明确执行不幸的是这会将该类中的
一些依赖性添加到特定于Spring的类中好这就是使用Spring和Acegi在得到受益的同时需要付出的小小代价请注意在实际的方法transferFunds()中代码仅委派给accountMgr bean
在Axis配置文件(deploywsdd和serverconfigwsdd)中需要确保将服务的实现类设置为该类FundsTransferServiceSoapBindingImpl而不是由Axis生成的其他框架类(FundsTransferServiceSoapBindingSkeleton)若要使Spring在与Axis相同的web应用程序中正确地工作我们需要将下面的条目添加到webxml文件中contextparam条目指定了放置Spring配置文件的位置listener条目的设置用于在启动时载入Spring配置和上下文
<webapp>
<contextparam>
<paramname>
contextConfigLocation
</paramname>
<paramvalue>
/WEBINF/springconfigxml
</paramvalue>
</contextparam>
<listener>
<listenerclass>
orgsprntext
ContextLoaderListener
</listenerclass>
</listener>
</webapp>
Acegi安全配置
现在我们来讨论如何在Spring配置文件中配置Acegi安全如前所述我们配置了业务逻辑bean因此由securityInterceptor bean来侦听方法调用以执行安全检查下面我们来看看如何配置该bean下面所示是securityInterceptor bean的部分Spring配置文件代码securityInterceptor bean是由名为MethodSecurityInterceptor的Acegi类提供的正如其名称所示该类用于加强方法调用的安全通过侦听调用并检查调用方是否经过认证和授权
<beans>
<bean id=securityInterceptor
class=orgacegisethod
aopallianceMethodSecurityInterceptor>
<property name=authenticationManager>
<bean class=orgacegisecurity
providersProviderManager>
<property name=providers>
<list>
<bean class=orgacegisecurityprovidersanonymous
AnonymousAuthenticationProvider>
<property name=key value=changeThis/>
</bean>
</list>
</property>
</bean>
</property>
<property name=accessDecisionManager>
<bean class=orgacegisecurityvoteUnanimousBased>
<property name=decisionVoters>
<list>
<bean class=orgacegisecurity voteRoleVoter/>
</list>
</property>
</bean>
</property>
<property name=objectDefinitionSource>
<value>
logicAccountMgrtransferFunds=ROLE_MANAGER
</value>
</property>
</bean>
</beans>
我们需要配置带有authenticationManager属性的securityInterceptor bean以指定使用哪种类型的认证由于我们的设计依靠Axis 处理程序来执行认证而这里不需要认证因此仅使用AnonymousAuthenticationProvider进行配置另外我们在Axis处理程序中创建认证令牌的方式会使Acegi获知它已经进行了认证因此不会尝试再次认证稍后讨论Axis处理程序时我们将对此进行更加详细的解释
接下来我们需要配置带有accessDecisionManager属性的bean以指定如何决定是否授权某用户进行访问以调用方法Acegi具有访问决策管理器(access decision manager)的三个具体实现:AffirmativeBasedConsensusBased和UnanimousBasedAcegi根据投票人对是否授权某用户进行访问以执行特定操作的投票来实施访问策略计算投票数来决定是否应该授权访问我们选择了UnanimousBased访问决策管理器为了客户端能够执行操作需要所有的投票都授权访问用户应该阅读Acegi文档以获取对该访问决策的更深入说明下面我们必须配置带有投票人列表的accessDecisionManager这里我们仅使用一个名为RoleVoter投票人这是来自Acegi的类根据基于角色访问控制对是否授权访问进行投票用户还应参考Acegi文档以获取对RoleVoter工作方式的更深入说明
需要配置的最后一个属性是objectDefinitionSource这就是如何指定访问受保护的对象上不同方法所需要的认证在这里我们只想保护transferFunds()方法并且只允许访问manager通过列出所有符合条件的类名方法名和进行访问所需的角色来实现:
logicAccountMgrtransferFunds=ROLE_MANAGER
定制的Axis处理程序
如先前所描述的我们需要某个事物来连接Web service安全上下文和Acegi安全上下文这里即用到定制的Axis处理程序AcegiBridgeAuthenticationHandler我们保持了非常简单的实现这样易于解释可能引起注意的第一件事情是它实际上没有进行任何认证我们只是从MessageContext中得到用户名和密码然后照旧使用它们当然事实上真正的实现将尝试验证该信息需要考虑的事情是真正的实现应该从SOAP消息中提取WSSecurity头然后进行处理以得到认证信息而不是像我们这样仅仅从Axis MessageContext对象中提取他们
得到认证信息后接下来就是使其可用于Acegi通过创建认证令牌并在Acegi安全上下文中进行设置来实现由于在进行用户名和密码认证因此将创建UsernamePasswordAuthenticationToken的实例进行创建前需要指定为该主体授予了哪些权限通过使用GrantedAuthority接口和称为GrantedAuthorityImpl的简单实现来实现由于使用RoleVoter来进行访问决策因此我们将进行授权的机构是角色这里我们再次简化了实现对其进行硬编码授权主体manager角色因为该角色是在POJO上调用transferFunds()方法所需要的真正的实现可能将获取用户名和密码然后在数据库或目录服务器中进行查找以找到与该主体实际相关的角色或者在某些情况下可在WSSecurity头中以SAML声明形式获得该信息
在任何情况下一旦完成都将创建UsernamePasswordAuthenticationToken实例传递用户名密码和授权的角色通过使用这种形式的构造方法(采用GrantedAuthority数组)实际上在告知Acegi此令牌已经过了认证因此不必再次进行认证接下来通过在SecurityContextHolder上
调用静态方法来得到SecurityContext并将认证令牌设置到SecurityContext中现在认证和角色信息对于Acegi是下游可用的用于执行其安全检查因此我们已有效地将Web service安全上下文连接到Acegi安全上下文
有几件需要考虑的其他事情首先Acegi还提供了非常可靠的认证能力因此不是让Axis处理程序来关注认证可以让Acegi做这些事情若要这样做使用未采用GrantedAuthority数组的构造方法来创建未经认证的认证令牌用户还需要确保配置了适当的认证提供程序而不是使用AnonymousAuthenticationProvider第二Acegi不仅支持用户名/密码认证还支持其他认证例如如果在进行基于PKI的认证用户可以使用XAuthenticationToken而不是UsernamePasswordAuthenticationToken
最后需要配置Axis在服务的请求处理路径上包含该处理程序通过向Axis配置文件deploywsdd和serverconfigwsdd添加以下条目来实现
<deployment xmlns=xmlns:java=
wsdd/providers/java>
<service name=FundsTransferService provider=java:RPC
use=literal>
<requestFlow>
<handler type=java:commybanksecurityAcegiBridgeAuthenticationHandler/>
</requestFlow>
</service>
</deployment>
结束语
关注点分离是开发面向服务的架构的关键原则但是它不仅需要应用到架构级还需要应用到实现级在本文中我们演示了如何使用AxisSpring和Acegi来实现符合SOA原则的受保护Web service如示例代码所示使用该方法使处理每个服务关注点的代码中的交叉依赖性减至最小我们所给出的示例是刻意保持简单的但应将其作为基础用于开发具有可靠安全机制(结合了Web service安全性和Acegi提供的应用程序级安全性)的Web service如前所述真正的系统很可能需要开发处理程序用来处理WSSecurity头并将它们连接到Acegi安全上下文一种方法是采用Apache工具包WSSJ然后扩展其Axis处理程序以填充Acegi安全上下文如本文所述可能需要进行一些其他工作来创建捕获Acegi安全异常的Axis出站处理程序并创建返回到客户端的更有意义的SOAP错误