上文WCF进阶:将消息正文Base64编码中介绍了实现自定义MessageInspector来记录消息和实现自定义Formatter来改写消息,本文介绍一下在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", "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);

}

在客户端调代码如下:

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);

}

通过上边的代码实现,已经能在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", "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通过代码方式应用到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("http://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函数内容和客户端调用方式,我们来看一下运行结果:

用户,密码正确情况下的调用

服务器端:

客户端:

如果用户名,密码不匹配,服务能正常运行,但客户端会遇到异常

示例程序:/Files/jillzhang/Robin_Wcf_CallOperationWithToken.rar

WCF不用证书实现验证(messageheader)的更多相关文章

  1. 关于WEB Service&WCF&WebApi实现身份验证之WCF篇(2)

    因前段时间工作变动(换了新工作)及工作较忙暂时中断了该系列文章,今天难得有点空闲时间,就继续总结WCF身份验证的其它方法.前面总结了三种方法(详见:关于WEB Service&WCF& ...

  2. 关于WEB Service&WCF&WebApi实现身份验证之WebApi篇

    之前先后总结并发表了关于WEB Service.WCF身份验证相关文章,如下: 关于WEB Service&WCF&WebApi实现身份验证之WEB Service篇. 关于WEB S ...

  3. WCF服务器证书配置说明-没有能够进行密钥交换的私钥,或者进程可能没有访问私钥的权限

    WCF服务器证书配置说明 1.创建证书: makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=XXX -sky exchange -pe 说明: -s ...

  4. 关闭Postman 证书的验证

    1.问题背景 使用自己生成的SSL证书,用Postman访问失败.需要忽略SSL证书的验证 2.关闭Postman 证书的验证 在Settings-General中 关闭SSL certificate ...

  5. 【WCF安全】使用X509证书自定义验证

    接触WCF时间比较短,在项目中要使用X509证书,纠结好几天终于有了结论,因此为了方便日后查阅和园友交流特意单独将部分代码提出,并做以记录. 1.准备工作 制作X509证书,此处用到三个证书名称 导入 ...

  6. 自定义实现wcf的用户名密码验证

    目前wcf分为[传输层安全][消息层安全]两种,本身也自带的用户名密码验证的功能,但是ms为了防止用户名密码明文在网络上传输,所以,强制要求一旦使用[用户名密码]校验功能,则必须使用证书,按照常理讲, ...

  7. 关于WEB Service&WCF&WebApi实现身份验证之WCF篇(1)

    WCF身份验证一般常见的方式有:自定义用户名及密码验证.X509证书验证.ASP.NET成员资格(membership)验证.SOAP Header验证.Windows集成验证.WCF身份验证服务(A ...

  8. 【WCF】Silverlight+wcf+自定义用户名密码验证

    本文摘自 http://www.cnblogs.com/virusswb/archive/2010/01/26/1656543.html 在昨天的博文Silverlight3+wcf+在不使用证书的情 ...

  9. 关于WEB Service&WCF&WebApi实现身份验证之WEB Service篇

    在这个WEB API横行的时代,讲WEB Service技术却实显得有些过时了,过时的技术并不代表无用武之地,有些地方也还是可以继续用他的,我之所以会讲解WEB Service,源于我最近面试时被问到 ...

随机推荐

  1. Ubuntu 9.10+ apache2.2 +Django的配置

    1.首先安装mod_python apt-get install libapache2-mod-python2.6 (Ubuntu 9.10默认安装的是python 2.6版,如果是2.5可改为 li ...

  2. 别说你不知道java中的包装类,wrapper type,以及容易在自动拆箱中出现的问题

    很多时候,会有人问你,你知道什么是包装类吗? 或者高端一点问你你知道,wrapper type,是什么吗? 然后你就懵逼了,学了java很多时候都不知道这是啥. 其实问你的人,可能只是想问你,java ...

  3. MyEclipse8.5安装findbugs方法

    step 1:首先从官网下载findbugs插件: edu.umd.cs.findbugs.plugin.eclipse_1.3.9.20090821.zipstep 2:将解压之后的edu.umd. ...

  4. Codeforces Round #363 (Div. 2) C. Vacations(DP)

    C. Vacations time limit per test 1 second memory limit per test 256 megabytes input standard input o ...

  5. hadoop伪分布式安装

    hadoop的伪分布安装:一台实体机或虚拟机的安装. 环境:Windows7.VMWare.CentOS 1.1 设置ip地址 说明:在CentOS中的网络的类型: 仅主机模式:虚拟机在Windows ...

  6. 转 通过 spring 容器内建的 profile 功能实现开发环境、测试环境、生产环境配置自动切换

                                      软件开发的一般流程为工程师开发 -> 测试 -> 上线,因此就涉及到三个不同的环境,开发环境.测试环境以及生产环境,通常 ...

  7. 【第k小素数 】 打表问题

    Prime Number TimeLimit: 1 Second MemoryLimit: 32 Megabyte Totalsubmit: 399 Accepted: 88 Description ...

  8. java dom4j解析xml实例(3)

    代码运行前需要先导入dom4j架包. 需要解析的XML文件test.xml如下: <students> <student age="25"><!--如 ...

  9. android脚步---UI界面修改,关于activity中增加按钮和监听

    增加按钮和监听,这个和上个不同在于,它不是在一个dialog里面,而是从新写了一个activity,因此需要先找到这个activity的入口. case R.id.checkframe: if (mC ...

  10. CentOS查询 杀死进程

    ps aux | grep XXX 查询进程 ps a 显示现行终端机下的所有程序,包括其他用户的程序. ps -A 显示所有程序. ps c 列出程序时,显示每个程序真正的指令名称,而不包含路径,参 ...