离刚开始做微信开发到现在已经两个月了,因为被分配了另外一个任务,所以微信的开发就先放到了一遍。
在小公司便是如此,只有自己一个人做开发,所以哪里需要就要先转到哪一块。其实想想自己也没什么太好的理由说留在这个公司,想想也就是工作比较放松点,老板人还可以,项目上也催的不紧,孩子还小家里有点事了可以随时请假回家,这次也是家里父亲和小孩都住院了请了半个月假刚过来。
闲话不多说,先说下微信开发的博客,微信开发也算完成了一部分,因为没有完成,所以之前的『微信公众平台开发(一)---接口介绍及配置』起了个头便一直没写,目前打算是等手头这边的工作完了把大致的功能都写的差不多了再继续写。
这次来了之后看到微信接口增加了“消息体签名及加解密”这块,于是抽了一点时间把原来的接口的消息加密模式改成安全模式。因为微信接口已经提供了加/解密的实现,所以修改起来也是极其的简单了,下面就简单说下 验证微信签名以及对微信消息的加/解密。
首先,在开发者首次提交验证申请时,微信服务器将发送GET请求到填写的URL上,并且带上四个参数(signature、timestamp、nonce、echostr),开发者通过对签名(即signature)的效验,来判断此条消息的真实性。
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。
修改我们上次创建的WeixinService.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.ServiceModel; 6 using System.ServiceModel.Activation; 7 using System.ServiceModel.Web; 8 using System.Web.Security; 9 using System.Runtime.Serialization; 10 using System.IO; 11 using System.Text; 12 using WeiXinModel; 13 using WeiXinRestWCF.BLL; 14 15 namespace WeiXinRestWCF 16 { 17 [ServiceContract] 18 [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] 19 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] 20 public class WeixinService 21 { 22 /// <summary> 23 /// 验证微信签名 24 /// </summary> 25 /// * 将token、timestamp、nonce三个参数进行字典序排序 26 /// * 将三个参数字符串拼接成一个字符串进行sha1加密 27 /// * 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信。 28 /// <returns></returns> 29 [OperationContract] 30 [WebGet(UriTemplate = "API?signature={signature}×tamp={timestamp}&nonce={nonce}&echostr={echostr}", ResponseFormat = WebMessageFormat.Json)] 31 public Stream CheckSignature(string signature, string timestamp, string nonce, string echostr) 32 { 33 string outstr = string.Empty; 34 if (SignatureHelper.CheckSignature(signature, timestamp, nonce)) 35 { 36 if (!String.IsNullOrEmpty(echostr)) 37 { 38 outstr = echostr; 39 } 40 } 41 byte[] resultBytes = Encoding.UTF8.GetBytes(outstr); 42 WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain"; 43 return new MemoryStream(resultBytes); 44 } 45 46 } 47 48 }
在上面的代码中,我们添加了一个WebGet来处理微信发送过来的签名验证GET请求,在UriTemplate中我们添加传入的4个参数,注意{}内的参数字符与方法体的参数名称大小写对应。
需要着重提示的一点就是wcf的返回结果默认是json或xml这两种格式之一,而对于微信服务器需要我们返回的结果是文本型的,所以在这里我们必须把方法体返回的结果设置为Stream类型,并在方法体内设置 WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain";将需要返回的结果字符串写进MemoryStream中,以此达到我们想到达到的目的。
SignatureHelper.CheckSignature对应的实现:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.IO; 6 using System.Web.Security; 7 using System.Text; 8 9 namespace WeiXinRestWCF.BLL 10 { 11 public class SignatureHelper 12 { 13 //检验signature判断真实性 14 public static bool CheckSignature(string signature, string timestamp, string nonce) 15 { 16 bool ischeck = false; 17 string[] ArrTmp = { DeveloperInfo.ServerToken, timestamp, nonce }; 18 Array.Sort(ArrTmp); 19 string tmpStr = string.Join("", ArrTmp); 20 tmpStr = FormsAuthentication.HashPasswordForStoringInConfigFile(tmpStr, "SHA1"); 21 tmpStr = tmpStr.ToLower(); 22 string outstr = string.Empty; 23 if (tmpStr == signature) 24 { 25 ischeck = true; 26 } 27 return ischeck; 28 } 29 } 30 }
这样,我们就完成了签名验证的过程。接下来我们处理来自微信用户的消息。
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
(每次开发者接收用户消息的时候,微信也都会带上前面三个参数(signature、timestamp、nonce)访问开发者设置的URL,开发者依然通过对签名的效验判断此条消息的真实性。PS:特别注意同事传递的另外一个的参数:msg_signature,这个也是对加密的消息体进行解密时的一个重要参数)
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。
对于接收到的微信消息我们这样处理:
在WeixinService.cs中添加下面的代码以对微信服务器的POST请求作出回应:
1 /// <summary> 2 /// 接收微信传递来的消息 3 /// </summary> 4 /// <param name="xmlMessage">传递的消息</param> 5 /// <returns></returns> 6 [OperationContract] 7 [WebInvoke(Method = "POST", UriTemplate = "API?signature={signature}×tamp={timestamp}&nonce={nonce}&msg_signature={msg_signature}", RequestFormat = WebMessageFormat.Xml, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)] 8 //注意上面我们的UriTemplate中url参数列表内包含了msg_signature参数 9 public Stream ReciveTextMessge(Stream xmlMessage, string signature, string timestamp, string nonce, string msg_signature) 10 { 11 //此处依然需要验证消息请求来源是否为微信服务器 12 if (!SignatureHelper.CheckSignature(signature, timestamp, nonce)) 13 { 14 return null; 15 } 16 WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(DeveloperInfo.ServerToken, DeveloperInfo.EncodingAESKey, DeveloperInfo.AppId); 17 //我这里创建了DeveloperInfo类,其中存放着开发者相关的信息,如 public const string AppId = "*************"; 18 int ret = 0; 19 20 double createTime = Math.Truncate(DateTime.UtcNow.Subtract(DateTime.Parse("1970-1-1")).TotalSeconds); 21 StreamReader reader = new StreamReader(xmlMessage); 22 //[密文]收到的消息 23 string incptmsg = reader.ReadToEnd(); 24 //[明文]收到的消息 25 string Recive = string.Empty; 26 27 //在对收到的用户消息进行处理之前进行解密(只在消息内容不为空的时候) 28 if (!string.IsNullOrEmpty(incptmsg)) 29 { 30 ret = wxcpt.DecryptMsg(msg_signature, timestamp, nonce, incptmsg, ref Recive);//注意,此处使用的是msg_signature 31 } 32 ReciveHandler.LogReciveMsg(Recive);//这里记录所有收到的消息到DataBase中 33 //[明文]回复的消息 34 string Reply = ReplyHandler.GetReply(Recive);//ReplyHandler.GetReply方法会对受到的消息进行处理,并返回对应回复消息的字符串 35 //[密文]回复的消息 36 string outcpt = string.Empty; 37 //在此对回复的消息体进行加密(只在消息内容不为空的时候) 38 if (!string.IsNullOrEmpty(Reply)) 39 { 40 ret = wxcpt.EncryptMsg(Reply, timestamp, nonce, ref outcpt); 41 } 42 43 byte[] resultBytes = Encoding.UTF8.GetBytes(outcpt); 44 WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain"; 45 46 Log("这里收到的一条POST信息,当前时间:" + DateTime.Now.ToString() + "\r\n" + "标准时间间隔秒数:" + createTime + "\r\n" + Recive + "\r\n\r\n" + "服务器返回信息为:" + Reply); 47 return new MemoryStream(resultBytes); 48 }
微信消息体的加密解密代码可以参照API文档中提供的Demo,下载地址:http://mp.weixin.qq.com/wiki/downloads/SampleCode.zip
结果如下:
暂时先写到这里。To be continued...