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

Python调用NetSuite Restlet分页请求的OAuth签名生成问题

NetSuite Restlet 分页查询的OAuth签名问题

我们公司使用NetSuite,通过Restlet对接数据集时,调用基础端点可正常获取前1000条数据,但遇到分页场景时,不清楚如何将limit=1000、offset=1000这类查询参数纳入OAuth签名以调用下一页数据。

现有可运行代码

import time
import uuid
import urllib.parse
import hmac
import hashlib
import base64
import requests
from requests_oauthlib import oauth1_auth

def get_request_header(url, realm, consumer_key, consumer_secret, token, token_secret):
    http_method = "GET"

    # OAuth parameters
    oauth_params = {
        "oauth_consumer_key": consumer_key,
        "oauth_token": token,
        "oauth_signature_method": "HMAC-SHA256",
        "oauth_timestamp": str(int(time.time())),
        "oauth_nonce": uuid.uuid4().hex,
        "oauth_version": "1.0"
    }

    # 1. Sort parameters by key
    sorted_params = sorted(oauth_params.items(), key=lambda x: x[0])

    # 2. URL-encode each key-value pair and join them into a query string
    encoded_params = urllib.parse.urlencode(sorted_params, quote_via=urllib.parse.quote)

    # 4. Construct the base string
    base_string = "&".join([
        http_method.upper(),
        urllib.parse.quote(url, safe=''),
        urllib.parse.quote(encoded_params, safe='')
    ])
    print(base_string)

    # 5. Create the signing key using consumer_secret and token_secret
    signing_key = "&".join([
        urllib.parse.quote(consumer_secret, safe=''),
        urllib.parse.quote(token_secret, safe='')
    ])
    print(signing_key)

    # 6. Compute the HMAC-SHA256 signature
    signature = hmac.new(
        signing_key.encode('utf-8'),
        base_string.encode('utf-8'),
        hashlib.sha256
    ).digest()

    # 7. Base64-encode the signature and URL-encode it
    oauth_params["oauth_signature"] = urllib.parse.quote(base64.b64encode(signature).decode('utf-8'), safe='')

    # 8. Construct the Authorization header
    auth_header = 'OAuth ' + ', '.join([f'{k}="{v}"' for k, v in oauth_params.items()])

    # Add realm if required by the API
    auth_header = f'OAuth realm="{realm}", ' + auth_header[len("OAuth "):]

    # Request headers
    headers = {
        'Prefer': 'transient',
        'Authorization': auth_header,
        'Content-Type': 'application/json'
    }

    return headers

url = "https://<my_account_id>.suitetalk.api.netsuite.com/services/rest/query/v1/dataset/custdataset303/result"
realm = "my_account_id"  # Example realm from your Postman snippet
consumer_key = "my_consumer_key"
consumer_secret = "my_consumer_secret"
token = "my_token"
token_secret = "my_token_secret"

# Get headers and URL with OAuth signature
headers = get_request_header(url, realm, consumer_key, consumer_secret, token, token_secret)

# Make the GET request to NetSuite
response = requests.get(url, headers=headers)

# Print the response data
print(response.status_code)
print(response.json())

已尝试的方法

  • 仅将查询参数(limit=1000, offset=1000)添加到基础URL字符串,但发现参数需纳入签名,不知如何操作
  • 在构造签名基字符串时,将查询参数作为URL的一部分传入urllib.parse.quote
  • 创建单独的params字典,单独排序编码后与OAuth参数合并
  • 将查询参数加入oauth_params字典并传入get_request_header,但仍仅返回前1000条数据

已验证返回结果中的下一页链接可在Postman中正常调用,不确定当前方法是否适配NetSuite要求的SHA-256签名,且oauth1库已被废弃。


解决方案

核心问题是OAuth 1.0a要求所有请求参数(包括查询参数和OAuth参数)必须共同参与签名基字符串的构造,之前的代码仅处理了OAuth参数,遗漏了分页查询参数。以下是修正后的实现:

修正后的完整代码

import time
import uuid
import urllib.parse
import hmac
import hashlib
import base64
import requests

def get_request_header(base_url, realm, consumer_key, consumer_secret, token, token_secret, query_params=None):
    http_method = "GET"

    # OAuth参数
    oauth_params = {
        "oauth_consumer_key": consumer_key,
        "oauth_token": token,
        "oauth_signature_method": "HMAC-SHA256",
        "oauth_timestamp": str(int(time.time())),
        "oauth_nonce": uuid.uuid4().hex,
        "oauth_version": "1.0"
    }

    # 合并OAuth参数与查询参数
    all_params = oauth_params.copy()
    if query_params:
        all_params.update(query_params)

    # 1. 按key排序所有参数
    sorted_params = sorted(all_params.items(), key=lambda x: x[0])

    # 2. URL编码每个键值对并拼接成查询字符串
    encoded_params = urllib.parse.urlencode(sorted_params, quote_via=urllib.parse.quote)

    # 3. 构造签名基字符串(使用不带查询参数的基础URL)
    base_string = "&".join([
        http_method.upper(),
        urllib.parse.quote(base_url, safe=''),
        urllib.parse.quote(encoded_params, safe='')
    ])

    # 4. 创建签名密钥
    signing_key = "&".join([
        urllib.parse.quote(consumer_secret, safe=''),
        urllib.parse.quote(token_secret, safe='')
    ])

    # 5. 计算HMAC-SHA256签名
    signature = hmac.new(
        signing_key.encode('utf-8'),
        base_string.encode('utf-8'),
        hashlib.sha256
    ).digest()

    # 6. 对签名进行Base64编码和URL编码,加入OAuth参数
    oauth_params["oauth_signature"] = urllib.parse.quote(base64.b64encode(signature).decode('utf-8'), safe='')

    # 7. 构造Authorization头
    auth_header_parts = [f'{k}="{v}"' for k, v in oauth_params.items()]
    auth_header = f'OAuth realm="{realm}", ' + ', '.join(auth_header_parts)

    # 请求头
    headers = {
        'Prefer': 'transient',
        'Authorization': auth_header,
        'Content-Type': 'application/json'
    }

    return headers

# 基础URL(不带查询参数)
base_url = "https://<my_account_id>.suitetalk.api.netsuite.com/services/rest/query/v1/dataset/custdataset303/result"
realm = "my_account_id"
consumer_key = "my_consumer_key"
consumer_secret = "my_consumer_secret"
token = "my_token"
token_secret = "my_token_secret"

# 分页查询参数
page_params = {
    "limit": 1000,
    "offset": 1000
}

# 获取带签名的请求头
headers = get_request_header(base_url, realm, consumer_key, consumer_secret, token, token_secret, page_params)

# 发起请求(两种方式二选一)
# 方式1:拼接查询参数到URL
full_url = f"{base_url}?{urllib.parse.urlencode(page_params)}"
response = requests.get(full_url, headers=headers)

# 方式2:使用requests的params参数自动拼接
# response = requests.get(base_url, headers=headers, params=page_params)

print(response.status_code)
print(response.json())

关键修改说明

  1. 新增query_params参数:用于接收分页等查询参数,实现参数的灵活传入
  2. 合并所有参数参与签名:将OAuth参数和查询参数合并后再排序编码,确保所有参数都纳入签名计算
  3. 分离基础URL与参数:签名基字符串使用不带查询参数的原始URL,避免参数重复处理
  4. 移除废弃依赖:手动实现OAuth 1.0a签名逻辑,不再依赖已废弃的requests_oauthlib库,完全适配NetSuite的HMAC-SHA256签名要求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 07:47:27