企业微信及微信企业号手册介绍
1 企业微信与微信企业号
1.1 什么是企业微信-企业微信介绍
1.2 企业号升级到企业微信的通知
1.3 企业微信的发布-微信企业号将作为微信插件
1.4 企业微信与微信企业号的区别和联系
1.5 微信企业号与服务号 订阅号的区别
2 企业微信使用
2.1 申请企业微信
2.2 使用微信插件(原企业号)
2.2.1 在企业微信中使用微信企业号
2.2.2 修改微信企业号的LOGO
2.2.3 在微信插件底部可快捷打开企业微信客户端
2.3 修改应用的LOGO
2.4 企业微信获取对方是否已经阅读了信息的状态
2.5 登录企业微信APP
2.6 企业微信退出指定的企业
2.7 企业微信退出后重新加入企业
2.8 企业微信后台添加成员及邀请成员加入
2.9 企业成员几种加入企业微信的方法对比
2.10 免费的企业邮箱并在企业微信中提示收到邮件
2.11 企业微信管理员后台管理
2.11.1 企业微信成员账号能够修改吗?
2.11.2 通讯录如何添加自定义的字段-扩展属性-企业微信
2.12 企业微信内部局域网如何访问企业微信-网管设置?
3 企业微信开发
3.1 企业微信开发入门
3.1.1 代码添加用户与先扫描企业微信二维码的区别
3.2 企业微信开发-回调
3.2.1 为什么要使用HTTPS以及国内外有哪些免费SSL证书
3.2.2 IIS使用阿里免费SSL证书
3.2.3 其它环境使用阿里免费SSL证书
3.2.4 企业微信及微信企业号开发经验总结
3.2.5 企业微信及微信企业号回调设置
3.2.6 回调URL校验失败或-40001错误或echostr校验失败,请您检查是否正确解密并输出明文echostr 终极解决方法
3.3 企业微信独家经验
3.3.1 使用中控服务器获取AccessToken突然提示无权限
3.3.2 如何避免Access Token失效,保证access_token长期有效?
3.4 相关说明
3.4.1 企业微信主动调用频率限制
3.4.2 企业微信开发全局错误码
3.4.3 微信加解密库下载与错误返回码
3.4.4 企业号的CorpSecret在企业微信中如何查到
4 企业微信开发VIP经验心得
4.1 选择企业微信与微信插件原企业号的心得
4.2 企业微信如何通过手机号来获取 对应的成员ID及成员的详细信息呢?

回调URL校验失败或-40001错误或echostr校验失败,请您检查是否正确解密并输出明文echostr 终极解决方法

2017-08-14 14:03:13
zstmtony
1312
最后编辑:zstmtony 于 2017-08-15 14:02:00

回调URL校验失败或-40001错误或echostr校验失败,请您检查是否正确解密并输出明文echostr 终极解决方法


感谢Fans.net的帮助。

主要解决的思路:

  

