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

通过JavaScript调用NetSuite RESTlet时遭遇"Invalid Login Attempt"错误

NetSuite Restlet OAuth 1.0 认证失败问题修复

问题分析

当前代码的核心错误在于OAuth 1.0签名的baseString构造完全不符合规范,这直接导致NetSuite无法验证登录请求,返回INVALID_LOGIN_ATTEMPT,同时因为认证失败,角色信息不会被记录到审计日志中。

关键错误点

  • BaseString构造错误:OAuth 1.0的baseString必须遵循HTTP_METHOD&ENCODED_REQUEST_URL&ENCODED_NORMALIZED_PARAMS格式,而非直接拼接realm、consumerKey等字段。
  • 参数未排序:OAuth参数需要按字典序排序后再进行编码拼接,否则签名验证会失败。
  • URL编码不完整:baseString的各个组成部分都需要单独进行URL编码,再用&连接。

修复后的代码

var crypto = require("crypto");
var fetch = require('node-fetch');

// 替换为你的实际账号信息
var accountId = "{accountId}";
var url = `https://${accountId}.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=729&deploy=1`;
var realm = accountId; // NetSuite的realm通常就是账号ID,沙箱环境可能是类似"ACCT123_SB1"
var signatureMethod = "HMAC-SHA256";
var oauthVersion = "1.0";
var consumerKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
var consumerSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
var tokenId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
var tokenSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

// 生成随机nonce和时间戳
var nonce = randomString(10);
var timeStamp = Math.round((new Date()).getTime() / 1000);

// 1. 整理所有OAuth参数,按字典序排序
var oauthParams = [
  `oauth_consumer_key=${encodeURIComponent(consumerKey)}`,
  `oauth_nonce=${encodeURIComponent(nonce)}`,
  `oauth_signature_method=${encodeURIComponent(signatureMethod)}`,
  `oauth_timestamp=${encodeURIComponent(timeStamp)}`,
  `oauth_token=${encodeURIComponent(tokenId)}`,
  `oauth_version=${encodeURIComponent(oauthVersion)}`
].sort().join('&');

// 2. 构造baseString
var encodedUrl = encodeURIComponent(url.split('?')[0]); // 只编码基础URL,参数部分单独处理
var encodedParams = encodeURIComponent(oauthParams);
var baseString = `POST&${encodedUrl}&${encodedParams}`;

// 3. 生成签名密钥
var signingKey = `${encodeURIComponent(consumerSecret)}&${encodeURIComponent(tokenSecret)}`;

// 4. 生成签名
var hmac = crypto.createHmac("sha256", signingKey);
hmac.update(baseString);
var signature = encodeURIComponent(hmac.digest("base64"));

// 构造Authorization头
var authorizationStr = `OAuth realm="${realm}"` +
  `,oauth_consumer_key="${consumerKey}"` +
  `,oauth_token="${tokenId}"` +
  `,oauth_signature_method="${signatureMethod}"` +
  `,oauth_timestamp="${timeStamp}"` +
  `,oauth_nonce="${nonce}"` +
  `,oauth_version="${oauthVersion}"` +
  `,oauth_signature="${signature}"`;

console.log("timeStamp: " + timeStamp);
console.log("nonce: " + nonce);
console.log("signature: " + signature);

var raw = JSON.stringify({
  "searchId": "customsearch_pendingso"
});

var requestOptions = {
  method: 'POST',
  headers: {
    "Authorization": authorizationStr,
    "Content-Type": "application/json",
    "Cache-Control": "no-cache"
    // 移除不必要的Cookie和Accept-Encoding,避免干扰
  },
  body: raw,
  redirect: 'follow'
};

fetch(url, requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

function randomString(length) {
  var text = "";
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  for (var i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

额外注意事项

  • 确保realm值正确:沙箱环境需要加上_SB1后缀(如123456_SB1),正式环境直接用账号ID。
  • 检查令牌权限:确认token对应的角色有权限访问该Restlet脚本(脚本部署页需添加对应角色)。
  • 避免不必要的请求头:移除Cookie和Accept-Encoding这类可能干扰NetSuite认证的头信息。

内容的提问来源于stack exchange,提问作者oycarlito

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 22:10:43