You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP 8.2生成NetSuite OAuth1.0授权头遇INVALID LOGIN ATTEMPT问题

PHP 8.2 调用NetSuite RESTlet的OAuth1.0认证问题

需要编写PHP 8.2函数调用NetSuite实例的RESTlet,采用Token-based Authentication(TBA,实际为OAuth1.0)方式。现有PHP OAuth1.0库均不支持NetSuite TBA的全部参数(如consumer和token密钥),且无法使用仅支持PHP7.4的oauth PHP扩展。

更新:尝试过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",
    /* 仅用于测试是否缺少User-Agent导致问题,测试后排除该原因 */
    "User-Agent: Mozilla 5.0",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$empty = curl_exec($ch);

/**
 * 生成NetSuite REST调用所需的OAuth头
 */
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"
    ];

    // 生成基字符串
    // 失败尝试
    // $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"])
    // ]);
    // 同样无效
    $base = restletBaseString($httpMethod, $url, CONSUMER_KEY, TOKEN_KEY, getOAuthNonce(), time(), "1.0", "HMAC-SHA256", $postParams);

    // 生成HMAC签名密钥
    $key = rawurlencode(CONSUMER_SECRET)."&".rawurlencode(TOKEN_SECRET);

    // 将base64编码的签名加入授权数组
    $authorizor["oauth_signature"] = base64_encode(hash_hmac("sha256", $base, $key, true));

    // 对授权数组中的值进行URL编码
    $authorizorEncoded = array_map("rawurlencode", ["realm" => ACCOUNT_ID, ...$authorizor]);

    // 将键值对格式化为`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)
    );
}

/**
 * 该函数为NetSuite官方提供的OAuth签名基字符串生成方法
 */
function restletBaseString($httpMethod, $url, $consumerKey, $tokenKey, $nonce, $timestamp, $version, $signatureMethod, $postParams){
    // HTTP方法必须大写
    $baseString = strtoupper($httpMethod) .'&';
    
    // 提取不含参数的URL,协议和主机名必须小写
    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) .'&';
    
    // 整合所有OAuth参数和GET参数:先解码,再按字母排序,然后分别编码键和值,最后整体编码
    $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);
        }
      }
    }
     
    // 所有参数按字母顺序排序
    ksort($params);
     
    $paramString = "";
    foreach ($params as $key => $valueArray){
      // 所有值按字母顺序排序
      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"

内容的提问来源于stack exchange,提问作者Bee S.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 10:17:02