【问题标题】:OAuth 1.0 Signature Generation with PHP使用 PHP 生成 OAuth 1.0 签名
【发布时间】:2015-01-14 21:08:26
【问题描述】:

我想为我的自定义标头生成签名以从 Magento 获取访问令牌。 “oauth_signature_method”是“HMAC-SHA1”。我准备了我的基本字符串,但不知道如何生成签名。我应该使用“hash_hmac($algo,$data,$key)”吗?如果是这样,关键应该是什么?任何帮助将不胜感激。

$consumerSecret = "abcdef";
...
$nonce = generateNonce();
$time = time();
...
$base = "POST&http%3A%2F%2Fmydomain.com%2Fmagento%2Foauth%2Ftoken&".
"oauth_consumer_key%3Dc67eee22b81ac3237cc501aa58fdc236%26".
"oauth_nonce%3D".$nonce."%26".
"oauth_signature_method%3DHMAC-SHA1%26".
"oauth_timestamp%3D".$time."%26".
"oauth_version%3D1.0%26".
"oauth_token%3D".$oauthToken."%26".
"oauth_verifier%3D".$oauthVerifier;

【问题讨论】:

    标签: php magento oauth signature sha


    【解决方案1】:

    您可以找到 OAuth 1.0 here 的文档,更准确地说是针对您的问题(子)章节

    • 9.2。 HMAC-SHA1
    • 附录 A.5.1。生成签名基字符串
    • 附录 A.5.2。计算签名值

    根据文档,签名基本字符串形成为: HTTP_METHOD&urlencode(BASE_URL_OF_RESOURCE)&urlencode(PARAMETERS_ORDERED_AND_NORMALIZED_AND_WITHOUT_OAUTHSIGNATURE)

    用于 hasing 的 key 是:
    urlencode(CONSUMER_SECRET)&urlencode(OAUTH_TOKEN_SECRET) - 当您有令牌密码时
    urlencode(CONSUMER_SECRET)& - 当您没有令牌密码时(非常从请求“请求令牌”时开始)

    最后的签名应该是base64_encode(hash_hmac('SHA1', $signatureBaseString, $key, 1));

    我放了一个示例脚本,以便您可以使用/测试/查看它是如何工作的:

        <?php
    // testMagentoRestApi.php file
    error_reporting(E_ALL);
    ini_set('display_errors', 'On');
    
    session_start();
    
    /**
     * @author Bogdan Constantinescu <{first3LettersFromFirstname}_{first3LettersFromLastname}{at}yahoo{dot}com>
     * @license The MIT License (http://opensource.org/licenses/MIT)
     */
    class MagentoRestApi
    {
        /**
         * @var const int       Curl connect timeout.
         */
        const TIMEOUT = 20;
        /**
         * @var string          Application 's key.
         */
        private $appKey;
        /**
         * @var string          Application 's secret.
         */
        private $appSecret;
        /**
         * @var resource        The curl resource.
         */
        protected $curl;
        /**
         * @var string          Base Magento url.
         */
        protected $baseMagentoUrl;
        /**
         * @var string          Consumer 's callback url.
         */
        protected $callbackUrl;
    
    
        /**
         * Constructor; initializes stuffs...
         * @param   string     $strBaseMagentoUrl   Magento 's base url.
         * @param   string     $strAppKey           API application key.
         * @param   string     $strAppSecret        API application secret.
         * @param   string     $strCallbackUrl      Consumer's callback url.
         * @throws  Exception  If params are not ok / curl could not be initialized.
         */
        public function __construct($strBaseMagentoUrl, $strAppKey, $strAppSecret, $strCallbackUrl)
        {
            // check params
            if(!filter_var($strBaseMagentoUrl, FILTER_VALIDATE_URL)) {
                throw new Exception('Invalid param base magento url.');
            }
            if (!is_string($strAppKey) || !mb_strlen($strAppKey)) {
                throw new Exception('Invalid param app key.');
            }
            if (!is_string($strAppSecret) || !mb_strlen($strAppSecret)) {
                throw new Exception('Invalid param app secret.');
            }
            if(!filter_var($strCallbackUrl, FILTER_VALIDATE_URL)) {
                throw new Exception('Invalid param callback url.');
            }
            $this->baseMagentoUrl = trim($strBaseMagentoUrl, '/');
            $this->callbackUrl    = $strCallbackUrl;
            $this->appKey         = $strAppKey;
            $this->appSecret      = $strAppSecret;
    
            if (!extension_loaded('curl')) {
                throw new Exception('cURL extension is not enabled.');
            }
            $this->curl = curl_init();
            if (false === $this->curl) {
                throw new Exception('cURL could not be initialized.');
            }
        }
    
    
        /**
         * Makes api call.
         * @param   string     $strUrl                      Api call url.
         * @param   string     $strMethod                   Request method (POST|GET|DELETE|PUT...)
         * @param   array      $arrHeaders                  Optional request headers.
         * @param   string     $strPostData                 Request body.
         * @param   boolean    $blnSuprimeResponseHeader    Whether to suprime response 's headers or not.
         * @return  string                                  Api call response.
         * @throws  Exception   If smth went wrong.
         */
        protected function makeApiCall(
            $strUrl,
            $strMethod = 'GET',
            array $arrHeaders = array(),
            $strPostData = '',
            $blnSuprimeResponseHeader = false,
            &$intStatus = null
        ) {
            curl_setopt($this->curl, CURLOPT_URL, $strUrl);
            curl_setopt($this->curl, CURLOPT_HTTPHEADER, $arrHeaders);
            curl_setopt($this->curl, CURLOPT_TIMEOUT, self::TIMEOUT);
            curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, self::TIMEOUT);
            curl_setopt($this->curl, CURLOPT_MAXREDIRS, 3);
            curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, 1);
            curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $strMethod);
            curl_setopt($this->curl, CURLOPT_POSTFIELDS, $strPostData);
            curl_setopt($this->curl, CURLOPT_HEADER, !$blnSuprimeResponseHeader);
            curl_setopt($this->curl, CURLOPT_USERAGENT, 'Magento REST API Client');
            curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, 0);
    
            $mxdResponse = curl_exec($this->curl);
            if (false === $mxdResponse) {
                throw new Exception(curl_error($this->curl), curl_errno($this->curl));
            }
            $intStatus = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
            // var_dump($mxdResponse);
            return $mxdResponse;
        }
    
    
        /**
         * Retrieve request token.
         * @return array        With keys 'oauth_token' & 'oauth_token_secret'
         * @throws Exception    If request did not succeed/smth went bad
         */
        public function getRequestToken()
        {
            $returnValue = array();
    
            // define params that will be used either in Authorization header, or as url query params, excluding 'oauth_signature'
            $params = array(
                'oauth_callback' => $this->callbackUrl,
                'oauth_consumer_key' => $this->appKey,
                'oauth_nonce' => uniqid(mt_rand(1, 1000)),
                'oauth_signature_method' => 'HMAC-SHA1',
                'oauth_timestamp' => time(),
                'oauth_version' => '1.0',
            );
            // define HTTP method
            $method = 'POST';
            // this is the url to get Request Token according to Magento doc
            $url = $this->baseMagentoUrl . '/oauth/initiate?oauth_callback=' . urlencode($params['oauth_callback']);
    
            // start making the signature
            ksort($params); // @see Zend_Oauth_Signature_SignatureAbstract::_toByteValueOrderedQueryString() for more accurate sorting, including array params 
            $sortedParamsByKeyEncodedForm = array();
            foreach ($params as $key => $value) {
                $sortedParamsByKeyEncodedForm[] = rawurlencode($key) . '=' . rawurlencode($value);
            }
            $strParams = implode('&', $sortedParamsByKeyEncodedForm);
            $signatureData = strtoupper($method) // HTTP method (POST/GET/PUT/...)
                    . '&'
                    . rawurlencode($this->baseMagentoUrl . '/oauth/initiate') // base resource url - without port & query params & anchors, @see how Zend extracts it in Zend_Oauth_Signature_SignatureAbstract::normaliseBaseSignatureUrl()
                    . '&'
                    . rawurlencode($strParams);
    
            $key = rawurlencode($this->appSecret) . '&'; // on later requests, when you also have a oauth_token_secret from this request append it to $key  ( eq: $key = rawurlencode($this->appSecret) . '&' . rawurlencode($someOauthTokenSecret); )
            $signature = base64_encode(hash_hmac('SHA1', $signatureData, $key, 1));
            // end making signature
    
            $responseStatusCode = 0;
            $response = $this->makeApiCall(
                $url,
                $method,
                array(
                    'Authorization: OAuth '
                    . 'oauth_callback="' . rawurlencode($params['oauth_callback']) . '",'
                    . 'oauth_consumer_key="' . $params['oauth_consumer_key'] . '",'
                    . 'oauth_nonce="' . $params['oauth_nonce'] . '",'
                    . 'oauth_signature_method="' . $params['oauth_signature_method'] . '",'
                    . 'oauth_signature="' . rawurlencode($signature) . '",'
                    . 'oauth_timestamp="' . $params['oauth_timestamp'] . '",'
                    . 'oauth_version="' . $params['oauth_version'] . '"'
                ),
                '',
                true,
                $responseStatusCode
            );
            if (200 == $responseStatusCode) {
                parse_str($response, $returnValue);
            } else {
                throw new Exception('Response HTTP code != 200, but ' . $responseStatusCode);
            }
            return $returnValue;
        }
    
    
        /**
         * Retrieve access token.
         * @param   string     $strOauthToken         token from "Request Token".
         * @param   string     $strOauthTokenSecret   token secret from "Request Token".
         * @param   string     $strOauthVerifier      verifier returened after user authorization.
         * @return array        With keys 'oauth_token' & 'oauth_token_secret'
         * @throws Exception    If request did not succeed/smth went bad
         */
        public function getAccessToken($strOauthToken, $strOauthTokenSecret, $strOauthVerifier)
        {
            $returnValue = array();
    
            // define params that will be used either in Authorization header, or as url query params, excluding 'oauth_signature'
            $params = array(
                'oauth_consumer_key' => $this->appKey,
                'oauth_nonce' => uniqid(mt_rand(1, 1000)),
                'oauth_signature_method' => 'HMAC-SHA1',
                'oauth_timestamp' => time(),
                'oauth_version' => '1.0',
                'oauth_token' => $strOauthToken,
                'oauth_verifier' => $strOauthVerifier,
            );
            // define HTTP method
            $method = 'POST';
            // this is the url to get Request Token according to Magento doc
            $url = $this->baseMagentoUrl . '/oauth/token';
    
            // start making the signature
            ksort($params); // @see Zend_Oauth_Signature_SignatureAbstract::_toByteValueOrderedQueryString() for more accurate sorting, including array params 
            $sortedParamsByKeyEncodedForm = array();
            foreach ($params as $key => $value) {
                $sortedParamsByKeyEncodedForm[] = rawurlencode($key) . '=' . rawurlencode($value);
            }
            $strParams = implode('&', $sortedParamsByKeyEncodedForm);
            $signatureData = strtoupper($method) // HTTP method (POST/GET/PUT/...)
                    . '&'
                    . rawurlencode($url) // base resource url - without port & query params & anchors, @see how Zend extracts it in Zend_Oauth_Signature_SignatureAbstract::normaliseBaseSignatureUrl()
                    . '&'
                    . rawurlencode($strParams);
    
            $key = rawurlencode($this->appSecret) . '&' . rawurlencode($strOauthTokenSecret); 
            $signature = base64_encode(hash_hmac('SHA1', $signatureData, $key, 1));
            // end making signature
    
            $responseStatusCode = 0;
            $response = $this->makeApiCall(
                $url,
                $method,
                array(
                    'Authorization: OAuth '
                    . 'oauth_consumer_key="' . $params['oauth_consumer_key'] . '",'
                    . 'oauth_nonce="' . $params['oauth_nonce'] . '",'
                    . 'oauth_signature_method="' . $params['oauth_signature_method'] . '",'
                    . 'oauth_signature="' . rawurlencode($signature) . '",'
                    . 'oauth_timestamp="' . $params['oauth_timestamp'] . '",'
                    . 'oauth_version="' . $params['oauth_version'] . '",'
                    . 'oauth_token="' . rawurlencode($params['oauth_token']) . '",'
                    . 'oauth_verifier="' . rawurlencode($params['oauth_verifier']) . '"'
                ),
                '',
                true,
                $responseStatusCode
            );
            if (200 == $responseStatusCode) {
                parse_str($response, $returnValue);
            } else {
                throw new Exception('Response HTTP code != 200, but ' . $responseStatusCode);
            }
            return $returnValue;
        }
    
    
        /**
         * Destructor. Free resources.
         */
        public function __destruct() {
            if (is_resource($this->curl)) {
                curl_close($this->curl);
            }
        }
    }
    
    // change the following variables accordingly to your needs
    $callbackUrl = 'http://localhost/testMagentoRestApi.php';
    $baseMagentoUrl = 'http://www.magento.dev/CE_1.9.1.0';
    $consumerKey = 'e93a5396269851aaddaabd86999bafcb';
    $consumerSecret = 'bfb0c10cf75f0b66f71186d806616807';
    
    $client = new MagentoRestApi($baseMagentoUrl, $consumerKey, $consumerSecret, $callbackUrl);
    
    if (!isset($_SESSION['request_token']) && !isset($_GET['oauth_token'])) { // retrieve "Request Token" and Authorize user
        $_SESSION['request_token'] = $client->getRequestToken();
        header('Location: ' . $baseMagentoUrl . '/oauth/authorize?oauth_token=' . urlencode($_SESSION['request_token']['oauth_token']));
        exit;
    } elseif (isset($_SESSION['request_token']) && isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])) { // user authorized and redirected here, now request access token (step E from http://oauth.net/core/diagram.png)
        $_SESSION['access_token'] = $client->getAccessToken($_GET['oauth_token'], $_SESSION['request_token']['oauth_token_secret'], $_GET['oauth_verifier']);
        var_dump($_SESSION['access_token']);
    }
    

    您还可以在 magento 中进行一些逆向工程。例如,当请求“请求令牌”时,流程是:

    Mage_Oauth_InitiateController::indexAction()
    Mage_Oauth_Model_Server::initiateToken()
    Mage_Oauth_Model_Server::_processRequest()
    Mage_Oauth_Model_Server::_validateSignature()
    Zend_Oauth_Http_Utility::sign()
    Zend_Oauth_Signature_Hmac::sign()
    Zend_Oauth_Signature_SignatureAbstract::_getBaseSignatureString()
    

    在最后 3 节课中,所有的魔法都发生了。你可以去看看。

    【讨论】:

    • 感谢您的详细回答,我检查了它们,发现我的输出与您的相同。我有这张图 (oauth.net/core/diagram.png),我有步骤 D 中的“oauth_token”和“oauth_verifier”,但我在步骤 E 中遇到了问题。您能否为我提供一个步骤 E 的示例?
    • 我还在示例脚本中添加了访问令牌检索
    • 它的检索方式与您检索请求令牌的方式相同,只是您没有oauth_callback 参数,而是有2个新参数oauth_token -> 请求令牌和@987654332 @ -> 用户授权后检索的字符串。散列时使用的密钥也附加了请求令牌秘密。您还可以使用 Oauth PHP 库 (php.net/manual/en/book.oauth.php),因为 magento rest api 示例基于它是否对您更容易。
    • 它刚刚成功,非常感谢您的帮助。我想我从前面的步骤中跳过了oauth_token_secret,只关注oauth_tokenoauth_verifier,因此遇到签名错误。顺便说一句,你接受任何捐赠吗? :)
    • 不客气。我很高兴能帮上忙,毕竟我们都是来互相帮助的。无需捐款,但如果您愿意,可以从我的个人资料中查看该网站 :)
    猜你喜欢
    • 2021-06-10
    • 2016-06-01
    • 1970-01-01
    • 2021-12-19
    • 2022-01-21
    • 1970-01-01
    • 2017-03-17
    • 2011-03-27
    • 1970-01-01
    相关资源
    最近更新 更多