SuiteScript 2.1通过OAuth 1.0(TBA)连接Restlet的签名实现问题
在SuiteScript 2.0/2.1中实现NetSuite OAuth 1.0(TBA)的HMAC-SHA256签名
问题描述
我尝试用SuiteScript 2.1通过OAuth 1.0(TBA)连接NetSuite Restlet,但发现SuiteScript 2.1的crypto模块不支持所需的哈希功能。试过crypto和encode模块都没效果,想知道在SuiteScript 2.1或2.0中,有没有其他方式实现OAuth 1.0(TBA)加密认证,或者生成HMAC-SHA256签名?
原尝试代码:
var SIGNATURE_METHOD = "HMAC-SHA256" var length = 11; var OAUTH_NONCE = random(length) var date = new Date() var milliseconds = date.getTime() var TIME_STAMP = parseInt(((milliseconds -120000)/1000)) var OAUTH_VERSION = "1.0"; var SCRIPT_DEPLOYMENT_ID = 1; var REALM = "78787878" var CONSUMERKEY = "4554545"; var CONSUMERSECRET = "45455454"; var TOKENKEY = "78787878"; var TOKENSECRET = "4545454544"; var base_url = "https://89898989-sb1.restlets.api.netsuite.com/app/site/hosting/restlet.nl" var encdata = "deploy=" + SCRIPT_DEPLOYMENT_ID + "&" encdata = encdata + "oauth_consumer_key=" + CONSUMERKEY + "&" encdata = encdata + "oauth_nonce=" + OAUTH_NONCE + "&" encdata = encdata + "oauth_signature_method=" + SIGNATURE_METHOD + "&" encdata = encdata + "oauth_timestamp=" + TIME_STAMP + "&" encdata = encdata + "oauth_token=" + TOKENKEY + "&" encdata = encdata + "oauth_version=" + OAUTH_VERSION + "&" encdata = encdata + "script=" + scriptId var baseEncrypt = base_url; var encryptData = encdata; var completeData = method+'&' + baseEncrypt + '&'+ encryptData var ConsumerSecretEncode = CONSUMERSECRET var ConsumerTokedEncode = TOKENSECRET var key = ConsumerSecretEncode + "&" + ConsumerTokedEncode var algorithm = 'SHA256'; var signatureNormal = generateHMAC(key, completeData, algorithm, crypto, encode); var signature = signatureNormal var OAuth = "OAuth realm=\"" + REALM + "\","; OAuth = OAuth + "oauth_consumer_key=\"" + CONSUMERKEY + "\","; OAuth = OAuth + "oauth_token=\"" + TOKENKEY + "\","; OAuth = OAuth + "oauth_signature_method=\"HMAC-SHA256\","; OAuth = OAuth + "oauth_timestamp=\"" + TIME_STAMP + "\","; OAuth = OAuth + "oauth_nonce=\"" + OAUTH_NONCE + "\","; OAuth = OAuth + "oauth_version=\"" + "1.0" + "\","; OAuth = OAuth + "oauth_signature=\"" + signature + "\"" return OAuth } function generateHMAC(key, data, algorithm, crypto, encode) { const encoder = encode const dataValue = encoder.convert({ string: data, inputEncoding: encoder.Encoding.UTF_8, // Use the value directly outputEncoding: encoder.Encoding.BASE_64 // Use the value directly }); const keyData = encoder.convert({ string: key, inputEncoding: encoder.Encoding.UTF_8, // Use the value directly outputEncoding: encoder.Encoding.BASE_64 // Use the value directly }); return crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']) .then((cryptoKey) => crypto.subtle.sign('HMAC', cryptoKey, dataValue)) .then((signature) => { const view = new DataView(signature); let hash = ''; for (let i = 0; i < view.byteLength; i++) { const hex = view.getUint8(i).toString(16); if (hex.length === 1) { hash += '0'; } hash += hex; } return hash; }); }
一、SuiteScript 2.0 实现方案
SuiteScript 2.0的N/crypto模块原生支持createHmac方法,可直接生成HMAC-SHA256签名,是最稳定的方案。
核心签名生成代码
function generateHMAC20(key, data) { var crypto = require('N/crypto'); var encode = require('N/encode'); // 转换密钥和数据为Base64字节数组 var keyBytes = encode.convert({ string: key, inputEncoding: encode.Encoding.UTF_8, outputEncoding: encode.Encoding.BASE_64 }); var dataBytes = encode.convert({ string: data, inputEncoding: encode.Encoding.UTF_8, outputEncoding: encode.Encoding.BASE_64 }); // 创建HMAC实例并生成签名 var hmac = crypto.createHmac({ algorithm: crypto.HashAlg.SHA256, key: keyBytes }); hmac.update({input: dataBytes}); var signature = hmac.digest({outputEncoding: encode.Encoding.BASE_64}); // OAuth要求签名需URL编码 return encodeURIComponent(signature); }
二、SuiteScript 2.1 修正方案
SuiteScript 2.1的crypto.subtle是异步API,原代码存在未处理异步Promise、参数未排序、缺少URL编码三个核心问题,以下是修正后的完整实现:
完整OAuth头生成代码
async function generateOAuthHeader(method, scriptId) { const SIGNATURE_METHOD = "HMAC-SHA256"; const length = 11; const OAUTH_NONCE = random(length); // 确保random函数已正确实现 const date = new Date(); const TIME_STAMP = Math.floor((date.getTime() - 120000) / 1000); const OAUTH_VERSION = "1.0"; const SCRIPT_DEPLOYMENT_ID = 1; const REALM = "78787878"; const CONSUMERKEY = "4554545"; const CONSUMERSECRET = "45455454"; const TOKENKEY = "78787878"; const TOKENSECRET = "4545454544"; const base_url = "https://89898989-sb1.restlets.api.netsuite.com/app/site/hosting/restlet.nl"; // 1. 按字母顺序排序参数(OAuth强制要求) const params = { deploy: SCRIPT_DEPLOYMENT_ID, oauth_consumer_key: CONSUMERKEY, oauth_nonce: OAUTH_NONCE, oauth_signature_method: SIGNATURE_METHOD, oauth_timestamp: TIME_STAMP, oauth_token: TOKENKEY, oauth_version: OAUTH_VERSION, script: scriptId }; const encdata = Object.keys(params).sort().map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}` ).join('&'); // 2. 构建签名基础字符串(所有部分需URL编码) const encodedBaseUrl = encodeURIComponent(base_url); const encodedParams = encodeURIComponent(encdata); const completeData = `${method.toUpperCase()}&${encodedBaseUrl}&${encodedParams}`; // 3. 构建签名密钥(密钥也需URL编码) const key = `${encodeURIComponent(CONSUMERSECRET)}&${encodeURIComponent(TOKENSECRET)}`; // 4. 异步生成签名 const signature = await generateHMAC21(key, completeData); // 5. 构建最终OAuth头 return `OAuth realm="${REALM}",` + `oauth_consumer_key="${CONSUMERKEY}",` + `oauth_token="${TOKENKEY}",` + `oauth_signature_method="${SIGNATURE_METHOD}",` + `oauth_timestamp="${TIME_STAMP}",` + `oauth_nonce="${OAUTH_NONCE}",` + `oauth_version="${OAUTH_VERSION}",` + `oauth_signature="${encodeURIComponent(signature)}"`; } async function generateHMAC21(key, data) { const crypto = require('N/crypto'); const textEncoder = new TextEncoder(); // 将字符串转换为Uint8Array(适配crypto.subtle API) const keyData = textEncoder.encode(key); const dataBytes = textEncoder.encode(data); // 导入密钥并生成签名 const cryptoKey = await crypto.subtle.importKey( 'raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ); const signatureBuffer = await crypto.subtle.sign('HMAC', cryptoKey, dataBytes); // 将ArrayBuffer转换为Base64字符串 return btoa(String.fromCharCode(...new Uint8Array(signatureBuffer))); }
关键注意事项
- 参数排序:OAuth 1.0要求所有请求参数按字母顺序排序,原代码未做此处理会导致签名验证失败
- URL编码:签名基础字符串中的URL、参数、密钥都必须做URL编码,这是OAuth规范的强制要求
- 异步处理:SuiteScript 2.1的
crypto.subtle是异步API,必须用async/await处理,不能直接将Promise对象赋值给签名变量 - 编码转换:2.1中使用原生
TextEncoder转换字节数组比N/encode更简洁适配
内容的提问来源于stack exchange,提问作者Sri
相关产品推荐
相关产品推荐