1.要借助微信企业号接口调试工具: http://qydev.weixin.qq.com/debug

    使用工具的好处比自己在代码中写结果日志更好一些,当然,可以把接口调试工具与打日志结合起来,效果更佳

    此工具旨在帮助开发者检测调用【微信公众平台开发者API】时发送的请求参数是否正确,提交相关信息后可获得服务器的验证结果
   使用说明:
   1). 选择合适的接口。
   2). 系统会生成该接口的参数表,您可以直接在文本框内填入对应的参数值。(红色星号表示该字段必填)
   3). 点击检查问题按钮,即可得到相应的调试信息。

   我们使用这个方法来调试

   选择“建立连接”--》”测试回调模式“

   

   1)选择接口类型为“建立连接”
   2)选择接口列表为“测试回调模式”
       方法:GET
   3)填写参数列表
 
   *URL 开发者填写URL,调试时将把消息推送到该URL上 (这个网页要先在服务器建立好文件,内容可参考这里:

     如我填的是: www.office-cn.net/Callback.php
   *Token:你设置的Token
   *EncodingAESKey:你的EncodingAESKey
   *EchoStr:回文,只允许23个字节的数字
   *ToUserName:企业号CorpID
 
   再点击 检查问题 按钮

    
   企业微信会要求你填写应用的URL、Token、EncodingAESKey三个参数,验证URL、Token,加密的详细处理请参考接收消息时的加解密处理章节。
   URL是企业应用接收企业微信推送请求的访问协议和地址,支持http或https协议。
   Token可由企业任意填写,用于生成签名。
   EncodingAESKey用于消息体的加密,是AES密钥的Base64编码。


2. 你可将 EchoStr 设置为:123456789  (先简单 方便测试)

3. 在服务器的Callback.php 文件中先做一个 直接写死的回传值,如代码如下:

    

   <?php
         
        echo "123456789";
         
   ?>
4.  点击 检查问题 按钮 看看有什么错误提示,如果返回是你指定的 123456789 的正确字符,那就正常,如果不是,根据相应的错误提示去处理


    如一般最多的错误是 这个:提示 {"errcode":70002,"errmsg":"echostr校验失败,请您检查是否正确解密并输出明文echostr "}

    有时也会返回 -40001 的错误 -40001 签名验证错误 (加解密错误返回值),也是下面的原因引起的。

    以下是在服务器的callback.php 将相关参数写到日志文件的结果:

  

sVerifyMsgSig: 21494885**********96f7c050c
sVerifyTimeStamp: 15*******9020
sVerifyNonce: 7*******574
sVerifyEchoStr: 5/wnXwID******************yyt3Oj1vQQGZTB5NVIVyMm4zdWUE3wXt5ejsqiGZ9b8t8vGkx9H VvA==
errCode: -40001

   这种可能有多个原因


    1)是你 Callback.php  的文件没有返回值 ,你可以在 IE里直接打开 www.office-cn.net/Callback.php

         看看返回的值 是否 是123456789 ,如果没有返回,那就找服务器或PHP有否正确解析
    2)IE里直接打开 www.office-cn.net/Callback.php 返回的值是正确的,但企业号调试工具总是提示 

         {"errcode":70002,"errmsg":"echostr校验失败,请您检查是否正确解密并输出明文echostr "}

         这个是很多网友遇到问题最多的一种情况

         一般可能是 :您的PHP文件应该对echostr参数解密并原样返回echostr明文(不能加引号,不能带bom头,不能带换行符),则接入验证生效,接收消息才能开启。

         如果你的echostr明文,带有引号,或带有换行符,或带有Bom头,则判断为不相同,其它带Bom头这种情况是遇到最多的情况,但很多人判断不出来

         因为直接在IE中打开 网页,返回的值看起来是正常的。是123456789, 但实际上在用如抓包软件工具二进制方式(如Fiddler中的Composer) 使用拼接出来的 网址打开

         (拼接起来的网址:https://wx.office-cn.net/example/callback_valid.php?msg_signature=1**************0b0b2a8af2&timestamp=1501724483&nonce=4182954&echostr=Sau/3******2dIR76Ajomsv/f6DJbNDv******IJStyTh9AQEjsKG45jvZEoIEYvvlz1wGh91JFIitK21Q==

         可看到是有不同的

         错误的值:在123456789前面 会带有 几个乱码符号

         

         正确的返回值应该是这样的:

         

         解决办法:1)可将PHP网页文件另存为ansi ,一般都可以解决,但这种方法不是最理想的办法

                         2)用sublime 或notpad++打开你的回调PHP文件,点击编码按钮,将文件的编码改为无BOM头后保存(可保存编码 改为 UTF-8那种 无bom头)

                           (这个问题我弄了2天,才解决,希望这个经验让您少走很多弯路)

   3) 如果 http 连接正常,而 https 连接超时,则很可能是你的https 有问题,或证书有问题,要换不同的地区 直接访问这个网址看看行不行

          我遇到过很奇怪的现象,企业微信测试工具 访问不到我的https, 而有些朋友 的电脑可以访问,有些不能访问。 

        

          用企业微信测试网页 检查问题,提示:

          建立连接:测试回调模式
         请求地址: https://*******/callback_valid.php
         返回结果: {"errcode":70001,"errmsg":"连接超时"} 错误号是 70001 连接超时。解决办法是先解决https可以正常直接访问,

        

     所以一定要先保证这个https://www.office-cn.net/Callback.php 网址可直接打开 

     如果 SSL证书没有问题,其它也正常,但经常出现https访问不到的话,如果你使用的是阿里云的免费SSL证书,可以使用他们提供的修复工具执行一下,并重启服务器

     阿里的免费SSL证书服务器设置工具ITrusIIS(感谢fans提供):在这篇文章的最下面有下载链接

     ITrusIIS设置界面如下:

      

  

     选择 最佳配置,然后点应用 即可,可解决很多https 有时访问不到的问题

     (如果 客户端的浏览器 是Google谷歌的浏览器 53 54版本有问题 要升级到55 56)
     

     另也确保你服务器的安全软件(如安全狗)有否拦截443 https的端口。

     我的其它一台服务器就是安全软件设置了 将443 TCP端口 一律拦截屏蔽导致的(我开放的白名单IP可以访问,但别人的网络就访问不了)

     导致微信企业号测试工具一直返回错误:

      

     建立连接:测试回调模式
    请求地址: https://www.****.com/callback_valid.php
    返回结果: {"errcode":70001,"errmsg":"连接超时"}

    只要在安全软件中设置 开放这个端口就可以了

    如果您使用的是Windows自带的防火墙,入口规则也需要将443开放即可。 

 

    另如果https打不开,可以逐级来检查问题,先检查网站服务器的443端口是否打开,可使用Telnet命令或Tcping 第三方工具

     telnet和ping是我们最经常使用的两个命令。但ping只是一个通信协议,是ip协议的一部分,tcp/ip 协议的一部分,Ping 在Windows系下是自带的一个可执行命令。而Telnet服务虽然也属于客户机/服务器模型的服务,但它更大的意义在于实现了基于Telnet协议的远程登录,但win7 win10 的telnet服务是默认关闭的,要打开 它,在控制面板里,执行“程序”—“打开或关闭Windows功能” 选择Telnet客户端 ,开启即可,然后在命令行就可使用它了

     也可使用Tcping第三方工具,或使用 http://tool.chinaz.com/port/  来检查指定网站指定端口是否开放了

     

     Tcping工具下载:https://elifulkerson.com/projects/tcping.php 

   4) 特别注意 在消息API 接收消息服务器配置及 企业微信调试网页里,填写的URL 网址后面一定不要有空格 或其它字符,有时明明所有都对了,就是不小心在网址最后 多了一个空格,总是校验通不过

       下图的URL 网址最后就是多了一个空格,导致总提示 URL校验失败

       

