为 NetSuite 生成 OAuth1.0 标头

问题描述 投票:0回答:1

我需要编写一个 PHP (8.2) 函数来调用 NetSuite 实例上的 RESTlet。为了调用此 RESTlet,我使用基于令牌的身份验证 (TBA) 方法,该方法实际上似乎是 OAuth 1.0。我在 PHP 中为 OAuth 1.0 找到的所有现成库都没有使用 NetSuite TBA 的所有部分,即消费者和令牌机密,因此我认为它们不起作用。值得注意的是,我无法在我的计算机上运行 oauth PHP 扩展,大概是因为它是针对 PHP 7.4 发布的,因此我使用 SOAP Web 服务部分中所示的方法生成基本字符串部分。 更新:我也尝试过使用NetSuite

提供的
restletBaseString函数,但这也不起作用。

因此,我尝试自己创建授权标头 遵循 NetSuite 创建的指南,但不断收到

INVALID LOGIN ATTEMPT
。尽管将我生成的标头与在 Postman 中创建的标头并排放置,并且它们看起来是相同的。同样可能重要的是,当我检查与这些凭据关联的帐户的登录审核跟踪时,我看到没有失败的登录尝试。

所有敏感信息均已更改,但我的实际值已直接从正在运行的 Postman 复制/粘贴

<?php

define("ACCOUNT_ID", "1234567_SB1");
define("CONSUMER_KEY", "8daa8433f54386d63a5317d71fef801cf47a8cc3d9552be3d0f63fc8ab6de8f9");
define("CONSUMER_SECRET", "72ad5f9134091493a108a72f283733304e4218d37bc559ca99d5fd46742f7ca1");
define("TOKEN_KEY", "65d48649dcf0f8ad2da444af1d16377232bb4a5f8ffa8f3fb6175a3e6a8ed0e5");
define("TOKEN_SECRET", "bb07a68caf57fb6628661f10e687d065959cb7ab1ad6e19da1467718c67b7d6b");
define("COMPANY_URL", "https://1234567-sb1.restlets.api.netsuite.com/app/site/hosting/restlet.nl");

$url = COMPANY_URL."?script=2211&deploy=1&id=28099";

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: ".createOAuthHeader("GET", $url),
    "Content-type: application/json",
    /* Only here to test if a lack of a user-agent was the cause. It was not */
    "User-Agent: Mozilla 5.0",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$empty = curl_exec($ch);

/**
 * Create the OAuth header for NetSuite REST calls
 */
function createOAuthHeader(string $httpMethod = "GET", $url): string
{
    $authorizor = [
        "oauth_consumer_key" => CONSUMER_KEY,
        "oauth_token" => TOKEN_KEY,
        "oauth_signature_method" => "HMAC-SHA256",
        "oauth_timestamp" => time(),
        "oauth_nonce" => getOAuthNonce(),
        "oauth_version" => "1.0"
    ];

    // Create the Base String
    // Failed attempt
    // $base = join("&", array_map("rawurlencode", array_merge([$httpMethod, $url], flattenAssociativeArray($authorizor, false))));
    // $base = oauth_get_sbs($httpMethod, $url, $authorizor);
    // $base = join("&", [
    //     rawurlencode(ACCOUNT_ID),
    //     rawurlencode(CONSUMER_KEY),
    //     rawurlencode(TOKEN_KEY),
    //     rawurlencode($authorizor["oauth_nonce"]),
    //     rawurlencode($authorizor["oauth_timestamp"])
    // ]);
    // Also doesn't work
    $base = restletBaseString($httpMethod, $url, CONSUMER_KEY, TOKEN_KEY, getOAuthNonce(), time(), "1.0", "HMAC-SHA256", $postParams);

    // Create the HMAC signature key
    $key = rawurlencode(CONSUMER_SECRET)."&".rawurlencode(TOKEN_SECRET);

    // Add the base64-encoded signature to the authorizor array
    $authorizor["oauth_signature"] = base64_encode(hash_hmac("sha256", $base, $key, true));

    // URL encode the values in the authorizor ID
    $authorizorEncoded = array_map("rawurlencode", ["realm" => ACCOUNT_ID, ...$authorizor]);

    // Flattens the key/value pairs into an array of strings in the form of key="value"
    $final = flattenAssociativeArray($authorizorEncoded, true);

    return "OAuth ".join(",", $final);
}

function getOAuthNonce(): string
{
    return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 11);
}

