接入腾讯云 OAuth

86 阅读2分钟

前言

腾讯云文档(自动交付接入):cloud.tencent.com/document/pr… 腾讯云文档(签名方法v3): cloud.tencent.com/document/ap…

起因

业务需求需要接入腾讯云授权,页面授权获取code还是很顺利的。我也很快拿到了code和signature并完成了合法性效验(自动交付接入:步骤4)。但是code效验接入的时候卡了一天半的时间。

解决

(重点): image.png

image.png 这里的公共参数是要放到header头的他们的作用仅仅只是放在header头,根据图1的非必要结合图二就得到header必要参数。

(重点): 这里用的是v3签名方式 第一步 拼接规范请求串的CanonicalHeaders和SignedHeaders这两个参数上面,他们提供的说明是参与签名的头部信息,这个很容易误导成header头,其次他们的结果示例:

`content-type:application/json; charset=utf-8\nhost:cvm.tencentcloudapi.com\nx-tc-action:describeinstances\n`。

其中的x-tc-action也很难不让人误导以为这里需要跟header一样。 其实并不是 这里只需要必填的content-type和host即可。 我的数组:

[
    'Content-Type'=>"application/json",
    'Host'=>"open.tencentcloudapi.com",
];

#上代码 各位老板请吃

namespace app\controller\api\TencentCloud\actions;

class TencentCloudServer
{
    //appid
    public $app_id           = "";
    public $secret_id        = "";
//    protected $secret_id        = "AKIDz8krbsJ5yKBZQpn74WFkmLPx3*******";
    public $secret_key       = "";
//    protected $secret_key       = "Gu5t9xGARNpq86cd98joQYCN3*******";
    public $encry_key        = "";
    //回调url
    public $redirect_url     = "";
    //授权url
    public $url              = "";
    //请求url
    public $sed_url         = "";
    //请求头
    public $headers;
    //第一步需要加签的头部
    public $has_headers;
    //时间戳
    public $timestamp;
    //时间戳日期
    public $timestamp_data;
    //URI 参数 (CanonicalURI)
    public $uri;
    //请求字符串 (CanonicalQueryString)
    public $canonical_query_string;
    //请求方式
    public $method;
    //参与签名的头部信息
    public $canonical_header;
    //参与签名的头部信息的头部
    public $signed_headers;
    //请求体也就是body
    public $payload;
    //接口名称
    public $action;
    //签名方法
    public $algorithm ="TC3-HMAC-SHA256";
    public function __construct($data=[])
    {
        $this->headers          = $data['headers']??"";
        $this->timestamp        = time();
        $this->timestamp_data   = gmdate("Y-m-d", $this->timestamp);
        $this->uri              = $data['uri'] ?? "/";
        $this->canonical_query_string = $data['canonical_query_string'] ??'';
        $this->method           = $data['method'] ??"POST";
        $this->canonical_header = $data['canonical_header'] ??"";
        $this->signed_headers   = $data['signed_headers'] ??"";
        $this->payload          = $data['payload'] ??"";
        $this->action           = $data['action'] ??"";
    }
    public function checkMd5Signature($code, $signature)
    {
        $code_key = $code . $this->encry_key;
        $sing = md5($code_key);
        return $signature === $sing;
    }
    //拼接规范请求串
    protected function getCanonicalRequest() {
        $HTTPRequestMethod = strtoupper($this->method);
        if ($HTTPRequestMethod === "POST") {
            $this->canonical_query_string = "";
        } else {
            $this->canonical_query_string = http_build_query($this->canonical_query_string);
        }
        ksort($this->has_headers);
        foreach ($this->has_headers as $key => $value) {
            $this->canonical_header .= strtolower($key) . ":" . trim($value) . "\n";
            $this->signed_headers .= strtolower($key) . ";";
        }
        $this->signed_headers = rtrim($this->signed_headers, ";");
        $payloadHash = strtolower(hash('sha256', $this->payload));
        $info = implode("\n", [
            $HTTPRequestMethod,
            $this->uri,
            $this->canonical_query_string,
            $this->canonical_header,
            $this->signed_headers,
            $payloadHash
        ]);
        return $info;
    }
    //拼接待签名字符串
    protected function getStringToSign($canonicalRequest) {
        $hashCanonicalRequest = hash('sha256', $canonicalRequest);
        $stringToSign = "TC3-HMAC-SHA256\n";
        $stringToSign .= $this->timestamp . "\n";
        $stringToSign .= $this->timestamp_data . "/" . $this->action . "/tc3_request\n";
        $stringToSign .= $hashCanonicalRequest;
        return $stringToSign;
    }
    protected function sign($key, $msg) {
        return hash_hmac('sha256', $msg, $key, true);
    }
    //计算签名
    protected function getSignature($stringToSign) {
        $secretDate = $this->sign("TC3" . $this->secret_key, $this->timestamp_data);
        $secretService = $this->sign($secretDate, $this->action);
        $secretSigning = $this->sign($secretService, "tc3_request");
        return hash_hmac('sha256', $stringToSign, $secretSigning);
    }
    //拼接 Authorization
    protected function getAuthorization($signature) {
        $credentialScope = $this->timestamp_data . "/" . $this->action . "/tc3_request";
        $auth = $this->algorithm . " ";
        $auth .= "Credential=" . $this->secret_id . "/" . $credentialScope . ", ";
        $auth .= "SignedHeaders=".$this->signed_headers. ", ";
        $auth .= "Signature=" . $signature;
        return $auth;
    }
    public function tencentCloudV3Sign() {
        $canonicalRequest = $this->getCanonicalRequest();
        $stringToSign = $this->getStringToSign($canonicalRequest);
        $signature = $this->getSignature($stringToSign);
        return $this->getAuthorization($signature);
    }
    public function curl()
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->sed_url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $this->payload);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(function ($k, $v) { return "$k: $v"; }, array_keys($this->headers), $this->headers));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        curl_close($ch);
        $response = json_decode($response,true);
        return $response;
    }
}

