WCF 笔记 (3) - 懶人验证法与 MessageContract

实作最简单的 WCF 懶人验证方式,透过 SoapHeader 或 MessageContract 传送用户名、密码,以调用 WCF 服务。本帖最后,还附一个利用 MessageContract 处理表单提交的示例。

-------------------------------------------------
本帖的示例下载点:
http://files.cnblogs.com/WizardWu/101115.zip
(执行本示例,需要 Visual Studio 2008,不需要数据库)
---------------------------------------------------


我们都知道,WCF 有一套很完整的安全机制,例如可透过 SSL 保障传输层的安全、透过 Federation 加密和数位签章来保护信息本身、使用 Certificate 以跨域或跨平台传送凭证,甚至 WCF 4 还支持新的 WIF 框架以实现 SSO (Single Sign On) 单点登录 [3]


但若我们在企业的 Intranet 或局域网中,没有安全顾虑时 (信任自己公司或部门没有那么无聊的员工),只想用最简单的明文 username、password 验证 (安全后果自负),难道也非得创建:证书、SSL、…及一堆安全性的配置,才能调用 ClientCredentials.UserName.UserName、ClientCredentials.UserName.Password 这些 API 吗?


我以前曾在论坛问过此问题 (听说问过相同问题的人很多):

WCF 用户名、密码验证的设置问题:
http://topic.csdn.net/u/20101024/18/1e491713-46a1-412d-859b-453e49dad672.html?seed=181740069&r=69308273
http://space.cnblogs.com/q/19235/


结论:最简单的做法,是不使用 ClientCredentials,直接把 username、password 添加到 SOAP Header 中,来实现身份验证。有兴趣的人可参考:

WCF进阶:为每个操作附加身份信息
http://www.cnblogs.com/jillzhang/archive/2010/04/11/1709397.html


在传统的 Web Service 也有同样的做法,可参考以下这篇文章。若 Web Service 有安全性顾虑的话,可使用 WSE (Web Services Enhancements) 来加强安全性。

简单的 WebService 安全:
http://www.cnblogs.com/kuyijie/archive/2011/01/08/1930795.html


SOAP Header - 懶人验证法

前述 jillzhang 他用 SOAP Header 传递用户名、密码的做法:

Server-side :

// IService.cs

using  System.ServiceModel;

[ServiceContract]
public   interface  IService
{
    [OperationContract]
    
string  getData( int  value);
}

 

ExpandedBlockStart.gif Service.cs
using  System.ServiceModel;

public   class  Service : IService
{
    
public   string  getData( int  value)
    {
        
string  username  =   "" ;
        
string  pwd  =   "" ;

        
int  index  =  OperationContext.Current.IncomingMessageHeaders.FindHeader( " username " " http://tempuri.org " );

        
if  (index  >=   0 )
        {
            username 
=  OperationContext.Current.IncomingMessageHeaders.GetHeader < string > (index).ToString();
        }

        index 
=  OperationContext.Current.IncomingMessageHeaders.FindHeader( " pwd " " http://tempuri.org " );

        
if  (index  >=   0 )
        {
            pwd 
=  OperationContext.Current.IncomingMessageHeaders.GetHeader < string > (index).ToString();
        }

        
return   string .Format( " 您输入了:  "   +  value  +   " , <br> 帐号:  "   +  username  +   " , 密码:  "   +  pwd); 
    }
}



Client-side :

 

ExpandedBlockStart.gif Default.aspx.cs
using  System.ServiceModel;
using  System.ServiceModel.Channels;

public   partial   class  _Default : System.Web.UI.Page 
{
    
protected   void  Page_Load( object  sender, EventArgs e)
    {
        ServiceReference1.ServiceClient svc 
=   new  ServiceReference1.ServiceClient();

        
using  (OperationContextScope scope  =   new  OperationContextScope(svc.InnerChannel))
        {
            MessageHeader header 
=  MessageHeader.CreateHeader( " username " " http://tempuri.org " " WizardWu " );
            OperationContext.Current.OutgoingMessageHeaders.Add(header);

            header 
=  MessageHeader.CreateHeader( " pwd " " http://tempuri.org " " 123456 " );
            OperationContext.Current.OutgoingMessageHeaders.Add(header);

            
string  res  =  svc.getData( 10 );

            Response.Write(res);
        }
    }
}

 


图 1 执行画面


MessageContract 实际应用 1 - 懶人验证法


同样的身份验证功能,我们也可透过 MessageContract 类 [5] 来处理,将其包装得更漂亮,亦即可指定 SOAP envelope 的结构,然后就可用 message 作为参数或返回类型,并且在不修改默认 SOAP envelope 本身的情况下,来控制 SOAP body 内容序列化的信息。

