轉帖|其它|編輯:郝浩|2010-11-29 15:53:34.000|閱讀 2177 次
概述:本文介紹一下在WCF中使用SoapHeader進行驗證的兩種實現方法,同時再次復習自定義Inspector和自定義EndpointBehavior。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
本文介紹一下在WCF中使用SoapHeader進行驗證的兩種實現方法,同時再次復習自定義Inspector和自定義EndpointBehavior。
在Xml Web Service中能將用戶的身份信息如用戶名,密碼添加到SoapHeader中,從而實現服務調用的身份驗證,這種做法是沿用了Http中用戶名,密碼身份驗證,是我們最樂于接受的。而在WCF中因為提供了非常健壯的安全機制,但實現起來真是不夠簡單。對于多數應用情景來講,有點大炮打蚊子的感覺。因此好多人在網上詢問在WCF中如何象XMl Web Service一樣使用SoapHeader來完成用戶名,密碼身份驗證。傳統的辦法是通過在服務的操作中從OperationContext.Current.IncomingMessageHeaders來獲取Header中的內容,而在客戶端在OperationContext.Current.OutgoingMessageHeaders中添加MessageHeader。下面的代碼片段簡要的介紹了這種實現:
在服務端的一個Operation中
public string GetData(int value)
{
System.Text.Encoding encoding = System.Text.Encoding.GetEncoding("utf-8");
string username = "";
string pwd = "";
int index =
OperationContext.Current.IncomingMessageHeaders.
FindHeader("username", "//tempuri.org");
if (index >= 0)
{
username =
OperationContext.Current.IncomingMessageHeaders.
GetHeader<string>(index).ToString();
}
index = OperationContext.Current.IncomingMessageHeaders.
FindHeader("pwd", "//tempuri.org");
if (index >= 0)
{
pwd = OperationContext.Current.IncomingMessageHeaders.
GetHeader<string>(index).ToString();
}
return string.Format("You entered: {0}", value);
}
在客戶端調代碼如下:
Robin_Wcf_Formatter_Svc.Service1Client svc =
new Robin_Wcf_Formatter_Svc.Service1Client();
using (OperationContextScope scope =
new OperationContextScope(svc.InnerChannel))
{
MessageHeader header =
MessageHeader.CreateHeader("username", "//tempuri.org", "robinzhang");
OperationContext.Current.OutgoingMessageHeaders.Add(header);
header = MessageHeader.CreateHeader("pwd", "//tempuri.org", "robinzhang");
OperationContext.Current.OutgoingMessageHeaders.Add(header);
string res = svc.GetData(10);
}
通過上邊的代碼實現,已經能在WCF中使用SoapHeader來傳遞身份信息了。但這種方式需要在每次客戶端調用和每個服務操作中都增加類似代碼片斷。比較麻煩。多數情況下,我們的服務開發好之后,往往只開放給固定的用戶用于消費,如果我們的服務的實例模式為PerCall,也就是不保存會話,同時我們又希望能驗證調用者的身份信息,我們需要在每個Operation的消息中增加SoapHeader來附加身份信息。這樣服務即可保證每一個操作都不被非法調用。閱讀完上篇文章,已經了解到通過MessageInspector能攔截消息用于記錄或者修改,如果在攔截到消息之后,在消息中增加MessageHeader便可以實現上述需求。為此我們實現了一個實現IClientMessageInspector, IDispatchMessageInspector, IEndpointBehavior三個接口的類,這樣該類就承擔了兩種角色,自定義MessageInspector,自定義EndpointBehavior。這個類的代碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ServiceModel;
namespace RobinLib
{
public class AttachUserNamePasswordBehavior : IClientMessageInspector, IDispatchMessageInspector, IEndpointBehavior
{
private static string UserName =
System.Configuration.ConfigurationSettings.AppSettings["username"];
private static string Password =
System.Configuration.ConfigurationSettings.AppSettings["pwd"];
public AttachUserNamePasswordBehavior()
{
}
#region IClientMessageInspector 成員
public void AfterReceiveReply(ref System.
ServiceModel.Channels.Message reply, object correlationState)
{
}
public object BeforeSendRequest
(ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel)
{
MessageHeader userNameHeader =
MessageHeader.CreateHeader
("OperationUserName", "//tempuri.org", UserName, false, "");
MessageHeader pwdNameHeader =
MessageHeader.CreateHeader
("OperationPwd", "//tempuri.org", Password, false, "");
request.Headers.Add(userNameHeader);
request.Headers.Add(pwdNameHeader);
Console.WriteLine(request);
return null;
}
#endregion
#region IDispatchMessageInspector 成員
string GetHeaderValue(string key)
{
int index = OperationContext.Current.IncomingMessageHeaders.FindHeader
(key, "//tempuri.org");
if (index >= 0)
{
return OperationContext.Current.IncomingMessageHeaders.GetHeader<string>(index).
ToString();
}
return null;
}
public object AfterReceiveRequest
(ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel,
System.ServiceModel.InstanceContext instanceContext)
{
Console.WriteLine(request);
string username = GetHeaderValue("OperationUserName");
string pwd = GetHeaderValue("OperationPwd");
if (username == "robinzhang" && pwd == "111111")
{
}
else
{
throw new Exception("操作中的用戶名,密碼不正確!");
}
return null;
}
public void BeforeSendReply(ref System.ServiceModel.
Channels.Message reply, object correlationState)
{
}
#endregion
#region IEndpointBehavior 成員
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add
(new AttachUserNamePasswordBehavior());
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new AttachUserNamePasswordBehavior());
}
public void Validate(ServiceEndpoint endpoint)
{
}
#endregion
}
}
象上文一樣,將自定義的EndpointBehavior通過代碼方式應用到Host和Proxy中
服務宿主程序
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace Robin_Wcf_OperationWithToken_Host
{
public class Program
{
static void Main(string[] args)
{
//服務地址
Uri baseAddress = new Uri("net.tcp://127.0.0.1:8081/Robin_Wcf_Formatter");
ServiceHost host =
new ServiceHost(typeof(Robin_Wcf_OperationWithToken_SvcLib.Service1),
new Uri[] { baseAddress });
//服務綁定
NetTcpBinding bind = new NetTcpBinding();
host.AddServiceEndpoint(typeof
(Robin_Wcf_OperationWithToken_SvcLib.IService1), bind, "");
if (host.Description.Behaviors.Find
<System.ServiceModel.Description.ServiceMetadataBehavior>() == null)
{
System.ServiceModel.Description.ServiceMetadataBehavior svcMetaBehavior =
new System.ServiceModel.Description.ServiceMetadataBehavior();
svcMetaBehavior.HttpGetEnabled = true;
svcMetaBehavior.HttpGetUrl = new Uri("//127.0.0.1:8001/Mex");
host.Description.Behaviors.Add(svcMetaBehavior);
}
host.Opened += new EventHandler(delegate(object obj, EventArgs e)
{
Console.WriteLine("服務已經啟動!");
});
foreach (var sep in host.Description.Endpoints)
{
sep.Behaviors.Add(new RobinLib.AttachUserNamePasswordBehavior());
}
host.Open();
Console.Read();
}
}
}
客戶端代理
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class Service1Client : System.ServiceModel.ClientBase<
Robin_Wcf_OperationWithToken_ClientApp.ServiceReference1.IService1>, Robin_Wcf_OperationWithToken_ClientApp.ServiceReference1.IService1
{
public Service1Client()
{
base.Endpoint.Behaviors.Add
(new RobinLib.AttachUserNamePasswordBehavior());
}
public Service1Client(string endpointConfigurationName) :
base(endpointConfigurationName)
{
base.Endpoint.Behaviors.Add
(new RobinLib.AttachUserNamePasswordBehavior());
}
public Service1Client
(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
base.Endpoint.Behaviors.Add
(new RobinLib.AttachUserNamePasswordBehavior());
}
public Service1Client
(string endpointConfigurationName,
System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
base.Endpoint.Behaviors.Add(new RobinLib.
AttachUserNamePasswordBehavior());
}
public Service1Client(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
base.Endpoint.Behaviors.Add
(new RobinLib.AttachUserNamePasswordBehavior());
}
public string GetData(int value)
{
return base.Channel.GetData(value);
}
public Robin_Wcf_OperationWithToken_ClientApp.ServiceReference1.
CompositeType GetDataUsingDataContract
(Robin_Wcf_OperationWithToken_ClientApp.
ServiceReference1.CompositeType composite)
{
return base.Channel.GetDataUsingDataContract(composite);
}
到此,代碼基本實現了,在正式應用的時候,我們只需要為每個客戶端創建獨立的用戶名,密碼對,然后將這個信息通過一些渠道告訴服務消費者,服務消費者需要將用戶名,密碼放到Web.Config中的AppSettings中。而且在正式應用的時候,需要將放置到MessageHeader中的用戶名,密碼進行加密,而不是明文傳輸。這樣這套機制就能用于生產啦。
通過這種辦法,我們能為每個操作都設定身份驗證,同時不需要更改Operation函數內容和客戶端調用方式,我們來看一下運行結果:
用戶,密碼正確情況下的調用
服務器端:
客戶端:
如果用戶名,密碼不匹配,服務能正常運行,但客戶端會遇到異常
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉載自:博客轉載