使用 .NET 调用有自定义 Handler 验证的 XFire Web 服务

原文:http://lzy.javaeye.com/blog/292219

 

定义和使用 Web 服务都不是一件费力的事情,尤其在当前框架和工具的支持下更加容易。一般来说,常见情况下我们都会使用同一个开发语言、框架和工具来开发 Web 服务和调用服务的客户端。但是由于技术或非技术上的原因,又需要我们在不同的平台上发布、订阅彼此的 Web 服务。就在昨天,应同事的需要,就在 .NET 平台上使用 C# 编写一段调用部署在 XFire 上的 Web 服务的客户端代码。之前认为 Web 服务/SOAP 协议本来就是在开放的精神下设计的,因此跨平台调用也不会有多麻烦,但是也正是因为之前对 XFire 上的 Web 服务细节不太了解,浪费了我宝贵的几个小时,在此作以记录,另一方面也希望能为其它人提供些许帮助,免得大家无谓浪费时间。

 

          在这里要说明的是如何在 .NET (C#)代码中定义正确的 SOAP 头来满足 XFire Web 服务中使用自定义 Handler 验证操作 。这包括两种方式:

 

  1. 基于 .NET WCF 服务模型 的客户端服务代理。
  2. 基于 .NET Web 服务模型 的客户端服务代理。

话说两头,先表服务端。XFire Web 服务可以提供四种验证的方式,它们分别是:

 

  1. HTTP 验证
  2. JSR181 规范
  3. 自定义 Handler 代码
  4. WS-Security 规范

          在这里说的就是自定义 Handler 代码验证方式,当前 Web 服务中的 Handler 代码是根据 XFire 提供的示例代码改造的,很容易理解。

 

Java代码 复制代码
  1. public class AuthenticationHandler extends AbstractHandler {   
  2.   
  3.     // Namespace define, come from services.xml configuration.   
  4.     private final static Namespace TOKEN_NS = Namespace.getNamespace("demoService");   
  5.   
  6.     public void invoke(MessageContext context) throws Exception {   
  7.   
  8.         Element header = context.getInMessage().getHeader();   
  9.            
  10.         if (header == null) {   
  11.             throw new XFireFault("Request must include company authentication token.", XFireFault.SENDER);   
  12.         }   
  13.            
  14.         Element token = header.getChild("AuthenticationToken", TOKEN_NS);   
  15.   
  16.         if (token == null) {   
  17.             throw new XFireFault("Request must include authentication token.", XFireFault.SENDER);   
  18.         }   
  19.   
  20.         Element license = token.getChild("license", TOKEN_NS);   
  21.            
  22.         if (license == null) {   
  23.             throw new XFireFault("license is null.", XFireFault.SENDER);   
  24.         }   
  25.            
  26.         String licenseValue = license.getValue();   
  27.            
  28.         if (licenseValue == null) {   
  29.             throw new XFireFault("license value is null.", XFireFault.SENDER);   
  30.         }   
  31.   
  32.         try {   
  33.             // Other check code.   
  34.         } catch(Exception e) {   
  35.             throw new XFireFault("Authentication Failed.", XFireFault.SENDER);   
  36.         }   
  37.     }   
  38. }  
public class AuthenticationHandler extends AbstractHandler {      // Namespace define, come from services.xml configuration.     private final static Namespace TOKEN_NS = Namespace.getNamespace("demoService");      public void invoke(MessageContext context) throws Exception {          Element header = context.getInMessage().getHeader();                  if (header == null) {             throw new XFireFault("Request must include company authentication token.", XFireFault.SENDER);         }                  Element token = header.getChild("AuthenticationToken", TOKEN_NS);          if (token == null) {             throw new XFireFault("Request must include authentication token.", XFireFault.SENDER);         }          Element license = token.getChild("license", TOKEN_NS);                  if (license == null) {             throw new XFireFault("license is null.", XFireFault.SENDER);         }                  String licenseValue = license.getValue();                  if (licenseValue == null) {             throw new XFireFault("license value is null.", XFireFault.SENDER);         }          try {             // Other check code.         } catch(Exception e) {             throw new XFireFault("Authentication Failed.", XFireFault.SENDER);         }     } } 
 

          上面代码中有多步检查动作,其中关于内容检查的代码忽略了,留下的主要是对 SOAP 报文头中携带元素的存在性检查的代码。客户端调用要想通过该检查,首先要有 SOAP 头并携带必要的元素,其次才是元素内容正确。由于元素内容正确性检查涉及具体算法逻辑,这里就忽略不关注了。这里主要想说明是怎么保证 .NET 客户端发送 SOAP 报文头中携带必要的元素,根据上面代码可知,这里必须包括三部分:

 

  1. SOAP 报文必须有头信息
  2. 头中必须携带 AuthenticationToken 元素
  3. AuthenticationToken 元素中必须包括 license 子元素

另外要说明,AuthenticationHandler 类是在 services.xml 文件中配置生效的:

 

Xml代码 复制代码
  1. <beans xmlns="http://xfire.codehaus.org/config/1.0">  
  2.   
  3.     <service>  
  4.         <name>demoService</name>  
  5.         <namespace>http://webservice.lzy.javaeye.com</namespace>  
  6.         <serviceClass>com.lzy.javaeye.webservice.IWebService</serviceClass>  
  7. <implementationClass>com.lzy.javaeye.webservice.impl.WebService</implementationClass>  
  8.         <style>wrapped</style>  
  9.         <use>literal</use>     
  10.         <scope>application</scope>    
  11.         <inHandlers>  
  12.             <handler handlerClass="com.lzy.javaeye.webservice.AuthenticationHandler"/>  
  13.         </inHandlers>          
  14.     </service>  
  15. </beans>  
<beans xmlns="http://xfire.codehaus.org/config/1.0">  	<service> 		<name>demoService</name> 		<namespace>http://webservice.lzy.javaeye.com</namespace> 		<serviceClass>com.lzy.javaeye.webservice.IWebService</serviceClass> <implementationClass>com.lzy.javaeye.webservice.impl.WebService</implementationClass> 		<style>wrapped</style> 		<use>literal</use>           <scope>application</scope>  		<inHandlers> 			<handler handlerClass="com.lzy.javaeye.webservice.AuthenticationHandler"/> 		</inHandlers>        	</service> </beans>
 

          其中配置的 inHandlers 元素就是为 demoService 服务的添加了自定义 Handler 验证,即 AuthenticationHandler 类。

 

          服务端就是上面这些,下面就要分两方面(WCF 服务模型和 Web 服务模型)来说明 .NET调用的事了。

 

          先说在 .NET 中通过 WCF 服务框架调用 XFire Web 服务。 在 .NET 3.5 中微软提出了 WCF 框架,在这种基于服务的通信框架中包括了很多概念,契约就是重要的一个。WCF 将所有服务都定义为契约,它与平台无关,是描述服务功能的标准方式。WCF 契约分为四种:

  1. 服务契约(Service Contract)
  2. 数据契约(Data Contract)
  3. 错误契约(Fault Contract)
  4. 消息契约(Message Contract)

          结合这个场景来说吧,调用一次服务,对于 WCF 来说属于使用规范的消息契约和定义的服务契约进行一次通信,因此 SOAP 报文本身属于消息契约,同时该消息契约内要包括一个头对象 AuthenticationToken(使用 System.ServiceModel.MessageHeaderAttribute 属性修饰),另外这个 AuthenticationToken 对象还应该是一个数据契约(使用 DataContractAttribute 属性修饰)。定义 SOAP 头的消息契约类代码如下,这个实际是扩展自 Visual Studio 的 Services 引用向导生成的 partial 类。

 

C#代码 复制代码
  1. public partial class serviceMethodRequest   
  2. {   
  3.     [System.ServiceModel.MessageHeaderAttribute(Name = "AuthenticationToken", Namespace = "demoService")]   
  4.     public DemoSoapHeader SoapHeader = new DemoSoapHeader();   
  5. }   
public partial class serviceMethodRequest {     [System.ServiceModel.MessageHeaderAttribute(Name = "AuthenticationToken", Namespace = "demoService")]     public DemoSoapHeader SoapHeader = new DemoSoapHeader(); } 

 

          需要注意的是其中 Namespace 参数与 WSDL 中定义的服务命名空间要对应,之前这个事就把我搞得头痛,还要感谢 Fiddler。再看看那个数据契约类代码,和上面是对应的。

 

C#代码 复制代码
  1. [System.Runtime.Serialization.DataContractAttribute(Namespace = "demoService")]   
  2. [Serializable]   
  3. public class DemoSoapHeader   
  4. {   
  5.     [System.Runtime.Serialization.DataMember()]   
  6.     public string license = null;   
  7. }  
[System.Runtime.Serialization.DataContractAttribute(Namespace = "demoService")] [Serializable] public class DemoSoapHeader {     [System.Runtime.Serialization.DataMember()]     public string license = null; } 
 

          剩下就是实例化客户端本地服务代理、实例化消息契约类、实例化并设置 SOAP 头数据契约类,调用等等正常走流程吧。

 

          接下来说在 .NET 中通过 “传统”(是 2.0 时的经典方法吧,呵呵) Web 服务框架调用 XFire Web 服务。 其实这种方式在网上文档很多,这里也不想再重复。主要就是首先定义一个表式 SOAP 头的对象:

 

C#代码 复制代码
  1. [System.Serializable]   
  2. [System.Xml.Serialization.XmlType(Namespace = "demoService")]   
  3. [System.Xml.Serialization.XmlRoot(Namespace = "demoService", IsNullable = false)]   
  4. public class AuthenticationToken : SoapHeader   
  5. {   
  6.     public string license = null;   
  7. }  
[System.Serializable] [System.Xml.Serialization.XmlType(Namespace = "demoService")] [System.Xml.Serialization.XmlRoot(Namespace = "demoService", IsNullable = false)] public class AuthenticationToken : SoapHeader {     public string license = null; }

 

          这里需要注意的就是类的名称 AuthenticationToken 就是 SOAP 报文中节点的名称,在这里和 WCF 中可配置的方式有些不同。然后扩展 Visual Studio 的 Web 引用向导生成的 partial 类,添加一个用来表示 SOAP 头中数据节点的成员对象:

 

C#代码 复制代码
  1. public partial class DemoService   
  2. {   
  3.     public AuthenticationToken SoapHeader = new AuthenticationToken();   
  4. }  
public partial class DemoService {     public AuthenticationToken SoapHeader = new AuthenticationToken(); }

 

          最重要的是,还需要在 WebMethod 方法上添加属性,该方法位于 Visual Studio 的 Web 引用向导生成的服务代理类,指定 SOAP 头中携带对象元素所关联的成员变量:

 

C#代码 复制代码
  1. [SoapHeader("SoapHeader")]   
  2. public OutEntBaseBean findEntBase([System.Xml.Serialization.XmlElementAttribute(IsNullable=true)] ParamClass in0)   
  3. {   
  4.     // Invok code.   
  5. }  
[SoapHeader("SoapHeader")] public OutEntBaseBean findEntBase([System.Xml.Serialization.XmlElementAttribute(IsNullable=true)] ParamClass in0) {     // Invok code. }

 

          这里比较有趣的是,使用 WCF 服务模型来编写时,可以看到不需要修改 Visual Studio 引用向导生成的本地服务代理类中的代码,而使用 Web 服务模型时却需要在 WebMethod 上添加 SoapHeader 标签,这意味着使用后者时,只要在 Visual Studio 中刷新引用的服务时,都要重写添加所有更改过的代码,这个就比较郁闷了。

 

          最后调用服务的方法相对使用 WCF 服务模型简单一些,实例化客户端本地服务代理、实例化并设置 SOAP 头对象、调用等等吧。

 

最后把 SOAP 报文也贴上来吧,首先是 WCF 服务模型生成的报文:

 

Xml代码 复制代码
  1. <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">  
  2.   <s:Header>  
  3.     <h:AuthenticationToken xmlns:h="demoService" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">  
  4.       <h:license>This is license.</h:license>  
  5.     </h:AuthenticationToken>  
  6.   </s:Header>  
  7.   <s:Body>  
  8.   
  9.   </s:Body>  
  10. </s:Envelope>  
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">   <s:Header>     <h:AuthenticationToken xmlns:h="demoService" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">       <h:license>This is license.</h:license>     </h:AuthenticationToken>   </s:Header>   <s:Body>    </s:Body> </s:Envelope>
 

接下来这个是  Web 服务模型生成的报文:

 

Xml代码 复制代码
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">  
  3.   <soap:Header>  
  4.     <AuthenticationToken xmlns="demoService">  
  5.       <license>This is license.</license>  
  6.     </AuthenticationToken>  
  7.   </soap:Header>  
  8.   <soap:Body>  
  9.   
  10.   </soap:Body>  
  11. </soap:Envelope>  
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">   <soap:Header>     <AuthenticationToken xmlns="demoService">       <license>This is license.</license>     </AuthenticationToken>   </soap:Header>   <soap:Body>    </soap:Body> </soap:Envelope>
 

上面的 Body 内容省略了。

 

          就是以上这些了,实际这里涉及的内容是很简单的,但当真正做这件简单的事情时却也浪费了一些时间,在此做以记录备忘吧。欢迎大家一起讨论、提高。

 

注明:以上所有内容和代码,如与我有关项目或文档有相似之处纯属巧合。

 

// 2009.01.05 20:55 添加 ////

 

巧了,看来“头”的问题很重要啊,呵呵。

java axis调用带有soap头(soapheader)的.net webservice

 

// 2009.02.06 16:48 添加 ////

 

附件 MdmSvcCallee.zip 为备份目的所添加,请勿下载使用。

 

// 2009.03.07 13:30 添加 ////


作者:lzy.je
出处:http://lzy.javaeye.com
本文版权归作者所有,只允许以摘要和完整全文两种形式转载,不允许对文字进行裁剪。未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

 

 

posted on 2010-01-06 15:49  冷月孤峰  阅读(480)  评论(2)    收藏  举报