MessageContract (消息契约) 对格式化 SOAP 消息,提供了有效的控制。虽然在许多情况下,DataContract (数据契约) 已提供了用户需要的大部分控制,但若需要更灵活地控制,可再加上 MessageContract 来辅助处理,如:允许用户将指定的类型映射到 SOAP 消息,并添加定制的 SOAP Header [7]


以下为 MCTS 70-503 WCF 认证,微软官方教程书籍 [6] 所述:

Data contracts enable you to define the structure of the data that will be sent in the body of your SOAP messages, either in the inbound (request) messages or in the outbound (response) messages. WCF Message contracts take it one step higher, when you use a Message contract as both the operation parameter type and the return type, you gain control over the entire SOAP message and not just over the structure of the data in the body. The following are the primary reasons you might want to use Message contracts :

* To control how the SOAP message body is structured and, ultimately, how it is serialized.
* To supply and access custom headers.


用 MessageContract 并透过其可「包装」的特性,我们可包装欲传递的用户名、密码,以及包装从服务器返回的信息,如下代码的 ContactInfoRequestMessage、ContactInfoResponseMessage 自定义类 (将这些类、接口都写在同一个 .cs 文件里,是不好的设计模式,这里只是为了演示方便):


Server-side :


ExpandedBlockStart.gif IService.cs
using  System.Runtime.Serialization;
using  System.ServiceModel;

[ServiceContract]
public   interface  IService
{
    [OperationContract()]
    [FaultContract(
typeof ( string ))]
    ContactInfoResponseMessage GetProviderContactInfo(ContactInfoRequestMessage reqMsg);
}

[DataContract]
public   class  ContactInfo
{
    [DataMember()]
    
public   string  PhoneNumber;

    [DataMember()]
    
public   string  EmailAddress;
}

[MessageContract(IsWrapped 
=   false )]
public   class  ContactInfoRequestMessage
{
    [MessageHeader()]
    
public   string  id;

    [MessageHeader()]
    
public   string  pwd;
}

[MessageContract(IsWrapped 
=   false )]
public   class  ContactInfoResponseMessage
{
    [MessageBodyMember()]
    
public  ContactInfo ProviderContractInfo;
}

 

ExpandedBlockStart.gif Service.cs
using  System.ServiceModel;

public   class  Service : IService
{
    
private   const   string  username  =   " WizardWu " ;
    
private   const   string  pwd  =   " 123456 " ;

    
public  ContactInfoResponseMessage GetProviderContactInfo(ContactInfoRequestMessage reqMsg)
    {
        
if  ((reqMsg.id  !=  username)  ||  (reqMsg.pwd  !=  pwd))
        {
            
const   string  msg  =   " 用户名、密码输入错误~~ " ;
            
throw   new  FaultException < string > (msg);
        }

        
// 返回信息。
        
// 透过 MessageContract 包装 ContactInfoResponseMessage 自定义类及其成员,
        
// 并包装真正存储数据的 DataContract 自定义类 ContactInfo

        ContactInfoResponseMessage respMsg 
=   new  ContactInfoResponseMessage();
        respMsg.ProviderContractInfo 
=   new  ContactInfo();
        respMsg.ProviderContractInfo.EmailAddress 
=   " test@mail.com " ;
        respMsg.ProviderContractInfo.PhoneNumber 
=   " 0512-67891234 " ;

        
return  respMsg;
    }
}


Client-side :


ExpandedBlockStart.gif Default.aspx.cs
using  System.ServiceModel;

public   partial   class  _Default : System.Web.UI.Page 
{
    
protected   void  Page_Load( object  sender, EventArgs e)
    {
        ServiceReference1.ServiceClient svc 
=   new  ServiceReference1.ServiceClient();

        
try
        {
            
// 透过 MessageContract 包装 ContactInfoRequestMessage 自定义类及其成员,让使用者输入用户名、密码
            ServiceReference1.ContactInfo ci  =  svc.GetProviderContactInfo( " WizardWu " " 123456 " );

            Response.Write(
" 电子邮件: "   +  ci.EmailAddress  +   " <br> 电话号码: "   +  ci.PhoneNumber);
        }
        
catch  (FaultException < string >  ex)
        {
            Response.Write(ex.Detail);  
// 印出「用户名、密码输入错误~~」
        }
        
catch  (Exception ex)
        {
            Response.Write(ex.ToString());
        }
    }
}

 


