我需要编写一个 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"
@Brian 提供的评论是正确的,因为我最初没有签署足够的值,并且我签署的值没有按密钥的字母顺序排序。此外,当尝试使用 NetSuite 提供的
restletBaseString
函数时,我生成了一个新的随机数和新的时间戳来创建基本字符串,然后在创建时仍然使用我之前在 $authorizor
数组中创建的随机数和时间戳标题。这导致签名和标头之间的值不匹配。