为何我的OAuth 1.0签名无法通过Twitter API认证?
问题描述
调用Twitter API获取用户凭证时,持续返回认证失败错误:
{"errors":[{"code":32,"message":"Could not authenticate you."}]}
我的代码如下:
# Libraries import requests import urllib.parse import time import hmac import hashlib import base64 # Set your callback URL here callback_url = 'https://twitter.com/lemonknowsall' # Generate nonce and timestamp timestamp = str(int(time.time())) nonce = timestamp # Define the OAuth signature method and version signature_method = 'HMAC-SHA1' oauth_version = '1.0' # Encode the callback URL encoded_callback_url = urllib.parse.quote(callback_url, safe='') # Construct the base string base_string = f'POST&{urllib.parse.quote("https://api.twitter.com/oauth/request_token", safe="")}&' base_string += f'oauth_callback%3D{encoded_callback_url}%26' base_string += f'oauth_consumer_key%3D{consumer_key}%26' base_string += f'oauth_nonce%3D{nonce}%26' base_string += f'oauth_signature_method%3D{signature_method}%26' base_string += f'oauth_timestamp%3D{timestamp}%26' base_string += f'oauth_version%3D{oauth_version}' # Create the signing key signing_key = f'{urllib.parse.quote(consumer_secret, safe="")}&' # Generate the signature signature = base64.b64encode(hmac.new(signing_key.encode('utf-8'), base_string.encode('utf-8'), hashlib.sha1).digest()) # Encode the signature encoded_signature = urllib.parse.quote(signature, safe='') # Construct the request URL url = "https://api.twitter.com/oauth/request_token?" url += f"oauth_callback={encoded_callback_url}"+"&" url += f"oauth_consumer_key={consumer_key}"+"&" url += "oauth_signature_method=HMAC-SHA1"+"&" url += "oauth_version=1.0"+"&" url += f"oauth_timestamp={timestamp}"+"&" url += f"oauth_nonce={nonce}"+"&" url += f"oauth_signature={encoded_signature}" response = requests.request("POST", url) print(response.text)
已确认consumer_key和consumer_secret已正确定义,回调URL也在Twitter开发者平台验证通过。替换Postman生成的encoded_signature就能成功响应,推测问题出在base_string或签名生成环节,但找不到具体错误。
解决方法
问题主要出在base_string的构建逻辑和签名编码处理上,以下是具体修正点:
1. 严格按字典序排列OAuth参数
OAuth 1.0要求base_string中的参数必须按参数名的ASCII字典序排列,手动拼接容易出错,建议用排序后的参数键值对构建:
# 替换原base_string构建代码 params = { 'oauth_callback': callback_url, 'oauth_consumer_key': consumer_key, 'oauth_nonce': nonce, 'oauth_signature_method': signature_method, 'oauth_timestamp': timestamp, 'oauth_version': oauth_version } # 按参数名ASCII排序 sorted_params = sorted(params.items()) # 编码每个键值对 encoded_params = [f"{urllib.parse.quote(k, safe='')}%3D{urllib.parse.quote(v, safe='')}" for k, v in sorted_params] # 拼接成base_string的参数部分 params_string = '%26'.join(encoded_params) base_string = f'POST&{urllib.parse.quote("https://api.twitter.com/oauth/request_token", safe="")}&{params_string}'
2. 签名需先转字符串再做URL编码
原代码直接对base64.b64encode返回的bytes对象做URL编码,会导致b'...'这类字节标识被错误编码,需先转成UTF-8字符串:
# 替换原签名生成和编码代码 signature = base64.b64encode(hmac.new(signing_key.encode('utf-8'), base_string.encode('utf-8'), hashlib.sha1).digest()).decode('utf-8') encoded_signature = urllib.parse.quote(signature, safe='')
3. 推荐用Authorization头传递参数(可选)
Twitter OAuth 1.0更规范的做法是将OAuth参数放在Authorization请求头中,避免URL编码的潜在问题:
# 替换原URL构建和请求代码 auth_header = ( f'OAuth oauth_callback="{encoded_callback_url}",' f'oauth_consumer_key="{consumer_key}",' f'oauth_nonce="{nonce}",' f'oauth_signature="{encoded_signature}",' f'oauth_signature_method="{signature_method}",' f'oauth_timestamp="{timestamp}",' f'oauth_version="{oauth_version}"' ) headers = {'Authorization': auth_header} response = requests.post("https://api.twitter.com/oauth/request_token", headers=headers)
应用以上修正后,即可生成符合要求的签名,解决认证失败问题。
内容的提问来源于stack exchange,提问作者lemon
相关产品推荐
相关产品推荐