调用代码

class TencentCloudMarket extends AuthController
{

    public function native()
    {
        Log::channel('tencent_cloud')->info('This is a log message to custom channel');
        return "success";
    }

    public function authUrl()
    {
        $tencentCloud = new TencentCloudServer();
        $query = http_build_query([
            'scope' => 'login',
            'app_id' => $tencentCloud->app_id,
            'redirect_url' =>$tencentCloud->redirect_url,
//            'state' => md5(uniqid(rand(), true)),
        ]);
        return bt_response($tencentCloud->url."?{$query}");
    }

    public function authLogin()
    {
        $params = $this->request->param([
            'code',   //授权code
            'signature',   // 签名
        ], null);
        // 验证参数
        validate([
            'code'    => 'require',
            'signature'    => 'require',
        ], [
            'code.require' => '授权不能为空',
            'signature.require' => '授权不能为空',
        ])->check($params);
        $tencentCloud = new TencentCloudServer();
        //code 的合法性做校验
        $check = $tencentCloud->CheckMd5Signature($params['code'],$params['signature']);
        if (!$check){
            return bt_response('授权登陆失败','true','200');
        }
        //code 的合法性做校验
        $time=time();
        $tencentCloud->server = 'open';
        $tencentCloud->uri = '/';
        $tencentCloud->method = 'POST';
        $tencentCloud->action = 'open';
        $tencentCloud->timestamp = $time;
        $tencentCloud->sed_url = "";
        $tencentCloud->has_headers = [
            'Content-Type'=>"application/json",
            'Host'=>"open.tencentcloudapi.com",
        ];
        $payload = json_encode([
            'UserAuthCode'=>$params['code'],
        ]);
        $tencentCloud->payload = $payload;
        //获取token
        $authorizationHeader = $tencentCloud->tencentCloudV3Sign();
        //设置header头
        $tencentCloud->headers = [
            //注头部必须要空格不然签名直接异常
            'X-TC-Action'=>"GetUserAccessToken",
            'X-TC-Version'=>"2018-12-25",
            'X-TC-Timestamp'=>$time,
            'Authorization'=>$authorizationHeader,
        ];
        //组合header头组成完整的header头部
        $tencentCloud->headers = array_merge($tencentCloud->has_headers,$tencentCloud->headers);
        $res = $tencentCloud->curl();
       var_dump($res);die();
        return bt_response();
    }

收摊.文档千千万不可能维护到每一份,希望这一份对开发人员有帮助。相关问题反馈至腾讯云也是积极响应并做了跟进处理。十分感谢。