5.然后再将这个写死的Callback.php 文件的内容改成 官方提供的php.zip的sample.php 的内容,就是用代码根据你提供的参数真正返回明文

    注意相关参数改成你自己的

<?php

include_once "WXBizMsgCrypt.php";

// 假设企业号在公众平台上设置的参数如下
$encodingAesKey = "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C";
$token = "QDG6eK";
$corpId = "wx***********6c7";

/*
------------使用示例一:验证回调URL---------------
*企业开启回调模式时,企业号会向验证url发送一个get请求 
假设点击验证时,企业收到类似请求:
* GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3&timestamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D 
* HTTP/1.1 Host: qy.weixin.qq.com

接收到该请求时,企业应
1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及公众平台推送过来的随机加密字符串(echostr),
这一步注意作URL解码。
2.验证消息体签名的正确性 
3. 解密出echostr原文,将原文当作Get请求的response,返回给公众平台
第2,3步可以用公众平台提供的库函数VerifyURL来实现。

*/

// $sVerifyMsgSig = HttpUtils.ParseUrl("msg_signature");
$sVerifyMsgSig = "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3";
// $sVerifyTimeStamp = HttpUtils.ParseUrl("timestamp");
$sVerifyTimeStamp = "1409659589";
// $sVerifyNonce = HttpUtils.ParseUrl("nonce");
$sVerifyNonce = "263014780";
// $sVerifyEchoStr = HttpUtils.ParseUrl("echostr");
$sVerifyEchoStr = "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==";

// 需要返回的明文
$sEchoStr = "";

