微信第三方应用的授权设计与实现(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,用于获取第三方平台接口调用凭据。在接口:接收验证票据(接收微信服务器推送) 实现中,获取与缓存第三方平台令牌、获取与缓存第三方平台预授权码。
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 解密成功 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/ 。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | /** * 获取第三方平台令牌 * * @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、获取第三方平台预授权码的实现中,与获取第三方平台令牌的实现类似。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | /** * 获取第三方平台预授权码 * * @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 模型代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | /** * 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 分钟)。
1 2 3 4 5 6 7 8 9 10 11 | [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]# |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | [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]# |
近期评论