百度云API鉴权总结

最近在研究百度云的一些服务,处理api接口鉴权时花了不少时间,总结一下,方便大家对接:
废话不说,直接上代码

  • Signer.php:签名工具类,鉴权签名的核心方法都在这里
  • Utils.php:封装的工具类,鉴权,返回json数据等都在这里
  • Account.php:示例Controller,请求百度云接口

使用的tp5框架,代码仅供参考,思路可以供大家借鉴,如有不当之处,欢迎指正

签名工具类
Signer.php

<?php

class SignerException extends Exception
{
    function __construct($message)
    {
        parent::__construct($message, 999);
    }
}

class Signer
{
    private $ak;
    private $sk;
    private $version = "1";
    private $timestamp;
    private $expiration = 1800;
    private $method;
    private $uri;
    private $params = array();
    private $headers = array();
    private $needLog = false;

    function __construct($accessKey, $secretKey)
    {
        $this->ak = $accessKey;
        $this->sk = $secretKey;
        $date = new DateTime('now');
        $date->setTimezone(new DateTimeZone('UTC'));
        $this->timestamp = $date->format('Y-m-d\TH:i:s\Z');
    }

    public function setVersion($version)
    {
        $this->version = $version;
    }

    public function setExpiration($expiration)
    {
        $this->expiration = $expiration;
    }

    public function setMethod($method)
    {
        if (!empty($method)) {
            $this->method = strtoupper($method);
        }
    }

    public function setTimestamp($timestamp)
    {
        $this->timestamp = $timestamp;
    }

    public function setUri($uri)
    {
        $this->uri = $uri;
    }

    public function setParams($params)
    {
        $this->params = $this->normalizeParam($params);
    }

    public function setSignHeaders($headers)
    {
        $this->headers = $this->normalizeHeaders($headers);
    }

    public function beLog($needLog)
    {
        $this->needLog = $needLog;
    }

    public function genAuthorization()
    {
        $signature = $this->genSignature();
        $authStr = "bce-auth-v" . $this->version . "/" .
            $this->ak . "/" . $this->timestamp . "/" .
            $this->expiration . "/" . $this->getSignedHeaderNames() . "/" . $signature;
        return $authStr;
    }

    public function genSignature()
    {
        if (empty($this->method)) {
            throw new SignerException("method is null or empty");
        }
        $signingKey = $this->genSigningKey();
        $this->signerLog("signingKey:" . $signingKey, __LINE__, __FILE__);
        $authStr = $this->method . "\n" .
            $this->getCanonicalURI() . "\n" .
            $this->getCanonicalParam() . "\n" .
            $this->getCanonicalHeaders();
        $this->signerLog("auth str:" . $authStr, __LINE__, __FILE__);
        return $this->sha256($signingKey, $authStr);
    }

    public function genSigningKey()
    {
        if (empty($this->ak)) {
            throw new SignerException("access key is null or empty");
        }
        if (empty($this->sk)) {
            throw new SignerException("secret key is null or empty");
        }
        if (empty($this->version)) {
            throw new SignerException("version is null or empty");
        }
        if (empty($this->timestamp)) {
            throw new SignerException("timestamp is null or empty");
        }
        if (empty($this->expiration)) {
            throw new SignerException("expiration is null or empty");
        }
        $authStr = "bce-auth-v" . $this->version . "/" . $this->ak . "/" .
            $this->timestamp . "/" . $this->expiration;
        return $this->sha256($this->sk, $authStr);
    }

    public function getCanonicalParam()
    {
        if (empty($this->params)) {
            return "";
        }
        $arryLen = count($this->params);
        $canonicalParams = "";
        foreach ($this->params as $key => $value) {
            if (is_array($value)) {
                $num = count($value);
                if (count($value) == 0) {
                    $canonicalParams = $canonicalParams . $key . "=";
                } else {
                    foreach ($value as $item) {
                        $canonicalParams = $canonicalParams . $key . "=" . $item;
                        if ($num > 1) {
                            $canonicalParams = $canonicalParams . "&";
                            $num--;
                        }
                    }
                }
            } else {
                $canonicalParams = $canonicalParams . $key . "=" . $value;
            }
            if ($arryLen > 1) {
                $canonicalParams = $canonicalParams . "&";
                $arryLen--;
            }
        }
        return $canonicalParams;
    }

    public function getCanonicalURI()
    {
        if (empty($this->uri)) {
            throw new SignerException("uri is null or empty");
        }
        $newUri = $this->dataEncode($this->uri, true);
        if (strpos($newUri, "/") === 0) {
            return $newUri;
        }
        return "/" . $newUri;
    }

    public function getCanonicalHeaders()
    {
        if (empty($this->headers) || !array_key_exists("host", $this->headers)) {
            throw new SignerException("host not in headers");
        }
        $canonicalHeaders = "";
        $strArry = array();
        foreach ($this->headers as $key => $value) {
            if (empty($value)) {
                continue;
            }
            $strArry[] = $this->dataEncode($key, false) . ":" . $value;
        }
        $arryLen = count($strArry);
        for ($i = 0; $i < $arryLen; $i++) {
            if ($i < $arryLen - 1) {
                $canonicalHeaders = $canonicalHeaders . $strArry[$i] . "\n";
                continue;
            }
            $canonicalHeaders = $canonicalHeaders . $strArry[$i];
        }
        return $canonicalHeaders;
    }

    private function sha256($key, $data)
    {
        return hash_hmac('sha256', $data, $key);
    }

