Authorization design and implementation of WeChat third-party applications (component_verify_ticket, component_access_token, pre_auth_code)
1. View the URL:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Before_Develop/creat_token.html. The WeChat server pushes the COMPONENT_VERIFY_TICKET to the message receiving address of the third party every 10 minutes to obtain the third-party platform interface call credentials. In the interface: receiving verification ticket (receiving the WeChat server push), obtain and cache the third-party platform token, obtain and cache the third-party platform pre-authorization code.
// 解密成功
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. In the implementation of the third-party platform token, refer to the website:https://www.shuijingwanwq.com/2021/07/05/5020/. The code is as follows
/**
* 获取第三方平台令牌
*
* @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. In the realization of obtaining the pre-authorization code of the third-party platform, it is similar to the realization of obtaining the token of the third-party platform. The code is as follows
/**
* 获取第三方平台预授权码
*
* @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. The HTTP model code is as follows
/**
* 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. The final result, check the generated log file. HTTP request to obtain COMPONENT_ACCESS_TOKEN Average time between: 1 hour and 50 minutes. HTTP requests to get HTTP requests to get pre_auth_code average interval: 20 minutes. The disadvantage of this scheme is that once the COMPONT_VERIFY_TICKET reception fails (such as the server of WeChat push COMPONENT_VERIFY_TICKET), WeChat’s Component_access_token and pre_auth_code will expire and cannot get the latest value. Because only this entry is the update trigger point. Subsequent plans to implement 2 timing commands (running every 10 minutes at an interval), and update COMPONENT_ACCESS_TOKEN based on COMPONENT_VERIFY_TICKET, and update based on COMPONENT_ACCESS_TOKEN. pre_auth_code. Avoid the failure to update COMPONENT_ACCESS_TOKEN and PRE_AUTH_CODE due to the failure of COMPONENT_VERIFY_TICKET reception. official recommendation. as shown in Figure 1
6. View the cached data in Redis. as shown in Figure 2
7. View the generated log files. to confirm the interval of HTTP requests. in line with expectations. HTTP request to get COMPONENT_ACCESS_TOKEN Average interval time: (21 09:39 – 20 18:55) / 8 = 110.5 minutes. HTTP request to get COMPONENT_ACCESS_TOKEN Maximum interval time: (20 22:39 – 20 20:44) = 115 minutes (less than 120 minutes expiration time). Http request to get pre_auth_code average interval time: (21 09:59 – 20 18:55) / 45 = 20 minutes. HTTP request to get pre_auth_code maximum interval time: (20 21:59 – 20 21:34) = 25 minutes (less than expiration time 30 minutes).
[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]#