图 2 执行画面,输入了正确的用户名、密码,便可从 Server-side 取得想要的信息 (邮件、电话号码)



图 3 执行画面,输入了错误的用户名、密码


NOTE: Message contracts must be used all or none
There isn't any partial usage of Message contracts. After you introduce a Message contract into an operation's signature, you must use a Message contract as the "only" parameter type "and" as the return type of the operation. This is in contrast with the more typical scenario in which you have a parameter list or return value composed of Data contracts or serializable types.


Supplying Custom Headers
You might sometimes need to send along private elements in your SOAP messages, and defining Message contracts supports this. Two common reasons for doing this are that :
* You have your own security mechanism in place, so you need to pass along your own authentication token in a private SOAP header.
* Consumers of your service might have to include some sort of license key (例如上方示例的用户名和密码) to access the service at run time. In such cases, a SOAP header is a reasonable place for such a field.


MessageContract 实际应用 2 - 订单提交

接下来我们看 MessageContract 在实务上的另一种应用 - 网上书店的订单处理 [7]。本例中,定义了包含订书信息的 MessageContract,其会在 Server-side 和 Client-side 之间传递。如下 IService.cs 的代码,这个 BookOrder 的 message,成员包含一个消息头,和五个消息体。

头部包含了书的 ISBN 号,体包含了订单提交者的信息。订单提交后,会将此消息传递至 WCF Service,经过处理后,会把订单编号 (00012345678) 传回到客户端浏览器中显示。


示例的核心,在于 Service.cs 里的 WCF 服务,若系统将来需要修改 Server-side 逻辑,直接修改即可,不需要重新编译。若有宿主 (Host) 应用程序,也不用修改该个宿主应用程序。


Server-side :

 

ExpandedBlockStart.gif IService.cs
using  System;
using  System.Runtime.Serialization;
using  System.ServiceModel;

[ServiceContract]
public   interface  IService
{
    [OperationContract]
    
string  InitiateOrder();

    [OperationContract()]
    BookOrder PlaceOrder(BookOrder request);

    [OperationContract]
    
string  FinalizeOrder();
}

[MessageContract(IsWrapped 
=   false )]
public   class  BookOrder
{
    
private   string  isbn;
    
private   int  quantity;
    
private   string  firstname;
    
private   string  lastname;
    
private   string  address;
    
private   string  ordernumber;

    
public  BookOrder() { }

    
public  BookOrder(BookOrder message)
    {
        
this .isbn  =  message.isbn;
        
this .quantity  =  message.quantity;
        
this .firstname  =  message.firstname;
        
this .lastname  =  message.lastname;
        
this .address  =  message.address;
        
// this.ordernumber = message.ordernumber;
    }

    [MessageHeader]
    
public   string  ISBN
    {
        
get  {  return  isbn; }
        
set  { isbn  =  value; }
    }

    [MessageBodyMember(Order 
=   1 )]
    
public   int  Quantity
    {
        
get  {  return  quantity; }
        
set  { quantity  =  value; }
    }

    [MessageBodyMember(Order 
=   2 )]
    
public   string  FirstName
    {
        
get  {  return  firstname; }
        
set  { firstname  =  value; }
    }

    [MessageBodyMember(Order 
=   3 )]
    
public   string  LastName
    {
        
get  {  return  lastname; }
        
set  { lastname  =  value; }
    }

    [MessageBodyMember(Order 
=   4 )]
    
public   string  Address
    {
        
get  {  return  address; }
        
set  { address  =  value; }
    }

    [MessageBodyMember(Order 
=   5 )]
    
public   string  OrderNumber
    {
        
get  {  return  ordernumber; }
        
set  { ordernumber  =  value; }
    }
}

 

ExpandedBlockStart.gif Service.cs
using  System;
using  System.ServiceModel;

public   class  Service : IService
{
    
string  IService.InitiateOrder()
    {
        
return   " 开始处理订单... " ;    // 进度显示「开始处理订单...」
    }

    
public  BookOrder PlaceOrder(BookOrder request)
    {
        
// here you could do something with the information passed in to place the order.
        BookOrder response  =   new  BookOrder(request);
        response.OrderNumber 
=   " 00012345678 " ;   // 系统给的订单编号
         return  response;
    }

    
string  IService.FinalizeOrder()
    {
        
return   " 您的订单已订购成功! " ;     // 进度显示「您的订单已订购成功!」
    }
}


Client-side :


ExpandedBlockStart.gif Default.aspx
< html >
< body >
    
< form  id ="form1"  runat ="server" >
    
< div >     
        ISBN: 
