问题描述
我正在尝试加入一个项目的eTrade API。他们使用OAuth v1流。在获取请求令牌的流程的第一步,我就遇到了错误。可以在here中找到其文档中的信息。
我得到的错误是401-无效的签名。
我一直在阅读OAuth流程here,并已熟悉该过程。过去,我将PassportJS用于第三者集成。
因此eTrade为我提供了4个密钥-PROD_KEY,PROD_SECRET,SANDBox_KEY,SANDBox_SECRET。我现在正在使用沙盒密钥,因为我的生产密钥仍在等待处理。
现在我的API上有一个端点,我将从客户端调用该端点以获取请求令牌。它的结构是这样的:
router.route('/auth/request').get(controller.request);
以下控制器是我生成签名并尝试从eTrade请求令牌的地方。该控制器如下所示:
// controllers/auth.js
const axios = require('axios');
const { v1 } = require('uuid');
const crypto = require('crypto');
function generateSignature() {
const method = 'GET',url = 'http://localhost/',encodedUrl = encodeURIComponent(url),paramaters = {
oauth_consumer_key: process.env.ETradE_SANDBox_API_KEY,oauth_signature_method: 'HMAC-SHA1',oauth_timestamp: Math.floor(Date.Now() / 1000),oauth_nonce: v1(),oauth_callback: 'oob'
}
var ordered = {};
Object.keys(paramaters).sort().forEach((key) => {
ordered[key] = paramaters[key];
});
var encodedParameters = '';
for(k in ordered) {
const encodedValue = escape(ordered[k]);
const encodedKey = encodeURIComponent(k);
if(encodedParameters === '') {
encodedParameters += encodeURIComponent(`${encodedKey}=${encodedValue}`);
} else {
encodedParameters += encodeURIComponent(`&${encodedKey}=${encodedValue}`);
}
}
encodedParameters = encodeURIComponent(encodedParameters);
const signature_base_string = `${method}&${encodedUrl}&${encodedParameters}`;
const signing_key = `${process.env.ETradE_SANDBox_SECRET_KEY}`;
const signature = crypto.createHmac("SHA1",signing_key).update(signature_base_string).digest().toString('base64');
const encodedSignature = encodeURIComponent(signature);
return encodedSignature;
}
module.exports = {
request: async (req,res) => {
console.log('Fetching request token from eTrade...');
try {
var response = await axios.get(`https://api.eTrade.com/oauth/request_token`,{
params: {
oauth_consumer_key: process.env.ETradE_SANDBox_API_KEY,oauth_signature: generateSignature(),oauth_callback: 'oob'
}
});
console.log('Fetched request token...',response.data);
} catch (error) {
console.log('Could not fetch request token...',error.response.data);
}
}
}
我已经按照互联网上的一些指南来帮助我理解签署请求的过程,但是我似乎无法获得有效的答复。
解决方法
您的代码定位到etrade生产网址。尝试调用沙盒URL。
Production https://api.etrade.com/v1/{module}/{endpoint}
Sandbox https://apisb.etrade.com/v1/{module}/{endpoint}
,
我不知道您是否还在寻找有关此问题的答案,但我认为以下内容可能会有所帮助。我在尝试与他们的 API 集成时遇到了类似的问题(虽然我使用的是 java)。查看他们的示例代码,我意识到对 baseString 进行签名的密钥需要在 request_token API 的末尾添加一个额外的“&”。从他们的示例应用程序中查看以下代码 -
if( token != null){
// not applicable for request_token API call
key = StringUtils.isEmpty(token.getOauth_token_secret()) ? context.getResouces().getSharedSecret() +"&" :
context.getResouces().getSharedSecret()+"&" + OAuth1Template.encode(token.getOauth_token_secret());
}else{
// applicable for request_token API call
key = context.getResouces().getSharedSecret() +"&";
}
AFAIK,这不是 oAuth 标准或任何东西。这似乎与他们的实施非常具体。无论如何,我更新了我的代码以在末尾包含额外的“&”并开始工作。
private String signBaseString(String httpMethod,String url) {
StringBuilder baseString = new StringBuilder();
baseString.append(encode(httpMethod)).append("&").append(encode(url)).append("&");
baseString.append(encode(normalizeParams(url)));
// the extra & is added here.
SecretKeySpec signingKey = new SecretKeySpec((this.apiSecret + "&").getBytes(),SIGN_ALG);
Mac mac = null;
try {
mac = Mac.getInstance(SIGN_ALG);
mac.init(signingKey);
return new String(Base64.encodeBase64(
mac.doFinal(baseString.toString().getBytes(
StandardCharsets.UTF_8))));
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
logger.error("Error during signing headers",e);
return null;
}
}
告诉我这是否适合您。