$wxcpt = new WXBizMsgCrypt($token, $encodingAesKey, $corpId);
$errCode = $wxcpt->VerifyURL($sVerifyMsgSig, $sVerifyTimeStamp, $sVerifyNonce, $sVerifyEchoStr, $sEchoStr);
if ($errCode == 0) {
	//
	// 验证URL成功,将sEchoStr返回
	// HttpUtils.SetResponce($sEchoStr);
} else {
	print("ERR: " . $errCode . "\n\n");
}


          

6.正常如果第4步是正确的,第5步就没有什么问题。如果有问题,可以将传递过来的参数或计算的中间值写到一个文件文件中作为日志来调试

   如在里面加一些写到日志文件的代码   ,这些代码让我很快就发现了问题所在。节约了大量的调试时间:

   

$myfile = fopen("日志文件.txt", "w") or die("Unable to open file!");

fwrite($myfile, "sVerifyMsgSig: " .$sVerifyMsgSig . "\r\n" );
fwrite($myfile, "sVerifyTimeStamp: " .$sVerifyTimeStamp . "\r\n" );
fwrite($myfile, "sVerifyNonce: " .$sVerifyNonce. "\r\n" );
fwrite($myfile, "sVerifyEchoStr: " .$sVerifyEchoStr . "\r\n" );

fwrite($myfile, "errCode: " .$errCode . "\r\n" );
fwrite($myfile, "sEchoStr: " .$sEchoStr . "\r\n" );
fwrite($myfile, "sEchoStr: " .strlen($sEchoStr) . "\r\n" );
fclose($myfile);

  如果 还是出错,可以将这些日志文件里获取的参数,再拼接成一个URL 直接在网址中 执行看看返回的值对不对

  如我的拼接成这样: 

  https://www.office-cn.net/callback.php?msg_signature=5c06f07ec8745da8c550ec54e0f503b9084752f0&timestamp=1502547531&nonce=209465957&echostr=Wd/nvLNvMCOpcWN6HO5NM99HP3eeUCSWGXmZnO5KFCCNIvMUuvjLwmtxzcpffQYh1Gp7tjljidXd9WJkbIosOw==

  

7.测试OK后,再打开企业应用,自建应用里,你的应用,接收消息,API接收


    

      启用API接收,然后设置 接收消息服务器配置

   


    保存即可,一般是成功的了,如果还是提示 回调URL校验失败,则按上面的步骤再逐个排查


经验总结:


把你接收到的echostr与你要返回给企业号的echostr都打印到日志文件里,然后做以下检查:

1. 检查返回的echostr有没有被加上换行符?

2. 检查返回的echostr有没有被加上html标签?

3. 检查返回的echostr有没有被加上不可见的字符?

4. 检查返回的echostr有没有被加上引号?

5. php开发者还需要检查返回的echostr有没有带上bom头?

6. 如果 sVerifyEchoStr: bCLSV************T6GilQRmCW wli1RYIjpH3uDhRsALel3/Fbme4pQiBLHwOadrB9QCsF86o3J8bY4Q==

    中间内容有空格,则解密的明文 不正确且显示 -40001 错误,如果中间内容没有空格,则解密的明文是正确的

    解决:这个参数不要使用UrlDecode解密。使用URL转码反而不行


7.  HTTP ERROR 500
    说明网址本身代码或引用的文件丢失。先直接打开这个网址看看是否正确


8. 有一种情况明明用 写日志文件方式发现返回的明文是123456789 是正确的,但还是会提示这个错误:

    {"errcode":70002,"errmsg":"echostr校验失败,请您检查是否正确解密并输出明文echostr "}

    原来是因为 callback_valid.php 调用了 helper.php文件,而我在helper.php文件的以下代码中为测试加一 print 的代码

    结果就导致 整个 callback_valid.php 会输入  print("config: " . get_php_file("../config.php") . "\n\n"); 再加真正的返回值  123456789

    这样就导致 与实际要求的值  123456789 不相等。 只要把这个函数中测试的 Print代码注释掉就可以了。

   function loadConfig(){
         print("config: " . get_php_file("../config.php") . "\n\n");
          return json_decode(get_php_file("../config.php"));
   }


