微信第三方应用的授权设计与实现(component_verify_ticket、component_access_token、pre_auth_code)
1、查看网址:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Before_Develop/creat_token.html 。微信服务器 每隔 10 分钟会向第三方的消息接收地址推送一次 component_verify_ticket,用于获取第三方平台接口调用凭据。在接口:接收验证票据(接收微信服务器推送) 实现中,获取与缓存第三方平台令牌、获取与缓存第三方平台预授权码。
// 解密成功 if ($errCode === 0) { $ticketData = simplexml_load_string($msg, 'SimpleXMLElement', LIBXML_NOCDATA); if (isset($ticketData->ComponentVerifyTicket)) { $redisCommandkeyPrefix = Yii::$app->params['redisCommand']['keyPrefix']; $componentVerifyTicketKey = $redisCommandkeyPrefix . 'component_verify_ticket:' . $ticketData->AppId; Yii::$app->redis->set($componentVerifyTicketKey, $ticketData->ComponentVerifyTicket); // 获取第三方平台令牌 WxOpenAuthService::getComponentAccessToken($appId, $appSecret); // 获取第三方平台预授权码 WxOpenAuthService::getComponentPreAuthCode($appId); } }
2、获取第三方平台令牌的实现中,参考网址:https://www.shuijingwanwq.com/2021/07/05/5020/ 。代码如下
/** * 获取第三方平台令牌 * * @param $appId string 第三方平台 appId * @param $appSecret string 第三方平台 appSecret * @return bool * @throws ServerErrorHttpException * @throws NotFoundHttpException */ public static function getComponentAccessToken($appId, $appSecret) { $time = time(); $redis = Yii::$app->redis; $redisCommandkeyPrefix = Yii::$app->params['redisCommand']['keyPrefix']; $componentVerifyTicketKey = $redisCommandkeyPrefix . 'component_verify_ticket:' . $appId; $componentVerifyTicket = $redis->get($componentVerifyTicketKey); if (!$componentVerifyTicket) { throw new NotFoundHttpException(Yii::t('error', 205059), 205059); } $componentAccessTokenKey = $redisCommandkeyPrefix . 'component_access_token:' . $appId; // 获取 Redis 中的 component_access_token $componentAccessToken = $redis->get($componentAccessTokenKey); // 判断 Redis 中的 component_access_token 是否存在 if ($componentAccessToken) { $componentAccessToken = unserialize($componentAccessToken); } if (!$componentAccessToken || (($componentAccessToken['expires_at'] - 900) < $time) ) { $data = [ 'component_appid' => $appId, 'component_appsecret' => $appSecret, 'component_verify_ticket' => $componentVerifyTicket, ]; $httpWxAuthAccessToken = new HttpWxAuthAccessToken(); // http 请求获取 component_access_token $httpWxAuthAccessTokenResult = $httpWxAuthAccessToken->wxComponentAccessToken($data); if ($httpWxAuthAccessTokenResult === false) { if ($httpWxAuthAccessToken->hasErrors()) { $firstError = ''; foreach ($httpWxAuthAccessToken->getFirstErrors() as $message) { $firstError = $message; break; } throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '205058'), ['first_error' => $firstError])), 205058); } elseif (!$httpWxAuthAccessToken->hasErrors()) { throw new ServerErrorHttpException('WeChat third-party platform HTTP requests fail for unknown reasons!'); } } // 保存 第三方平台 access_token、有效截止时间 到 Redis $componentAccessToken = [ 'component_access_token' => $httpWxAuthAccessTokenResult['data']['component_access_token'], 'expires_at' => $time + $httpWxAuthAccessTokenResult['data']['expires_in'], ]; $redis->set($componentAccessTokenKey, serialize($componentAccessToken)); file_put_contents(Yii::getAlias('@runtime') . '/frontend-services-WxOpenAuthService-componentAccessToken-' . microtime(true) . '-' . mt_rand() . '.txt', print_r($componentAccessToken, true), FILE_APPEND | LOCK_EX); } return true; }
3、获取第三方平台预授权码的实现中,与获取第三方平台令牌的实现类似。代码如下
/** * 获取第三方平台预授权码 * * @param $appId string 第三方平台 appId * @return bool * @throws ServerErrorHttpException * @throws NotFoundHttpException */ public static function getComponentPreAuthCode($appId) { $time = time(); $redis = Yii::$app->redis; $redisCommandkeyPrefix = Yii::$app->params['redisCommand']['keyPrefix']; $componentAccessTokenKey = $redisCommandkeyPrefix . 'component_access_token:' . $appId; $componentAccessToken = $redis->get($componentAccessTokenKey); if (!$componentAccessToken) { throw new NotFoundHttpException(Yii::t('error', 214017), 214017); } $componentAccessToken = unserialize($componentAccessToken); $componentPreAuthCodeKey = $redisCommandkeyPrefix . 'component_pre_auth_code:' . $appId; // 获取 Redis 中的 component_pre_auth_code $componentPreAuthCode = $redis->get($componentPreAuthCodeKey); // 判断 Redis 中的 component_pre_auth_code 是否存在 if ($componentPreAuthCode) { $componentPreAuthCode = unserialize($componentPreAuthCode); } if (!$componentPreAuthCode || (($componentPreAuthCode['expires_at'] - 900) < $time) ) { $data = [ 'component_appid' => $appId, ]; $httpWxAuthAccessToken = new HttpWxAuthAccessToken(); // http 请求获取 pre_auth_code $httpWxAuthAccessTokenResult = $httpWxAuthAccessToken->wxComponentPreAuthCode($data, $componentAccessToken['component_access_token']); if ($httpWxAuthAccessTokenResult === false) { if ($httpWxAuthAccessToken->hasErrors()) { $firstError = ''; foreach ($httpWxAuthAccessToken->getFirstErrors() as $message) { $firstError = $message; break; } throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '217004'), ['first_error' => $firstError])), 217004); } elseif (!$httpWxAuthAccessToken->hasErrors()) { throw new ServerErrorHttpException('WeChat third-party platform HTTP requests fail for unknown reasons!'); } } // 保存 第三方平台 pre_auth_code、有效截止时间 到 Redis $componentPreAuthCode = [ 'pre_auth_code' => $httpWxAuthAccessTokenResult['data']['pre_auth_code'], 'expires_at' => $time + $httpWxAuthAccessTokenResult['data']['expires_in'], ]; $redis->set($componentPreAuthCodeKey, serialize($componentPreAuthCode)); file_put_contents(Yii::getAlias('@runtime') . '/frontend-services-WxOpenAuthService-componentPreAuthCode-' . microtime(true) . '-' . mt_rand() . '.txt', print_r($componentPreAuthCode, true), FILE_APPEND | LOCK_EX); } return true; }
4、HTTP 模型代码如下
/** * HTTP请求,微信第三方平台通过 component_verify_ticket 获取第三方平台的 component_access_token * @param array $data 数据 * 格式如下: * * [ * 'component_appid' => 'wxd7f67f1792c6e238', // 第三方平台 appId * 'component_appsecret' => '9853ba602cae7a31b4dacd7978ad75c6', // 第三方平台 appSecret * 'component_verify_ticket' => 'ticket@@@wM91oELf8K9_3g8QCXJ9gkPXs6JN2AZjNjI7JEDhFL9MEWi00eEMIqqhPH338OFnwJ5cpjksZUGWYTQXqjsLMw', // 微信后台推送的 ticket * ] * * @return array|false * * 响应格式如下: * * [ * 'component_access_token' => '20_qD79ll9kFdMh3--X43qS-Tw8E04cuFEnoAUrbldWodKbFVg11VtlpJrRXVuVMs7fsVyEXbLByWT__P0o9-lCCWw0rsxD3yxFT3skdxsznhsMBrAKxlhOlRvbUWBsZMJ57oTLSnCMlWtdDv8tGMKjAJAYEC', // 第三方平台 access_token * 'expires_in' => 7200, // 有效期,单位:秒 * ] * * 失败(将错误保存在 [[yii\base\Model::errors]] 属性中) * false * * @throws ServerErrorHttpException 如果响应状态码不等于20x */ public function wxComponentAccessToken($data) { $response = Yii::$app->wxAuthHttp->createRequest() ->setMethod('post') ->setFormat('json') ->setUrl('cgi-bin/component/api_component_token') ->setData($data) ->send(); // 检查响应状态码是否等于20x if ($response->isOk) { // 检查业务逻辑是否成功 if (!isset($response->data['errcode'])) { $responseData = ['message' => '', 'data' => $response->data]; return $responseData; } else { $this->addError('id', $response->data['errmsg']); return false; } } else { throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '202227'), ['status_code' => $response->statusCode, 'error_code' => $response->data['errcode'], 'error' => $response->data['errmsg']])), 202227); } } /** * HTTP请求,微信第三方平台通过 component_access_token 获取第三方平台的预授权码 * @param array $data 请求数据 * 格式如下: * [ * 'component_appid' => 'wxd7f67f1792c6e238', // 第三方平台 appId * ] * @param string $componentAccessToken 第三方平台的 component_access_token * @return array|false * * 响应格式如下: * * [ * 'pre_auth_code' => 'preauthcode@@@IexVwWK9bIkK-0pEd8plLnza0O8oalvXz1JWah5nfaHBJ0CN8Z8Kucu8rX2yA_4l', // 预授权码 * 'expires_in' => 1800, // 有效期,单位:秒 * ] * * 失败(将错误保存在 [[yii\base\Model::errors]] 属性中) * false * @throws ServerErrorHttpException */ public function wxComponentPreAuthCode($data, $componentAccessToken) { $response = Yii::$app->wxAuthHttp->createRequest() ->setMethod('post') ->setFormat('json') ->setUrl('cgi-bin/component/api_create_preauthcode?component_access_token=' . $componentAccessToken) ->setData($data) ->send(); // 检查响应状态码是否等于20x if ($response->isOk) { // 检查业务逻辑是否成功 if (!isset($response->data['errcode'])) { $responseData = ['message' => '', 'data' => $response->data]; return $responseData; } else { $this->addError('id', $response->data['errmsg']); return false; } } else { throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '202228'), ['status_code' => $response->statusCode, 'error_code' => $response->data['errcode'], 'error' => $response->data['errmsg']])), 202228); } }
5、最终的结果,查看生成的日志文件。http 请求获取 component_access_token 平均间隔时间:1小时50分钟。http 请求获取 http 请求获取 pre_auth_code 平均间隔时间:20分钟。此方案的缺陷在于:一旦 component_verify_ticket 接收失败(如微信推送 component_verify_ticket 的服务器发生故障),则微信的 component_access_token、pre_auth_code 皆会过期,无法获取到最新值。因为仅有此入口为更新触发点。后续计划实现2个定时命令(间隔10分钟运行一次),分别基于 component_verify_ticket 更新 component_access_token、基于 component_access_token 更新 pre_auth_code。避免出现因为 component_verify_ticket 接收失败而无法更新 component_access_token、pre_auth_code 的情况。官方建议。如图1
6、查看 Redis 中的缓存数据。如图2
7、查看生成的日志文件。以确认 http 请求的时间间隔。符合预期。http 请求获取 component_access_token 平均间隔时间:(21 09:39 – 20 18:55) / 8 = 110.5 分钟。http 请求获取 component_access_token 最大间隔时间:(20 22:39 – 20 20:44) = 115 分钟(小于过期时间 120 分钟)。http 请求获取 pre_auth_code 平均间隔时间:(21 09:59 – 20 18:55) / 45 = 20 分钟。http 请求获取 pre_auth_code 最大间隔时间:(20 21:59 – 20 21:34) = 25 分钟(小于过期时间 30 分钟)。
[root@api-64796bf684-4dk5b runtime]# ls -lrt -rw-r--r-- 1 nginx nginx 231 Jul 20 18:55 frontend-services-WxOpenAuthService-componentAccessToken-1626778505.5976-2090872825.txt -rw-r--r-- 1 nginx nginx 231 Jul 20 20:44 frontend-services-WxOpenAuthService-componentAccessToken-1626785088.6584-1698188199.txt -rw-r--r-- 1 nginx nginx 231 Jul 20 22:39 frontend-services-WxOpenAuthService-componentAccessToken-1626791942.0405-1205529613.txt -rw-r--r-- 1 nginx nginx 231 Jul 21 00:24 frontend-services-WxOpenAuthService-componentAccessToken-1626798260.5317-841190668.txt -rw-r--r-- 1 nginx nginx 231 Jul 21 02:14 frontend-services-WxOpenAuthService-componentAccessToken-1626804844.4907-2046370607.txt -rw-r--r-- 1 nginx nginx 231 Jul 21 04:04 frontend-services-WxOpenAuthService-componentAccessToken-1626811446.0864-1972564751.txt -rw-r--r-- 1 nginx nginx 231 Jul 21 05:54 frontend-services-WxOpenAuthService-componentAccessToken-1626818067.4014-922194254.txt -rw-r--r-- 1 nginx nginx 231 Jul 21 07:45 frontend-services-WxOpenAuthService-componentAccessToken-1626824703.4927-671650863.txt -rw-r--r-- 1 nginx nginx 231 Jul 21 09:39 frontend-services-WxOpenAuthService-componentAccessToken-1626831557.828-269483974.txt [root@api-64796bf684-4dk5b runtime]#
[root@api-64796bf684-4dk5b runtime]# ls -lrt -rw-r--r-- 1 nginx nginx 165 Jul 20 18:55 frontend-services-WxOpenAuthService-componentPreAuthCode-1626778505.8489-752113831.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 19:15 frontend-services-WxOpenAuthService-componentPreAuthCode-1626779710.0295-1883392791.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 19:35 frontend-services-WxOpenAuthService-componentPreAuthCode-1626780909.2572-2030512685.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 19:54 frontend-services-WxOpenAuthService-componentPreAuthCode-1626782090.5806-110245625.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 20:14 frontend-services-WxOpenAuthService-componentPreAuthCode-1626783290.1634-865268997.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 20:34 frontend-services-WxOpenAuthService-componentPreAuthCode-1626784491.401-904668390.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 20:54 frontend-services-WxOpenAuthService-componentPreAuthCode-1626785681.1526-312599467.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 21:14 frontend-services-WxOpenAuthService-componentPreAuthCode-1626786884.6617-148824611.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 21:34 frontend-services-WxOpenAuthService-componentPreAuthCode-1626788086.7058-993076861.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 21:59 frontend-services-WxOpenAuthService-componentPreAuthCode-1626789542.8081-1843679886.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 22:19 frontend-services-WxOpenAuthService-componentPreAuthCode-1626790746.9546-2130813835.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 22:39 frontend-services-WxOpenAuthService-componentPreAuthCode-1626791942.3167-1090847874.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 22:54 frontend-services-WxOpenAuthService-componentPreAuthCode-1626792884.6659-1225024664.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 23:14 frontend-services-WxOpenAuthService-componentPreAuthCode-1626794063.5141-313962662.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 23:34 frontend-services-WxOpenAuthService-componentPreAuthCode-1626795259.6539-1501709505.txt -rw-r--r-- 1 nginx nginx 165 Jul 20 23:54 frontend-services-WxOpenAuthService-componentPreAuthCode-1626796457.9117-1972547675.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 00:14 frontend-services-WxOpenAuthService-componentPreAuthCode-1626797660.4519-582262606.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 00:34 frontend-services-WxOpenAuthService-componentPreAuthCode-1626798857.7039-272816531.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 00:54 frontend-services-WxOpenAuthService-componentPreAuthCode-1626800055.7545-1335033710.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 01:14 frontend-services-WxOpenAuthService-componentPreAuthCode-1626801254.9479-933596508.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 01:34 frontend-services-WxOpenAuthService-componentPreAuthCode-1626802448.9506-533002721.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 01:54 frontend-services-WxOpenAuthService-componentPreAuthCode-1626803664.2556-451414973.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 02:14 frontend-services-WxOpenAuthService-componentPreAuthCode-1626804844.7527-849127051.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 02:34 frontend-services-WxOpenAuthService-componentPreAuthCode-1626806043.7185-2047662025.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 02:54 frontend-services-WxOpenAuthService-componentPreAuthCode-1626807243.118-1751995970.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 03:14 frontend-services-WxOpenAuthService-componentPreAuthCode-1626808449.8901-1639170548.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 03:34 frontend-services-WxOpenAuthService-componentPreAuthCode-1626809644.1905-1681815156.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 03:54 frontend-services-WxOpenAuthService-componentPreAuthCode-1626810849.7381-1770515381.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 04:14 frontend-services-WxOpenAuthService-componentPreAuthCode-1626812045.9617-1614735470.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 04:34 frontend-services-WxOpenAuthService-componentPreAuthCode-1626813252.8063-1859536354.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 04:54 frontend-services-WxOpenAuthService-componentPreAuthCode-1626814453.7108-802504726.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 05:14 frontend-services-WxOpenAuthService-componentPreAuthCode-1626815669.4836-1412803818.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 05:34 frontend-services-WxOpenAuthService-componentPreAuthCode-1626816865.4676-278503141.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 05:54 frontend-services-WxOpenAuthService-componentPreAuthCode-1626818067.6856-1726415446.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 06:14 frontend-services-WxOpenAuthService-componentPreAuthCode-1626819272.89-123856324.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 06:34 frontend-services-WxOpenAuthService-componentPreAuthCode-1626820477.3397-694428578.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 06:54 frontend-services-WxOpenAuthService-componentPreAuthCode-1626821679.8346-1999505075.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 07:15 frontend-services-WxOpenAuthService-componentPreAuthCode-1626822907.5068-670400676.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 07:35 frontend-services-WxOpenAuthService-componentPreAuthCode-1626824107.4827-1497989531.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 07:55 frontend-services-WxOpenAuthService-componentPreAuthCode-1626825308.6138-127964288.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 08:19 frontend-services-WxOpenAuthService-componentPreAuthCode-1626826759.7268-2073402842.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 08:38 frontend-services-WxOpenAuthService-componentPreAuthCode-1626827916.6763-1465280404.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 08:59 frontend-services-WxOpenAuthService-componentPreAuthCode-1626829158.7873-2084121660.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 09:18 frontend-services-WxOpenAuthService-componentPreAuthCode-1626830319.8617-1775614952.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 09:39 frontend-services-WxOpenAuthService-componentPreAuthCode-1626831558.0899-1560152765.txt -rw-r--r-- 1 nginx nginx 165 Jul 21 09:59 frontend-services-WxOpenAuthService-componentPreAuthCode-1626832767.0986-1848114078.txt [root@api-64796bf684-4dk5b runtime]#
近期评论