关注点分离(separation of concerns)是面向服务的架构(Service-Oriented Architectures,SOA)的核心原则。令人遗憾的是,该原则在实现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实现的所有代码之间的依赖性减到最小。
该实现的设计如图1所示。以黄色表示的对象就是我们需要实现的Web service。以蓝色表示的对象来自Axis;以粉红色表示的对象来自Acegi;以绿色表示的对象来自Spring。FundsTransferService是WSDL中所定义的服务接口。为了简化图表,我们将所有Axis类显示为名为Axis Engine的组件。BasicHandler也是Axis类,但由于它对于设计比较重要(稍后详述),所以单独显示出来。
FundsTransferServiceSoapBindingImpl是Axis的生成类,需要实现它来提供服务功能。将直接通过Spring委派业务逻辑POJO AccountMgrImpl(稍后也会对此进行详细解释)。AccountMgrImpl与AccountMgr接口捆绑在一起是不错的做法,因为这样就允许我们插入Spring以发挥其作用(尽管有其他方法可以不带接口使用Spring)。
图1.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把各方面联系起来。法宝就是图1中名为AOP proxy的小绿色对象,由Spring生成。该对象实现了AccountMgr接口,并担当业务逻辑POJO AccountMgrImpl的代理。这允许Spring插入Acegi的MethodSecurityInterceptor,当有人尝试在POJO上调用方法时,会执行访问控制检查。当FundsTransferServiceSoapBindingImpl将Web service请求委派给POJO时,实际上是委派给AOP代理的实例,而不是直接委派给AccountMgrImpl。由于FundsTransferServiceSoapBindingImpl扩展了ServletEndpointSupport,因此它可以访问Spring应用程序上下文,以获得配置了正确接口、侦听器和目标类的AOP代理的引用AccountMgrImpl。
图2中的序列图演示了客户端调用FundsTransferService时,将发生的处理流程。请求消息由Axis Engine接收,然后调用AcegiBridgeAuthenticationHandler。AcegiBridgeAuthenticationHandler将验证认证信息,然后创建UsernamePasswordAuthenticationToken。接着,将该令牌设置到Acegi的SecurityContext中,用于稍后使用。AcegiBridgeAuthenticationHandler成功返回后,Axis Engine将在FundsTransferServiceSoapBindingImpl上调用transferFunds()方法。
FundsTransferServiceSoapBindingImpl将其委派给AOP代理,在初始化过程中,可以较早从Spring web应用程序上下文获取。AOP proxy将调用Acegi MethodSecurityInterceptor,这样就可以进行它的安全检查。MethodSecurityInterceptor从SecurityContext获取认证令牌,然后检查是否已经对其进行了认证。接下来,将使用认证令牌中的信息来确定是否应该授权客户端的访问,在AccountMgrImpl上调用transferFunds()方法。如果允许客户端进行访问,那么MethodSecurityInterceptor将允许调用该方法,然后进入AccountMgrImpl。最后AccountMgrImpl处理该请求,并返回结果,最终传送回客户端程序。
图2.演示服务实现处理流程的序列图
业务逻辑实现和配置
我们的讨论将从解释业务逻辑类的实现和配置开始,因为他们是最简单的。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="com.mybank.bizlogic.AccountMgrImpl"/>
. . .
<bean id="accountMgr"
class="org.springframework.aop.framework.
ProxyFactoryBean">
<property name="proxyInterfaces">
<list>
<value>
com.mybank.bizlogic.AccountMgr
</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提供的便利类,可以用于JAX-RPC Web service实现,来获取Spring应用程序上下文的引用。通过扩展该类,FundsTransferServiceSoapBindingImpl类可以访问Spring上下文,来获取先前所描述的accountMgr bean的引用。由于FundsTransferServiceSoapBindingImpl类由Axis管理,我们无法使用Spring的依赖性注入工具来自动获取该bean的引用。因此,必须在onInit()方法中明确执行。不幸的是,这会将该类中的一些依赖性添加到特定于Spring的类中。好,这就是使用Spring和Acegi在得到受益的同时需要付出的小小代价。请注意在实际的方法transferFunds()中,代码仅委派给accountMgr bean。
在Axis配置文件(deploy.wsdd和server-config.wsdd)中,需要确保将服务的实现类设置为该类FundsTransferServiceSoapBindingImpl,而不是由Axis生成的其他框架类(FundsTransferServiceSoapBindingSkeleton)。若要使Spring在与Axis相同的web应用程序中正确地工作,我们需要将下面的条目添加到web.xml文件中。context-param条目指定了放置Spring配置文件的位置。listener条目的设置用于在启动时载入Spring配置和上下文。
<web-app>
<context-param>
<param-name>
contextConfigLocation
</param-name>
<param-value>
/WEB-INF/spring-config.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.
ContextLoaderListener
</listener-class>
</listener>
. . .
</web-app>
Acegi安全配置
现在我们来讨论如何在Spring配置文件中配置Acegi安全。如前所述,我们配置了业务逻辑bean,因此由securityInterceptor bean来侦听方法调用,以执行安全检查。下面我们来看看如何配置该bean。下面所示是securityInterceptor bean的部分Spring配置文件代码。securityInterceptor bean是由名为MethodSecurityInterceptor的Acegi类提供的。正如其名称所示,该类用于加强方法调用的安全,通过侦听调用并检查调用方是否经过认证和授权。
<beans>
. . .
<bean id="securityInterceptor"
class="org.acegisecurity.intercept.method.
aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager">
<bean class="org.acegisecurity.
providers.ProviderManager">
<property name="providers">
<list>
<bean class="org.acegisecurity.providers.anonymous.
AnonymousAuthenticationProvider">
<property name="key" value="changeThis"/>
</bean>
</list>
</property>
</bean>
</property>
<property name="accessDecisionManager">
<bean class="org.acegisecurity.vote.UnanimousBased">
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity. vote.RoleVoter"/>
</list>
</property>
</bean>
</property>
<property name="objectDefinitionSource">
<value>
com.mybank.bizlogic.AccountMgr.transferFunds=ROLE_MANAGER
</value>
</property>
</bean>
. . .
</beans>
我们需要配置带有authenticationManager属性的securityInterceptor bean,以指定使用哪种类型的认证。由于我们的设计依靠Axis 处理程序来执行认证,而这里不需要认证,因此仅使用AnonymousAuthenticationProvider进行配置。另外,我们在Axis处理程序中创建认证令牌的方式会使Acegi获知它已经进行了认证,因此不会尝试再次认证。稍后讨论Axis处理程序时,我们将对此进行更加详细的解释。
接下来,我们需要配置带有accessDecisionManager属性的bean,以指定如何决定是否授权某用户进行访问以调用方法。Acegi具有访问决策管理器(access decision manager)的三个具体实现:AffirmativeBased、ConsensusBased和UnanimousBased。Acegi根据投票人对是否授权某用户进行访问以执行特定操作的投票,来实施访问策略。计算投票数来决定是否应该授权访问。我们选择了UnanimousBased访问决策管理器,为了客户端能够执行操作,需要所有的投票都授权访问。用户应该阅读Acegi文档,以获取对该访问决策的更深入说明。下面,我们必须配置带有投票人列表的accessDecisionManager。这里我们仅使用一个名为RoleVoter投票人。这是来自Acegi的类,根据基于角色访问控制对是否授权访问进行投票。用户还应参考Acegi文档,以获取对RoleVoter工作方式的更深入说明。
需要配置的最后一个属性是objectDefinitionSource。这就是如何指定访问受保护的对象上不同方法所需要的认证。在这里,我们只想保护transferFunds()方法,并且只允许访问manager。通过列出所有符合条件的类名、方法名和进行访问所需的角色来实现:
com.mybank.bizlogic.AccountMgr.transferFunds=ROLE_MANAGER
定制的Axis处理程序
如先前所描述的,我们需要某个事物来连接Web service安全上下文和Acegi安全上下文。这里即用到定制的Axis处理程序AcegiBridgeAuthenticationHandler。我们保持了非常简单的实现,这样易于解释。可能引起注意的第一件事情是它实际上没有进行任何认证。我们只是从MessageContext中得到用户名和密码,然后照旧使用它们。当然,事实上真正的实现将尝试验证该信息。需要考虑的事情是真正的实现应该从SOAP消息中提取WS-Security头,然后进行处理以得到认证信息,而不是像我们这样仅仅从Axis MessageContext对象中提取他们。
得到认证信息后,接下来就是使其可用于Acegi。通过创建认证令牌,并在Acegi安全上下文中进行设置来实现。由于在进行用户名和密码认证,因此将创建UsernamePasswordAuthenticationToken的实例。进行创建前,需要指定为该主体授予了哪些权限。通过使用GrantedAuthority接口和称为GrantedAuthorityImpl的简单实现来实现。由于使用RoleVoter来进行访问决策,因此我们将进行授权的机构是角色。这里我们再次简化了实现,对其进行硬编码,授权主体manager角色,因为该角色是在POJO上调用transferFunds()方法所需要的。真正的实现可能将获取用户名和密码,然后在数据库或目录服务器中进行查找,以找到与该主体实际相关的角色。或者,在某些情况下,可在WS-Security头中以SAML声明形式获得该信息。
在任何情况下,一旦完成,都将创建UsernamePasswordAuthenticationToken实例,传递用户名、密码和授权的角色。通过使用这种形式的构造方法(采用GrantedAuthority数组),实际上在告知Acegi此令牌已经过了认证,因此不必再次进行认证。接下来,通过在SecurityContextHolder上调用静态方法来得到SecurityContext,并将认证令牌设置到SecurityContext中。现在认证和角色信息对于Acegi是下游可用的,用于执行其安全检查。因此,我们已有效地将Web service安全上下文连接到Acegi安全上下文。
有几件需要考虑的其他事情。首先,Acegi还提供了非常可靠的认证能力,因此不是让Axis处理程序来关注认证,可以让Acegi做这些事情。若要这样做,使用未采用GrantedAuthority数组的构造方法来创建未经认证的认证令牌。用户还需要确保配置了适当的认证提供程序,而不是使用AnonymousAuthenticationProvider。第二,Acegi不仅支持用户名/密码认证,还支持其他认证。例如,如果在进行基于PKI的认证,用户可以使用X509AuthenticationToken,而不是UsernamePasswordAuthenticationToken。
最后,需要配置Axis,在服务的请求处理路径上包含该处理程序。通过向Axis配置文件deploy.wsdd和server-config.wsdd添加以下条目来实现:
<deployment >http://xml.apache.org/axis/wsdd/"xmlns:java="http://xml.apache.org/axis/
wsdd/providers/java">
<service name="FundsTransferService" provider="java:RPC" style="document"
use="literal">
. . .
<requestFlow>
<handler type="java:com.mybank.security.AcegiBridgeAuthenticationHandler"/>
</requestFlow>
. . .
</service>
</deployment>
结束语
关注点分离是开发面向服务的架构的关键原则。但是,它不仅需要应用到架构级,还需要应用到实现级。在本文中,我们演示了如何使用Axis、Spring和Acegi来实现符合SOA原则的受保护Web service。如示例代码所示,使用该方法使处理每个服务关注点的代码中的交叉依赖性减至最小。我们所给出的示例是刻意保持简单的,但应将其作为基础,用于开发具有可靠安全机制(结合了Web service安全性和Acegi提供的应用程序级安全性)的Web service。如前所述,真正的系统很可能需要开发处理程序,用来处理WS-Security头并将它们连接到Acegi安全上下文。一种方法是采用Apache工具包WSS4J,然后扩展其Axis处理程序以填充Acegi安全上下文,如本文所述。可能需要进行一些其他工作来创建捕获Acegi安全异常的Axis出站处理程序,并创建返回到客户端的更有意义的SOAP错误。
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国
作者
相关推荐
-
事件驱动框架和SOA在空军的应用
空军正在利用SOA来改善数据共享,并实时跟踪战机,美国空军机动司令部的Michael Marek解释了企业可从中学习的经验。
-
揭秘New Relic APM技术细节
New Relic应性能管理(APM)套件主要用于Web软件开发。它允许用户在面向服务的架构(SOA)上跟踪关键事务性能,并且支持代码级别的可见性来评估特定代码段和SQL语句对性能的影响
-
仅凭SOA和云无法解决业务数据管理风险问题
SOA和云可以是某些恼人问题高效的解决方案;这一点我们已经知道了。但是也要记住它们并不是所有事情的直接答案,特别是当你的问题是业务数据管理风险,而不是技术问题时。
-
联合创新,携手共赢 华为与Commvault签署全球合作联盟协议
【中国,上海,2015年9月19日】在2015年华为云计算大会上,全球领先的信息与通信解决方案供应商华为与美国知名的数据管理软件及相关服务主要供应商Commvault签署全球合作联盟协议。基于合作协议,双方将会加大投入数据中心备份解决方案在云化环境下的“可服务化”技术研究 。Commvault公司全球业务发展副总裁Andreas May、华为IT数据中心解决方案总裁马力出席签约仪式。