NodeJS 如何使用eTrade API为请求令牌签名?

fquxozlt  于 2023-08-04  发布在  Node.js
关注(0)|答案(3)|浏览(98)

我正在尝试为一个项目连接eTrade的API。他们使用OAuth v1流。我在流程的第一步--获取请求令牌--遇到了错误。从他们的文档的信息可以找到here
我得到的错误是401-无效签名。
我一直在阅读关于OAuth流程的文章,并熟悉了这个过程。在过去,我使用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_method: 'HMAC-SHA1',
          oauth_signature: generateSignature(),
          oauth_timestamp: Math.floor(Date.now() / 1000),
          oauth_nonce: v1(),
          oauth_callback: 'oob'
        }
      });

      console.log('Fetched request token...', response.data);
    } catch (error) {
      console.log('Could not fetch request token...', error.response.data);
    }
  }
}


我已经遵循了互联网上的一些指南,以帮助我了解签署请求的过程,但我似乎不能得到一个有效的回应。

13z8s7eq

13z8s7eq1#

您的代码以etrade生产URL为目标。尝试调用沙盒URL。

Production  https://api.etrade.com/v1/{module}/{endpoint}
Sandbox     https://apisb.etrade.com/v1/{module}/{endpoint}

字符串

6ojccjat

6ojccjat2#

我不知道你是否还在寻找这个问题的答案,但我认为以下内容可能会有所帮助。我遇到了一个类似的问题,而我试图与他们的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;
    }
  }


让我知道如果这对你有用。

ghhaqwfi

ghhaqwfi3#

我有一个应用程序使用TDAmeritrade,我已经运行多年,他们是非常缓慢的释放信息过渡到嘉信理财,所以我正在探索的选择。不幸的是,我正在使用 Delphi ,而几乎没有其他人这样做。
Delphi 确实有OAuth 1类,但似乎不起作用。但是HMAC_SHA1的类确实可以工作,所以我尝试直接构建授权字符串,然后对其进行散列。上面的例子基本上就是我正在做的事情。当我传递一个在OAuth文档中找到的字符串时,我会得到正确的哈希值。但是我试着在Etrade中建立这个例子,我不能得到正确的结果,当我试着调用请求令牌时,当然我得到了未经授权的无效签名。
看到上面的例子,我希望我能得到一些调试的BaseString被传递到散列。
我访问了https://oauth.net/core/1.0a/#sig_base_example,找到了一个生成要散列的基本字符串的示例。示例基本字符串为GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg% 26 oauth_consumer_key% 3Ddpf43f3p2l4k3l03% 26 oauth_nonce% 3Dkllo 9940 pd 9333 jh % 26 oauth_signature_method%3DHMAC-SHA1% 26 oauth_timestamp% 3D1191242096% 26 oauth_token% 3Dnnch 734 d 00 sl 2 jdk % 26 oauth_version% 3D 1. 0% 26 size %3Doriginal
它表示将此字符串传递给HMAC-SHA1,密钥为kd94hf93k423kf44&pfkkdhi9sl3r4s00,结果签名为tR 3 + Ty 81 lMeYAr/Fid 0 kMTYa/WM=
我的哈希返回正确的哈希。

AString :=
  'GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg' +
  '%26oauth_consumer_key%3Ddpf43f3p2l4k3l03' +
  '%26oauth_nonce%3Dkllo9940pd9333jh' +
  '%26oauth_signature_method%3DHMAC-SHA1' +
  '%26oauth_timestamp%3D1191242096' +
  '%26oauth_token%3Dnnch734d00sl2jdk' +
  '%26oauth_version%3D1.0' +
  '%26size%3Doriginal';
result := EncodeBase64(THashSHA1.GetHMACAsBytes(AString, 'kd94hf93k423kf44&pfkkdhi9sl3r4s00'));
Memo1.Lines.Add('Test THashSHA1 ' + result);

字符串
如果任何人都可以张贴示例签名字符串被散列,我将不胜感激。

相关问题