上一篇介绍了如何配置通用开发参数及通过url回调验证,
本篇将通过服务商后台配置关联小程序应用配置和获取第三方凭证及如何配置企业可信IP。
当然上篇配置的回调设置也不会白费,在下方的指令和数据回调会用到。
官方企业微信第三方应用开发的基本流程如下:
获取suite_ticket需要使用SuiteID参数,因为项目使用的是三方应用方式开发,
这里可以通过设置小程序->关联小程序详情。
前面的配置基础信息与应用权限不在介绍,比较简单。
应用主页:必填,用户从企业微信工作台进入应用时将直接跳转到对应主页URL
桌面端独立主页:用户从企业微信桌面端工作台进入应用时跳转的应用页面URL
可信域名:仅支持可信域名内的应用调用OAuth2授权、JSSDK等
安装完成回调域名:用户安装成功后可指定跳转至该域名的链接
业务设置URL:授权企业的管理员可从企业微信后台的应用详情页免登录直接跳转该链接进行应用配置。
数据回调URL:必填,用于正确响应企业微信验证URL的请求,用于接收托管企业微信应用的用户信息、进入应用事件、通讯录变更事件。
指令回调URL:必填,用户正确响应企业微信验证URL的请求,用于接收应用(添加、删除、修改)以及ticket参数等API请求。
Secret是可以生成和改变的,SuiteID是系统生成的。
配置可信域名:先填写网站域名,不要写http或者https前缀,直接是网站域名。
如下:
具体怎么验证可信域名呢,接着点击检验可信域名归属:
需要把这个文件下载后,上传到网站域名下,并且可以访问到;
放好之后,如果验证不成功,可能有缓存,等待几分钟在尝试。
这几项就关于回调的参数, 之前配置过url/token/encodingAESKey直接再粘贴过来即可。
指令回调和数据回调地址可以写上篇完成的回调地址,只是要注意现在的token和EncodingAESKey换成之前调好的token和EncodingAESKey和字符串就行或者也可以把代码中的这两项换成现在新生成的。
这些可以分步配置,最后保存完成后,效果如下图:
如果暂时不知道设置项是什么作用,可以先配置一个,之后用上了再改即可。
设置好应用参数后,把suiteID、secret粘贴一下,在代码中要使用。
这里因为一开始不确定参数情况,所以对接收的所有参数存储到日志中。
首先判断请求方式为post,然后具体进入接收数据环节。
经过多次调试后确定需要接收四个参数内容,其中msg_signature/timestamp/nonce三个可直接通过字段名获取到,比较麻烦的是密文数据,需要使用php://input获取。
内容是一个xml格式的字符串。
内容如下:
public function wxNotify()
{
$obj = new CompanyWxPushService();
$all = request()->all();
writeRecordLog('companyWechat.log', request()->method());
writeRecordLog('companyWechat.log', var_export($all, true));
if (request()->isMethod('POST')) {
$msg_signature = request()->input('msg_signature');
$timeStamp = request()->input('timestamp');
$nonce = request()->input('nonce');
// post请求的密文数据
$sReqData = file_get_contents('php://input');
echo $obj->callbackPOST($msg_signature, $timeStamp, $nonce, $sReqData);
} die();
}
下面的处理中思路是:收到post请求之后
1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce);
2.验证消息体签名的正确性。
3.将post请求的数据进行xml解析,并将
解密出来的明文是xml格式的字符串需要转变为xml对象。
4.判断对象的infotype获取suite_ticket并进行存储,之后调用access_token需要使用。
内容如下:
class CompanyWxPushService
{
// 接收信息时的加解密参数
protected static $encodingAesKey = "随机生成的encodingAESKey";
// 接收信息时的校验Token
protected static $token = "随机生成的token";
protected static $corpId = "服务商注册后获取的corpID";
protected static $SuiteID = '小程序应用详情获取的suiteID';
protected static $Secret = '小程序应用详情获取的secret';
/**
* 企业微信post回调解析
* @param $sReqMsgSig
* @param $sReqTimeStamp
* @param $sReqNonce
* @param $sReqData
* @return string
*/
public function callbackPOST($sReqMsgSig, $sReqTimeStamp, $sReqNonce, $sReqData)
{
$sMsg = ""; // 解析之后的明文
$wxcpt = new \WXBizMsgCrypt(self::$token, self::$encodingAesKey, self::$SuiteID);
$errCode = $wxcpt->DecryptMsg($sReqMsgSig, $sReqTimeStamp, $sReqNonce, $sReqData, $sMsg);
writeRecordLog('companyWechat.log', $errCode);
if ($errCode == 0) {
// 解密成功,sMsg即为xml格式的明文
writeRecordLog('companyWechat.log', "解密成功:\r\n". var_export($sMsg, true));
// TODO: 对明文的处理
// 解析该xml字符串,利用simpleXML
libxml_disable_entity_loader(true);
//禁止xml实体解析,防止xml注入
$xml = simplexml_load_string($sMsg, 'SimpleXMLElement', LIBXML_NOCDATA);
switch ($xml->InfoType) {
case 'suite_ticket': // 存储suite_ticket
$log_desc = '存储suite_ticket:' . $xml->SuiteTicket;
self::$redisWechat->setCompanyTicket($xml->SuiteTicket);
break;
default:
$log_desc = '未知的类型:' . $xml->InfoType;
break;
}
writeRecordLog('companyWechat.log', '解析日志:' . $log_desc);
return 'success';
} else {
return "ERR: " . $errCode . "\n\n";
}
}
}
企业微信服务器会定时(每十分钟)向指令回调 URL 推送 suite_ticket,在指令回调的后台逻辑中解密消息体即可得到对应的 InfoType 和 SuiteTicket 。
不同类型的指令回调会通过不同的 InfoType 进行区分,在每次解密得到 suite_ticket 后,应在服务端临时缓存起来。
也可通过刷新ticket按钮来多次快速获取验证解析。
日志情况如下:
获取suite_access_token即为获取第三方凭证,这个在整个开发中很重要,接下来的几个接口都需要三方凭证;在成功接受并且缓存 suite_ticket 之后,我们可以主动来获取 suite_access_token。获取suite_access_token时,需要 suite_id,suite_secret suite_ticket 作为参数。
POST
https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token
参数 |
是否必须 |
说明 |
suite_id |
是 |
第三方应用id或者代开发应用模板id。第三方应用以ww或wx开头应用id(对应于旧的以tj开头的套件id);代开发应用以dk开头 |
suite_secret |
是 |
第三方应用secret 或者代开发应用模板secret |
suite_ticket |
是 |
企业微信后台推送的ticket,即回调中获取的ticket |
暂时先写一个请求,看一下返回内容。
/**
* 获取企业微信 suite_access_token
*/
public function companyAccessToken()
{
$obj = new CompanyWxPushService();
print_r($obj->companyAccessToken());die;
}
需要三方应用SuiteID和Secret参数和之前回调返回中解密后获取的suite_ticket。
Suite_ticket这一步是通过回调获取的suite_ticket内容,设置的redis缓存。
/**
* 获取企业微信 suite_access_token
* @return bool|string
*/
public function companyAccessToken()
{
$url = "https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token";
$params = [
'suite_id' => self::$SuiteID,
'suite_secret' => self::$Secret,
'suite_ticket' => self::$redisWechat->getCompanyTicket()
];
return $this->linkCurl($url, 'POST', $params);
}
/**
* 请求接口返回内容
* @param $url : 请求的URL地址
* @param $method : 请求方式POST|GET
* @param bool $params : 请求的参数
* @return bool|string
*/
protected function linkCurl($url, $method, $params = false)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
if (strpos("$" . $url, "https://") == 1) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
if ($method == "POST") {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json($params));
} else if ($params) {
curl_setopt($ch, CURLOPT_URL, $url . '?' . http_build_query($params));
}
$response = curl_exec($ch);
if ($response === FALSE) return false;
curl_close($ch);
return $response;
}
响应:
{"errcode":60020,"errmsg":"not allow to access from your ip:client ip 123......."}
怀疑是没注册应用和配置企业可信ip。
响应60020是未设置IP白名单,可查看下方第三方应用企业可信IP设置。
设置后,再次请求返回正常。
{"suite_access_token":"tpBubbmIlo_N5zoCKXvz_9q6IanamtrZMB2rBI_Mnh48m9Fqwr7dwak3QZZPJA5O7KBQ_736ks3yogBT5djZerXrajnDUk9hsvP2xGVMnJSq1jPKDvlzIbayUoIX3lCr","expires_in":7200}
参数 |
是否必须 |
说明 |
suite_id |
是 |
第三方应用id或者代开发应用模板id。第三方应用以ww或wx开头应用id(对应于旧的以tj开头的套件id);代开发应用以dk开头 |
suite_secret |
是 |
第三方应用secret 或者代开发应用模板secret |
suite_ticket |
是 |
企业微信后台推送的ticket |
第三方应用或待开发应用需要在服务商后台->服务商信息->基本信息
中设置ip白名单;1分钟后生效。
注意:一般员工账号看不到,需要切换管理员账号。
通过第三方应用开发流程可以知道获取第三方凭证suite_access_token的原因,在之后的API中是一个很重要的参数;在获取的途中,因为不熟悉遇到设置白名单的问题,权限是普通权限看不到服务商功能,之后才发现是权限问题,希望其他开发者不会有这个困惑。