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

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)));
}

关键注意事项

  1. 参数排序:OAuth 1.0要求所有请求参数按字母顺序排序,原代码未做此处理会导致签名验证失败
  2. URL编码:签名基础字符串中的URL、参数、密钥都必须做URL编码,这是OAuth规范的强制要求
  3. 异步处理:SuiteScript 2.1的crypto.subtle是异步API,必须用async/await处理,不能直接将Promise对象赋值给签名变量
  4. 编码转换:2.1中使用原生TextEncoder转换字节数组比N/encode更简洁适配

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 00:44:57