function flattenAssociativeArray(array $arr, bool $quoteValue = true): array
{
    $formatString = $quoteValue ? '%s="%s"' : "%s=%s";

    return array_map(
        fn($key, $value): string => sprintf($formatString, $key, $value)
        , array_keys($arr)
        , array_values($arr)
    );
}

/**
 * The function used to create the base string for the OAuth signature as provided by NetSuite
 * @see https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1534939551.html#subsect_1520632716
 */
function restletBaseString($httpMethod, $url, $consumerKey, $tokenKey, $nonce, $timestamp, $version, $signatureMethod, $postParams){
    //http method must be upper case
    $baseString = strtoupper($httpMethod) .'&';
    
    //include url without parameters, schema and hostname must be lower case
    if (strpos($url, '?')){
      $baseUrl = substr($url, 0, strpos($url, '?'));
      $getParams = substr($url, strpos($url, '?') + 1);
    } else {
     $baseUrl = $url;
     $getParams = "";
    }
    $hostname = strtolower(substr($baseUrl, 0,  strpos($baseUrl, '/', 10)));
    $path = substr($baseUrl, strpos($baseUrl, '/', 10));
    $baseUrl = $hostname . $path;
    $baseString .= rawurlencode($baseUrl) .'&';
    
    //all oauth and get params. First they are decoded, next sorted in alphabetical order, next each key and values is encoded and finally whole parameters are encoded
    $params = array();
    $params['oauth_consumer_key'] = array($consumerKey);
    $params['oauth_token'] = array($tokenKey);
    $params['oauth_nonce'] = array($nonce);
    $params['oauth_timestamp'] = array($timestamp);
    $params['oauth_signature_method'] = array($signatureMethod);
    $params['oauth_version'] = array($version);
     
    foreach (explode('&', $getParams ."&". $postParams) as $param) {
      $parsed = explode('=', $param);
      if ($parsed[0] != "") {
        $value = isset($parsed[1]) ? urldecode($parsed[1]): "";
        if (isset($params[urldecode($parsed[0])])) {
          array_push($params[urldecode($parsed[0])], $value);
        } else {
          $params[urldecode($parsed[0])] = array($value);
        }
      }
    }
     
    //all parameters must be sorted in alphabetical order 
    ksort($params);
     
    $paramString = "";
    foreach ($params as $key => $valueArray){
      //all values must sorted in alphabetical order
      sort($valueArray);
      foreach ($valueArray as $value){
        $paramString .= rawurlencode($key) . '='. rawurlencode($value) .'&';
      }
    }
    $paramString = substr($paramString, 0, -1);
    $baseString .= rawurlencode($paramString);
    return $baseString;
}

Postman 创建的有效授权标头示例

Authorization: OAuth realm="1234567_SB1"
    ,oauth_consumer_key="8daa8433f54386d63a5317d71fef801cf47a8cc3d9552be3d0f63fc8ab6de8f9"
    ,oauth_token="65d48649dcf0f8ad2da444af1d16377232bb4a5f8ffa8f3fb6175a3e6a8ed0e5"
    ,oauth_signature_method="HMAC-SHA256"
    ,oauth_timestamp="1710773563"
    ,oauth_nonce="qcFwZvbnJ5u"
    ,oauth_version="1.0"
    ,oauth_signature="iL7L430GE%2Bdw5oSBISr848VKNFm5LLsMskiMD362qCk%3D"

PHP 创建的无效授权标头示例

Authorization: OAuth realm="1234567_SB1"
    ,oauth_consumer_key="8daa8433f54386d63a5317d71fef801cf47a8cc3d9552be3d0f63fc8ab6de8f9"
    ,oauth_token="65d48649dcf0f8ad2da444af1d16377232bb4a5f8ffa8f3fb6175a3e6a8ed0e5"
    ,oauth_signature_method="HMAC-SHA256"
    ,oauth_timestamp="1710775464"
    ,oauth_nonce="3Hulo6OPf78"
    ,oauth_version="1.0"
    ,oauth_signature="zQlMYe%2F9VtANAvgvJo%2Bs7SvLMTvcrrJcOa8Yz5wLQsM%3D"
php oauth netsuite
1个回答
0
投票

@Brian 提供的评论是正确的,因为我最初没有签署足够的值,并且我签署的值没有按密钥的字母顺序排序。此外,当尝试使用 NetSuite 提供的

restletBaseString
函数时,我生成了一个新的随机数和新的时间戳来创建基本字符串,然后在创建时仍然使用我之前在
$authorizor
数组中创建的随机数和时间戳标题。这导致签名和标头之间的值不匹配。

© www.soinside.com 2019 - 2024. All rights reserved.