9. 为保证 https://www.test.com/callback.php?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3&timestamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ

   可将  http://qydev.weixin.qq.com/debug 里的 EchoStr 设置为很短:如123

    尽量避免 &echostr= 这个参数里 没有空格 或 + 符号。否则不好用直接打开网址的方式来测试,会返回-40001错误


10.重点

   1.要注意将PHP另存为无BOM头的UTF-8格式

    2 是 EchoStr参数获取不要加  urldecode转换 (要命的是官方提供的示例就加了url转换,所以出错)

      即改成  $sVerifyEchoStr = $_GET["echostr"];  //$sVerifyEchoStr = urldecode($_GET["echostr"]);

    3.注意URL网址 后面不能有空格
    4.注意 PHP网页中 前面调试 OK后,到最后一步时,要把前面所有的 echo 或 print 代码去掉,否则即使最后的 echo  $sEchoStr 是正确的

       但因为前面有一些 调试的代码: echo "取得token成功" , 结果 总的返回值 为  取得token成功 及 $sEchoStr 从而企业微信判断明文不符,所以在最后一步调试时,要将前面所有调试的echo 代码注释 掉或删除




另也可尝试其它一些经验:

1.应该返回整形echo (int)$sEchoStr,非字符串型。

   另外,其他文件errorCode.php, pkcs7Encoder.php, sha1.php, xmlparse.php,不要去修改
2.第一次解决是另存为ANSI格式,但不太理想,因为有时还是要用UTF8
   现在找到原因后改成成无bom的UTF8格式就可以了 

3.在返回结果前,加上 header("Content-Type:text/html; charset=utf-8");

   header("Content-Type:text/html; charset=utf-8");
        echo $sEchoStr;

4.mcrypt没装对,mcrypt没加载的话解密一定出错,echostr自然也算不出. 最好下载并使用官方最新的加解密库。


5. 问题已经解决。错误原因是我在request参数时,听网上例子,加了urldecode,直接去掉即可。
   附加代码如下:
int ret = 0;
        string sReqMsgSig = context.Request.QueryString["msg_signature"];
        string sReqTimeStamp = context.Request.QueryString["timestamp"];
        string sReqNonce = context.Request.QueryString["nonce"];
        // 获取Post请求的密文数据
        StreamReader reader = new StreamReader(context.Request.InputStream, Encoding.GetEncoding("UTF-8"));
        string sReqData = reader.ReadToEnd();
        reader.Close();
        string sMsg = "";  // 解析之后的明文
        ret = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, sReqData, ref sMsg);

6. 使用接口EchoStr字符不要超过23个,超过了就会echostr校验失败。


如果是新浪云SAE

1.你用的是SAE的服务器吗?是否实名审核通过。
2.下载的微信接口代码有问题(我从官网下载的代码就一直验证失败,换了一个就好了)。
3.在输出 $echoStr之前加上header('content-type:text');




另附件fans.net asp.net 相关的 代码


public class CheckSignature
    {
        public static bool Check(string token, string sVerifyMsgSig, string sVerifyTimeStamp, string sVerifyNonce, string corpId, string encodingAESKey, string echostr, ref string retEchostr)
        {
            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, corpId);
            int result = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce, echostr, ref retEchostr);
            if (result != 0)
            {
                System.Console.WriteLine("ERR: VerifyURL fail, ret: " + result);
                return false;
            }
            return true;
            //ret==0表示验证成功,retEchostr参数表示明文,用户需要将retEchostr作为get请求的返回参数,返回给企业号。
            // HttpUtils.SetResponse(retEchostr);
        }

        public static bool DecryptMsg(string token, string encodingAESKey, string corpId, string sReqMsgSig, string sReqTimeStamp, string sReqNonce, string sReqData, ref string sMsg)
        {
            int ret = 0;
            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, corpId);
            ret = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, sReqData, ref sMsg);
            if (ret != 0)
            {
                System.Console.WriteLine("ERR: Decrypt Fail, ret: " + ret);
                return false;
            }
            return true;
        }
    }


我与Fans.net为这个回调来来回回测试了很多天,有些问题是只有他遇到的,有些问题是只有我遇到的,但很多人都遇到大大小小不同的坑,Fans的总结就是:企业微信开发太多坑了,小心翼翼地躲开一个个小坑,以便顺利地掉入一个大坑。