WCF進(jìn)階:為每個(gè)操作附加身份信息
上文WCF進(jìn)階:將消息正文Base64編碼中介紹了實(shí)現(xiàn)自定義MessageInspector來記錄消息和實(shí)現(xiàn)自定義Formatter來改寫消息,本文介紹一下在WCF中使用SoapHeader進(jìn)行驗(yàn)證的兩種實(shí)現(xiàn)方法,同時(shí)再次復(fù)習(xí)自定義Inspector和自定義EndpointBehavior。
在Xml Web Service中能將用戶的身份信息如用戶名,密碼添加到SoapHeader中,從而實(shí)現(xiàn)服務(wù)調(diào)用的身份驗(yàn)證,這種做法是沿用了Http中用戶名,密碼身份驗(yàn)證,是我們最樂于接受的。而在WCF中因?yàn)樘峁┝朔浅=训陌踩珯C(jī)制,但實(shí)現(xiàn)起來真是不夠簡單。對于多數(shù)應(yīng)用情景來講,有點(diǎn)大炮打蚊子的感覺。因此好多人在網(wǎng)上詢問在WCF中如何象XMl Web Service一樣使用SoapHeader來完成用戶名,密碼身份驗(yàn)證。傳統(tǒng)的辦法是通過在服務(wù)的操作中從OperationContext.Current.IncomingMessageHeaders來獲取Header中的內(nèi)容,而在客戶端在OperationContext.Current.OutgoingMessageHeaders中添加MessageHeader。下面的代碼片段簡要的介紹了這種實(shí)現(xiàn):
在服務(wù)端的一個(gè)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", "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("You entered: {0}", value);
}
在客戶端調(diào)代碼如下:
Robin_Wcf_Formatter_Svc.Service1Client svc = new Robin_Wcf_Formatter_Svc.Service1Client();
using (OperationContextScope scope = new OperationContextScope(svc.InnerChannel))
{
MessageHeader header = MessageHeader.CreateHeader("username", "http://tempuri.org", "robinzhang");
OperationContext.Current.OutgoingMessageHeaders.Add(header);
header = MessageHeader.CreateHeader("pwd", "http://tempuri.org", "robinzhang");
OperationContext.Current.OutgoingMessageHeaders.Add(header);
string res = svc.GetData(10);
}
通過上邊的代碼實(shí)現(xiàn),已經(jīng)能在WCF中使用SoapHeader來傳遞身份信息了。但這種方式需要在每次客戶端調(diào)用和每個(gè)服務(wù)操作中都增加類似代碼片斷。比較麻煩。多數(shù)情況下,我們的服務(wù)開發(fā)好之后,往往只開放給固定的用戶用于消費(fèi),如果我們的服務(wù)的實(shí)例模式為PerCall,也就是不保存會話,同時(shí)我們又希望能驗(yàn)證調(diào)用者的身份信息,我們需要在每個(gè)Operation的消息中增加SoapHeader來附加身份信息。這樣服務(wù)即可保證每一個(gè)操作都不被非法調(diào)用。閱讀完上篇文章,已經(jīng)了解到通過MessageInspector能攔截消息用于記錄或者修改,如果在攔截到消息之后,在消息中增加MessageHeader便可以實(shí)現(xiàn)上述需求。為此我們實(shí)現(xiàn)了一個(gè)實(shí)現(xiàn)IClientMessageInspector, IDispatchMessageInspector, IEndpointBehavior三個(gè)接口的類,這樣該類就承擔(dān)了兩種角色,自定義MessageInspector,自定義EndpointBehavior。這個(gè)類的代碼如下:
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", "http://tempuri.org", UserName, false, "");
MessageHeader pwdNameHeader = MessageHeader.CreateHeader("OperationPwd", "http://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, "http://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通過代碼方式應(yīng)用到Host和Proxy中
服務(wù)宿主程序
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)
{
//服務(wù)地址
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 });
//服務(wù)綁定
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("http://127.0.0.1:8001/Mex");
host.Description.Behaviors.Add(svcMetaBehavior);
}
host.Opened += new EventHandler(delegate(object obj, EventArgs e)
{
Console.WriteLine("服務(wù)已經(jīng)啟動!");
});
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);
}
到此,代碼基本實(shí)現(xiàn)了,在正式應(yīng)用的時(shí)候,我們只需要為每個(gè)客戶端創(chuàng)建獨(dú)立的用戶名,密碼對,然后將這個(gè)信息通過一些渠道告訴服務(wù)消費(fèi)者,服務(wù)消費(fèi)者需要將用戶名,密碼放到Web.Config中的AppSettings中。而且在正式應(yīng)用的時(shí)候,需要將放置到MessageHeader中的用戶名,密碼進(jìn)行加密,而不是明文傳輸。這樣這套機(jī)制就能用于生產(chǎn)啦。
通過這種辦法,我們能為每個(gè)操作都設(shè)定身份驗(yàn)證,同時(shí)不需要更改Operation函數(shù)內(nèi)容和客戶端調(diào)用方式,我們來看一下運(yùn)行結(jié)果:
用戶,密碼正確情況下的調(diào)用
服務(wù)器端:
客戶端:
如果用戶名,密碼不匹配,服務(wù)能正常運(yùn)行,但客戶端會遇到異常
示例程序:/Files/jillzhang/Robin_Wcf_CallOperationWithToken.rar
出處:http://jillzhang.cnblogs.com/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。

浙公網(wǎng)安備 33010602011771號