传说中的WCF:消息拦截与篡改
我们知道,在WCF中,客户端对服务操作方法的每一次调用,都可以被看作是一条消息,而且,可能我们还会有一个疑问:如何知道客户端与服务器通讯过程中,期间发送和接收的SOAP是什么样子。当然,也有人是通过借助其他工具来抓取数据包来查看。那,有没有办法让程序自己输出相应的SOAP信息呢?
当然有,这就是我们本文要说的,对消息的拦截与篡改,呵,我用了一个不太好听动词——篡改。
由于WCF的模型相对复杂,对于如何拦截和修改消息会让许多刚接触的朋友有点抓狂。是的,虽然MSDN文档都有详细的说明,但估计你也和我有相同的感觉,看了MSDN的说明后依然一头雾水。确实如此,毕竟WCF不像窗口和控件那样可以看得见,理解起来比较直观,相反的,这些东西会相对抽象。
说到消息拦截,这个你肯定可以理解,如果你不懂,你可以想一想电话窃听程序,我在你的手机上植入一种木马,可以截取你和MM的通话内容,其实这就是消息拦截。
WCF相关的API比较难寻找,我当初也找了N久,现在,我直接把思路和方法告诉各位,也免得大家太辛苦。
要对SOAP消息进行拦截和修改,我们需要实现两个接口,它们都位于System.ServiceModel.Dispatcher (程序集System.ServiceModel)。下面分别价绍。
接口一:IClientMessageInspector
从名字中我们可以猜测,它是用来拦截客户消息的,而看看它的方法,你就更加肯定当初的猜测了。
- BeforeSendRequest:向服务器发送请求前拦截或修改消息(事前控制)
- AfterReceiveReply:接收到服务器的回复消息后,在调用返回之前拦截或修改消息(事后诸葛亮)
接口二:IDispatchMessageInspector
刚才那个接口是针对客户端的,而这个是针对服务器端的。
- AfterReceiveRequest:接收客户端请求后,在进入操作处理代码之前拦截或修改消息(欺上)
- BeforeSendReply:服务器向客户端发送回复消息之前拦截和修改消息(瞒下)。
虽然实现了这两个接口,但你会有新的疑问,怎么用?把它们放到哪儿才能拦截消息?因此,下一步就是要实现IEndpointBehavior按口(System.ServiceModel.Description命名空间,程序集System.ServiceModel),它有四个方法,而我们只需要处理两个就够了。
下面是MSDN的翻译版本说明:
使用 ApplyClientBehavior 方法可以在客户端应用程序中修改、检查或插入对终结点中的扩展。
使用 ApplyDispatchBehavior 方法可以在服务应用程序中修改、检查或插入对终结点范围执行的扩展。
我想不用额外解释了,说白了就是一个在客户拦截和修改消息,另一个在服务器端拦截和修改消息。
在实现这两个方法时,和前面我们实现的IClientMessageInspector和IDispatchMessageInspector联系起来就OK了。
做完了IEndpointBehavior的事情后,把它插入到服务终结点中就行了,无论是服务器端还是客户端,这一步都必须的,因为我们实现的拦截器是包括两个端的,因此,较好的做法是把这些类写到一个独立的类库(dll)中,这样一来,服务器端和客户端都可以引用它。详见后面的示例。
理论课上完了,下面开始实验课,按照前面的指导思想,我们先要写一个类库。
新建一个类库应用,然后添加System.ServiceModel程序集的引用,这个不用我教你了,你懂的。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using System.ServiceModel;
- using System.ServiceModel.Dispatcher;
- using System.ServiceModel.Description;
- using System.ServiceModel.Channels;
- namespace MyLib
- {
- /// <summary>
- /// 消息拦截器
- /// </summary>
- public class MyMessageInspector:IClientMessageInspector,IDispatchMessageInspector
- {
- void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)
- {
- Console.WriteLine("客户端接收到的回复:\n{0}", reply.ToString());
- }
- object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
- {
- Console.WriteLine("客户端发送请求前的SOAP消息:\n{0}", request.ToString());
- return null;
- }
- object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
- {
- Console.WriteLine("服务器端:接收到的请求:\n{0}", request.ToString());
- return null;
- }
- void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
- {
- Console.WriteLine("服务器即将作出以下回复:\n{0}", reply.ToString());
- }
- }
- /// <summary>
- /// 插入到终结点的Behavior
- /// </summary>
- public class MyEndPointBehavior : IEndpointBehavior
- {
- public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
- {
- // 不需要
- return;
- }
- public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
- {
- // 植入“偷听器”客户端
- clientRuntime.ClientMessageInspectors.Add(new MyMessageInspector());
- }
- public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
- {
- // 植入“偷听器” 服务器端
- endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyMessageInspector());
- }
- public void Validate(ServiceEndpoint endpoint)
- {
- // 不需要
- return;
- }
- }
- }
这一步,我们先建立服务器端。
记得要引用我们刚才写的类库。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using System.Runtime;
- using System.Runtime.Serialization;
- using System.ServiceModel;
- using System.ServiceModel.Description;
- namespace WCFServer
- {
- class Program
- {
- static void Main(string[] args)
- {
- // 服务器基址
- Uri baseAddress = new Uri("http://localhost:1378/services");
- // 声明服务器主机
- using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddress))
- {
- // 添加绑定和终结点
- WSHttpBinding binding = new WSHttpBinding();
- host.AddServiceEndpoint(typeof(IService), binding, "/test");
- // 添加服务描述
- host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
- // 把自定义的IEndPointBehavior插入到终结点中
- foreach (var endpont in host.Description.Endpoints)
- {
- endpont.EndpointBehaviors.Add(new MyLib.MyEndPointBehavior());
- }
- try
- {
- // 打开服务
- host.Open();
- Console.WriteLine("服务已启动。");
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex.Message);
- }
- Console.ReadKey();
- }
- }
- }
- [ServiceContract(Namespace = "MyNamespace")]
- public interface IService
- {
- [OperationContract]
- int AddInt(int a, int b);
- [OperationContract]
- Student GetStudent();
- [OperationContract]
- CalResultResponse ComputingNumbers(CalcultRequest inMsg);
- }
- [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
- public class MyService : IService
- {
- public int AddInt(int a, int b)
- {
- return a + b;
- }
- public Student GetStudent()
- {
- Student stu = new Student();
- stu.StudentName = "小明";
- stu.StudentAge = 22;
- return stu;
- }
- public CalResultResponse ComputingNumbers(CalcultRequest inMsg)
- {
- CalResultResponse rmsg = new CalResultResponse();
- switch (inMsg.Operation)
- {
- case "加":
- rmsg.ComputedResult = inMsg.NumberA + inMsg.NumberB;
- break;
- case "减":
- rmsg.ComputedResult = inMsg.NumberA - inMsg.NumberB;
- break;
- case "乘":
- rmsg.ComputedResult = inMsg.NumberA * inMsg.NumberB;
- break;
- case "除":
- rmsg.ComputedResult = inMsg.NumberA / inMsg.NumberB;
- break;
- default:
- throw new ArgumentException("运算操作只允许加、减、乘、除。");
- break;
- }
- return rmsg;
- }
- }
- [DataContract]
- public class Student
- {
- [DataMember]
- public string StudentName;
- [DataMember]
- public int StudentAge;
- }
- [MessageContract]
- public class CalcultRequest
- {
- [MessageHeader]
- public string Operation;
- [MessageBodyMember]
- public int NumberA;
- [MessageBodyMember]
- public int NumberB;
- }
- [MessageContract]
- public class CalResultResponse
- {
- [MessageBodyMember]
- public int ComputedResult;
- }
- }
接下来,实现客户端。
a、引用刚才写的类库MyLib;
b、引用WCF服务。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- namespace WCFClient
- {
- class Program
- {
- static void Main(string[] args)
- {
- WS.ServiceClient client = new WS.ServiceClient();
- // 记得在客户端也要插入IEndPointBehavior
- client.Endpoint.EndpointBehaviors.Add(new MyLib.MyEndPointBehavior());
- try
- {
- // 1、调用带元数据参数和返回值的操作
- Console.WriteLine("\n20和35相加的结果是:{0}", client.AddInt(20, 35));
- // 2、调用带有数据协定的操作
- WS.Student student = client.GetStudent();
- Console.WriteLine("\n学生信息---------------------------");
- Console.WriteLine("姓名:{0}\n年龄:{1}", student.StudentName, student.StudentAge);
- // 3、调用带消息协定的操作
- Console.WriteLine("\n15乘以70的结果是:{0}", client.ComputingNumbers("乘", 15, 70));
- }
- catch (Exception ex)
- {
- Console.WriteLine("异常:{0}", ex.Message);
- }
- client.Close();
- Console.ReadKey();
- }
- }
- }
现在你可以运行程序来观察了。
知道了如何拦截消息,那么修改消息就不难了。
现在我们把前面写的类库MyLib。
将消息拦截器MyMessageInspector作如下修改:
- /// <summary>
- /// 消息拦截器
- /// </summary>
- public class MyMessageInspector:IClientMessageInspector,IDispatchMessageInspector
- {
- void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)
- {
- //Console.WriteLine("客户端接收到的回复:\n{0}", reply.ToString());
- return;
- }
- object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
- {
- //Console.WriteLine("客户端发送请求前的SOAP消息:\n{0}", request.ToString());
- // 插入验证信息
- MessageHeader hdUserName = MessageHeader.CreateHeader("u", "fuck", "admin");
- MessageHeader hdPassWord = MessageHeader.CreateHeader("p", "fuck", "123");
- request.Headers.Add(hdUserName);
- request.Headers.Add(hdPassWord);
- return null;
- }
- object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
- {
- //Console.WriteLine("服务器端:接收到的请求:\n{0}", request.ToString());
- // 栓查验证信息
- string un = request.Headers.GetHeader<string>("u", "fuck");
- string ps = request.Headers.GetHeader<string>("p", "fuck");
- if (un == "admin" && ps == "abcd")
- {
- Console.WriteLine("用户名和密码正确。");
- }
- else
- {
- throw new Exception("验证失败,滚吧!");
- }
- return null;
- }
- void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
- {
- //Console.WriteLine("服务器即将作出以下回复:\n{0}", reply.ToString());
- return;
- }
- }
注意:添加对System.Runtime.Serialization的引用。
创建消息头时,第一个参数是名字,如上面的“u”,第二个参数是命名空间,这个可以自己来定义,比如上面的“fuck”,第三个参数就是消息头的内容。
现在重新生成一下项目,再试试。
前面我们说过,如果安装证书进行身份验证会相当TMD麻烦,而可以通过修改SOAP消息头来验证,但是,上次的做法会有一个麻烦,那就是每次调用操作协定都要手动修改一次,这一次,我们直接在终结点级别进行修改和验证,就省去了许多功夫。
传说中的WCF:消息拦截与篡改的更多相关文章
- 传说中的WCF(10):消息拦截与篡改
我们知道,在WCF中,客户端对服务操作方法的每一次调用,都可以被看作是一条消息,而且,可能我们还会有一个疑问:如何知道客户端与服务器通讯过 程中,期间发送和接收的SOAP是什么样子.当然,也有人是通过 ...
- 重温WCF之消息拦截与篡改(八)
我们知道,在WCF中,客户端对服务操作方法的每一次调用,都可以被看作是一条消息,而且,可能我们还会有一个疑问:如何知道客户端与服务器通讯过程中,期间发送和接收的SOAP是什么样子.当然,也有人是通过借 ...
- WCF消息拦截,利用消息拦截做身份验证服务
本文参考 http://blog.csdn.net/tcjiaan/article/details/8274493 博客而写 添加对信息处理的类 /// <summary> /// 消 ...
- Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务、WCF消息头添加安全验证Token
原文:Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务.WCF消息头添加安全验证Token 为什么选择wcf? 因为好像wcf和wpf就是哥俩,,, 为什么选择异步 ...
- 自定义HTTP消息拦截
/// <summary> /// HTTP消息拦截器 /// </summary> public class RequestHandler : DelegatingHandl ...
- WCF初探-4:WCF消息交换模式之请求与答复模式
请求与答复模式( Request/Reply) 这种交换模式是使用最多的一中,它有如下特征: 调用服务方法后需要等待服务的消息返回,即便该方法返回 void 类型 相比Duplex来讲,这种模式强调的 ...
- WCF初探-19:WCF消息协定
WCF消息协定概述 在生成 WCF应用程序时,开发人员通常会密切关注数据结构和序列化问题,而不必关心携带数据的消息结构. 对于这些应用程序,为参数或返回值创建数据协定的过程很简单.但是,有时完全控制 ...
- 替换 wcf 消息传输中的 命名空间
替换 wcf 消息传输中的 命名空间,http://vanacosmin.ro/Articles/Read/WCFEnvelopeNamespacePrefix
- Windows消息拦截技术的应用
Windows消息拦截技术的应用 民航合肥空管中心 周毅 一.前 言 众所周知,Windows程式的运行是依靠发生的事件来驱动.换句话说,程式不断等待一个消息的发生,然后对这个消息的类型进行判断,再做 ...
随机推荐
- 【ASP.NET Core】运行原理(2):启动WebHost
本系列将分析ASP.NET Core运行原理 [ASP.NET Core]运行原理[1]:创建WebHost [ASP.NET Core]运行原理[2]:启动WebHost [ASP.NET Core ...
- node.js学习笔记(三)——事件循环
要理解事件循环,首先要理解事件驱动编程(Event Driven Programming).它出现在1960年.如今,事件驱动编程在UI编程中大量使用.JavaScript的一个主要用途是与DOM交互 ...
- c# table 怎么在前台循环展示 ViewBag
后台 public ActionResult DoctorEvaluation()//前台页面 { HE_Department HE_dt = new HE_Department(); DataTab ...
- OpenGL 笔记<1> 固定管线实例 + 双缓存测试实例
欲以此分类来记录opengl的学习历程,此为第一篇,所以先来一个固定管线的例子,以及对双缓存的测试. 一.配置环境 写之前,先进行配置,然后再讲内容. 注:第一部分涉及的代码均忽略. [环境配置传送门 ...
- 自动分配ip的方法- 【Linux】
1. 查看本机无线网络使用的网卡 2. 设置vbox的网络连接为桥接,并选择本机无线网络对应的网卡 3. 进入系统,输入ifconfig命令,记录下系统的HWaddr 4. 修改系统ip配置文 ...
- grok正则
USERNAME [a-zA-Z0-9._-]+ USER %{USERNAME} INT (?:[+-]?(?:[0-9]+)) BASE10NUM (?<![0-9.+-])(?>[+ ...
- CocoStuff—基于Deeplab训练数据的标定工具【五、训练成果分析】
一.说明 本文为系列博客第五篇,主要展示训练的结果,以及对训练进行分析. *注:暂未进行大量的数据训练以及IoU测算,目前只做到使用Matlab将训练结果的mat文件可视化. 二. *占坑
- Linux 文件系统 -- 文件权限简介
一.文件权限 使用 ls -l 命令可以查看文件的具体属性: 如图所示,第一列所示告诉了用户一个文件的类型和权限信息: 1)第一个字符 "d",表明该文件是一个目录文件: 2)r ...
- IIS 无法加载 CSS,JS的问题
IIS 能加载 aspx,但不能加载里面的 js,css ,感觉有点坑. 解决方案如下:http://www.pageadmin.net/article/20121001/479.html 原来是没有 ...
- [BUAA OO]第二次博客作业
第五次作业 这次作业是电梯系列作业的终极版,要求是使用多线程实现三部电梯的运行.这次作业的难点在于第一次运用多线程技术,对于线程中的行为并不了解,以及电梯功能的实现(如果之前作业采取的是扫描指令队列预 ...