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.
相关产品推荐
相关产品推荐

