最近需要用到钉钉的自定义机器人做个消息推送,钉钉默认的鉴权方式三种,分别是关键词,签名和 IP 白名单;关键词没有做多考虑放弃了,IP 白名单比较简单,但考虑到测试、生产环境换着来换 IP 比较麻烦,所以还是觉得签名或许好一点。
钉钉文档:https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq,根据文档中的描述,刚开始觉得,emmmm... 好像很简单,结果一搞就出问题。
踩坑一,参数 timestamp
文档描述:当前时间戳,单位是毫秒,与请求调用时间误差不能超过 1 小时,所以在用 PHP 写的时候,单纯使用 time
和 microtime
函数都是不行的,正确的做法是:intval(microtime(true) * 1000)
,否则请求的时候就返回一个无效时间戳
描述...
踩坑二,签名函数 hash_hmac
函数文档:https://www.php.net/manual/zh/function.hash-hmac.php
前三个参数分别是哈希算法,计算数据和密钥,均不能为空,好像没什么问题,第一次加密的时候也的的确确返回了数据,但请求之后接口一直报签名错误...
折腾半小时重新看了 hash_hmac
函数文档,问题出在第四个参数 raw_output
,“设置为 TRUE 输出原始二进制数据, 设置为 FALSE 输出小写 16 进制字符串”,然而默认是 false
,所以计算之后返回的字符串再经过 base64 编码,肯定就错了;
所以正确的签名做法是:urlencode(base64_encode(hash_hmac('sha256', "{$time}\n{$secret}", $secret, true)));
效果图:
完整源码:
- <?php
- $secret = 'SEC065d711a3b14615ae20bbf2ccea4eb86a8e3d6c2b221eeba440d8f3f9b42bf6y'; // 替换自己serert
- $token = 'e6111ebe513727ac288e9bbb6b7d212c2ab34b0666eca9c5d0af88f34d7b2cf16'; // 替换自己access token
- $time = intval(microtime(true) * 1000);
- $sign = urlencode(base64_encode(hash_hmac('sha256', "{$time}\n{$secret}", $secret, true)));
- $api = "https://oapi.dingtalk.com/robot/send?access_token={$token}×tamp={$time}&sign={$sign}";
- $headers = ['Content-Type: application/json;charset=utf-8'];
- $text = <<<TEXT
- ## 莫名博客
- > 嘿嘿哈哈
- 这里是莫名的博客哦!\n
- 
- TEXT;
- $post = [
- 'msgtype' => 'actionCard',
- 'actionCard' => [
- 'title' => date('Y-m-d H:i:s'),
- 'text' => $text,
- 'btnOrientation' => 1,
- 'btns' => [
- [
- 'title' => 'Yes',
- 'actionURL' => 'https://qzone.work',
- ],
- [
- 'title' => 'No',
- 'actionURL' => 'https://www.baidu.com',
- ]
- ],
- ],
- ];
-
- $res = json_decode(getCurl($api, [
- 'post' => json_encode($post),
- 'headers' => $headers,
- ]), true);
-
- print_r($res);
-
-
- function getCurl($url, $opt = [])
- {
- $cookie = '';
- if (is_array($opt['cookie'])) {
- foreach ($opt['cookie'] as $k => $v) {
- $cookie .= $k . '=' . $v . '; ';
- }
- }
-
- $cookie = (mb_substr($cookie, 0, mb_strlen($cookie) - 2));
-
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_COOKIE, $cookie);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_NOBODY, $opt['nobody']);
- curl_setopt($ch, CURLOPT_HEADER, $opt['header'] ?? false);
- curl_setopt($ch, CURLOPT_HTTPHEADER, $opt['headers'] ?? []);
- curl_setopt($ch, CURLOPT_TIMEOUT_MS, $opt['rtime'] ?? 10000);
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $opt['ctime'] ?? 10000);
- curl_setopt($ch, CURLOPT_REFERER, $opt['refer'] ?? 'https://user.qzone.qq.com/');
- curl_setopt($ch, CURLOPT_USERAGENT,
- $opt['UA'] ?? "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");
-
- if (isset($opt['post'])) {
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($opt['post']) ? http_build_query($opt['post']) : $opt['post']);
- }
- if (isset($opt['proxy']) && is_array($opt['proxy'])) {
- curl_setopt($ch, CURLOPT_PROXY, $opt['proxy']['ip']);
- curl_setopt($ch, CURLOPT_PROXYPORT, $opt['proxy']['port']);
- }
- $res = curl_exec($ch);
- $error = curl_error($ch);
- $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- curl_close($ch);
-
- if ($opt['detail']) {
- return ['code' => $code, 'error' => $error, 'response' => $res,];
- }
-
- return $res;
- }