    private function dataEncode($data, $isPath)
    {
        if (empty($data)) {
            return "";
        }
        $encode = mb_detect_encoding($data, array("ASCII", "UTF-8", "GB2312", "GBK", "BIG5"));
        if ($encode != "UTF-8") {
            $data = $code1 = mb_convert_encoding($data, 'utf-8', $encode);
        }
        $encodeStr = rawurlencode($data);
        if ($isPath) {
            $encodeStr = str_replace("%2F", "/", $encodeStr);
        }
        return $encodeStr;
    }

    private function normalizeHeaders($headers)
    {
        $newArray = array();
        if (empty($headers)) {
            return $newArray;
        }
        foreach ($headers as $key => $value) {
            $newKey = strtolower($key);
            if (empty($newKey)) {
                continue;
            }
            $newArray[$newKey] = $this->dataEncode(trim($value), false);
        }
        ksort($newArray);
        return $newArray;
    }

    private function normalizeParam($params)
    {
        $newArray = array();
        if (empty($params)) {
            return $newArray;
        }
        foreach ($params as $key => $value) {
            if (empty($key) || strtolower($key) == "authorization") {
                continue;
            }
            if (is_array($value)) {
                $newSubArray = array();
                foreach ($value as $item) {
                    $newSubArray[] = $this->dataEncode($item, false);
                }
                sort($newSubArray);
                $newArray[$this->dataEncode($key, false)] = $newSubArray;
            } else {
                $newArray[$this->dataEncode($key, false)] = $this->dataEncode($value, false);
            }
        }
        ksort($newArray);
        return $newArray;
    }

    private function getSignedHeaderNames()
    {
        $arryLen = count($this->headers);
        $headerNames = "";
        foreach ($this->headers as $key => $value) {
            $headerNames = $headerNames . $key;
            if ($arryLen > 1) {
                $headerNames = $headerNames . ";";
                $arryLen--;
            }
        }
        return $headerNames;
    }

    private function signerLog($content, $line, $file)
    {
        if ($this->needLog) {
            error_log($file . ":" . $line . ":[" . $content . "]\n", 3, "./signer_log");
        }
    }
}

?>

封装的工具类,集成了常用的方法
Utils.php

<?php

/**
 * Created by PhpStorm.
 * User: 王中阳
 * Date: 2019/7/24
 * Time: 9:45
 */
require 'Signer.php';
define('AK', "your ak");
define('SK', "your sk");
define('HOST', "sem.baidubce.com"); //按百度要求换内容
define('BASE_URL', "http://sem.baidubce.com/");//按百度要求换内容
define('HTTP_Method', "POST");//按百度要求换内容

class Utils
{
    //公共header  注意:我对接的是百度信息流推广api,header应根据百度云各服务的要求进行修改
    static function Header()
    {
        return [
            'opUsername' => 'xxxxxxx',
            'opPassword' => 'xxxxxxx',
            'tgUsername' => 'xxxxxxx',
            'tgPassword' => 'xxxxxxx',
            'bceUser' => 'xxxxxxx',
        ];
    }

    //请求百度
    static function curl_post($url, $body, $auth)
    {
        //处理请求体
        $files = [
            'header' => self::Header(),
            'body' => $body
        ];
        $files = json_encode($files, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_URL, $url);


        curl_setopt($ch, CURLOPT_POSTFIELDS, $files);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json',
                'host:sem.baidubce.com',  //改成你的数据
                'authorization:' . $auth,
                'accept-encoding:gzip, deflate',
                'accept:*/*'
            )
        );
        $response = curl_exec($ch);
        $request = curl_getinfo($ch, CURLINFO_HEADER_OUT);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        self::result($httpCode, $response);
    }

    //校验权限
    static function gen_auth($uri = "")
    {
        $headers = array();
        $headers['host'] = HOST;
        $signer = new \Signer(AK, SK);
        $signer->setMethod(HTTP_Method);
        $signer->setUri($uri);
        $signer->setSignHeaders($headers);
        try {
            $signature = $signer->genAuthorization();
            return $signature;
        } catch (SignerException $e) {
            echo $e->getMessage();
            return;
        }
    }

    //返回结果
    static public function result($errno = 0, $data = '')
    {
        header("Content-type: application/json;charset=utf-8");

        $errno = intval($errno);

        //注意:这里可能不满足你的项目 根据百度返回结果做修改 或者不用我这种处理方式
        //json转数组
        $data = json_decode($data, true);
        if (isset($data['header']['failures'][0])) {
            $message = $data['header']['failures'][0]['message'];
        } else {
            $message = 'success';
        }

        $json = json_encode($data['body']['data'][0]);
        $result = array(
            'errno' => 1,
            'message' => $message,
            'data' => json_encode($data['body']['data'][0]),//处理百度 返回结果
        );
        echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

    /**
     * 时间 日期
     */
    static public function ymd($time)
    {
        return date("Y-m-d", $time);
    }
}

业务层controller:百度api在请求接口的同时做权限校验
Account.php

<?php
namespace app\index\controller;

require 'Utils.php';

class Account
{
    /**
     * 获得账号信息
     */
    public function info()
    {
        $uri = "v1/feed/cloud/AccountFeedService/getAccountFeed";
        $auth = \Utils::gen_auth($uri);
        $url = BASE_URL . $uri;

        $body = [
            'accountFeedFields' => [
                'userId',
                'balance',
                'budget',
                'balancePackage',
                'userStat',
                'uaStatus',
                'validFlows',
            ],
        ];
        //返回数据集成在Utils中
        \Utils::curl_post($url, $body, $auth);
    }

思路仅供参考。如有更好的处理方式,欢迎赐教。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339