读书人

传说中的WCF(十):消息拦截与篡改

发布时间: 2012-12-27 10:17:10 作者: rapoo

传说中的WCF(10):消息拦截与篡改

我们知道,在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的翻译版本说明:

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


现在你可以运行程序来观察了。

传说中的WCF(十):消息拦截与篡改

传说中的WCF(十):消息拦截与篡改


知道了如何拦截消息,那么修改消息就不难了。

现在我们把前面写的类库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”,第三个参数就是消息头的内容。


现在重新生成一下项目,再试试。

传说中的WCF(十):消息拦截与篡改

前面我们说过,如果安装证书进行身份验证会相当TMD麻烦,而可以通过修改SOAP消息头来验证,但是,上次的做法会有一个麻烦,那就是每次调用操作协定都要手动修改一次,这一次,我们直接在终结点级别进行修改和验证,就省去了许多功夫。

读书人网 >编程

热点推荐