< asp:TextBox  ID ="TextBox_isbn"  runat ="server" ></ asp:TextBox >< br  />
        数量: 
< asp:TextBox  ID ="TextBox_quantity"  runat ="server" ></ asp:TextBox >< br  />
        名字: 
< asp:TextBox  ID ="TextBox_firstname"  runat ="server" ></ asp:TextBox >< br  />
        姓氏: 
< asp:TextBox  ID ="TextBox_lastname"  runat ="server" ></ asp:TextBox >< br  />
        地址: 
< asp:TextBox  ID ="TextBox_address"  runat ="server" ></ asp:TextBox >< br  />
        订单编号(由系统自动带入): 
< asp:TextBox  ID ="TextBox_ordernumber"  runat ="server"  ReadOnly ="true"  BackColor ="Silver" ></ asp:TextBox >< br  />
        
< asp:Button  ID ="Button1"  runat ="server"  Text ="提交订单"  onclick ="Button1_Click"   />
        
< p ></ p >
        
< asp:Label  ID ="Label_Progress"  runat ="server"  ForeColor ="Red" ></ asp:Label >
    
</ div >
    
</ form >
</ body >
</ html >

 

ExpandedBlockStart.gif Default.aspx.cs
using  System.ServiceModel;

public   partial   class  _Default : System.Web.UI.Page 
{
    
protected   void  Page_Load( object  sender, EventArgs e)
    {        
    }

    
protected   void  Button1_Click( object  sender, EventArgs e)
    {
        ServiceReference1.ServiceClient svc 
=   new  ServiceReference1.ServiceClient();
        ServiceReference1.BookOrder bo 
=   new  ServiceReference1.BookOrder();
        
        bo.ISBN 
=  TextBox_isbn.Text;
        bo.Quantity 
=  Convert.ToInt32(TextBox_quantity.Text);
        bo.FirstName 
=  TextBox_firstname.Text;
        bo.LastName 
=  TextBox_lastname.Text;
        bo.Address 
=  TextBox_address.Text;

        
try
        {
            Label_Progress.Text 
=  svc.InitiateOrder();   // 进度显示「开始处理订单...」    

            ServiceReference1.BookOrder reply 
=  ((ServiceReference1.IService)svc).PlaceOrder(bo);
            TextBox_ordernumber.Text 
=  reply.OrderNumber;    // Server-side 系统给的订单编号

            Label_Progress.Text 
=  svc.FinalizeOrder();   // 进度显示「您的订单已订购成功!」
        }
        
catch  (Exception ex)
        {
            Label_Progress.Text 
=   " 订购过程发生错误!<br> "   +  ex.Message;
        }
    }
}

 


图 4 执行画面,订单提交成功的画面


此示例中,用户在表单里填写的信息,会借由「消息头 (MessageHeader)」和「消息体 (MessageBodyMember)」传送。提交后,系统会返回一个订单编号。若提交成功,最后会有反馈信息,可让用户知道已正确提交了订单。

此例还可加入其他应用,提交后除了返回一个订单编号,还可再扩展来完成更多工作,如:验证书籍库存数量是否足够、查找书名信息 ...等。



相关文章

[1] WCF分布式安全开发实践(10):消息安全模式之自定义用户名密码:Message_UserNamePassword_WSHttpBinding
http://www.cnblogs.com/frank_xl/archive/2009/08/12/1543867.html

[2] WCF问题(10):如何配置不启用安全的WCF服务?
http://social.microsoft.com/Forums/zh-CN/wcfzhchs/thread/e5d6e169-bb86-43df-8b13-9d4bead33cba
http://social.microsoft.com/Forums/zh-CN/wcfzhchs/thread/2f95f41c-430d-495a-b55b-245922fc63fe

[3] WCF 4 安全性和 WIF 简介
http://www.cnblogs.com/WizardWu/archive/2010/10/04/1841793.html

[4] Security - SSL (繁体中文)
http://sites.google.com/site/stevenattw/dot-net/wcf/security-SSL

[5] MessageContractAttribute 类
http://msdn.microsoft.com/zh-cn/library/system.servicemodel.messagecontractattribute.aspx
http://msdn.microsoft.com/zh-cn/library/ms732038.aspx

 

参考书籍

[6] MCTS 70-503 - Microsoft .NET 3.5 Windows Communication Foundation, Chapter 1
http://www.silverlightchina.net/html/download/books/2009/1110/235.html

[7] Professional WCF Programming, Chapter 6
http://www.wrox.com/WileyCDA/WroxTitle/Professional-WCF-Programming-NET-Development-with-the-Windows-Communication-Foundation.productCd-0470089849.html

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值