【问题标题】:E*Trade API frequently returns HTTP 401 Unauthorized when fetching an access token but not alwaysE*Trade API 在获取访问令牌时经常返回 HTTP 401 Unauthorized 但并非总是如此
【发布时间】:2021-04-03 04:13:33
【问题描述】:

总结

我编写了一个简单的 C# .NET Core 应用程序来使用 OAuthv1 对 E*Trade API 进行身份验证,目的是获取股票报价。我能够进行身份验证并获取请求令牌,重定向到授权页面并获取验证字符串。但是,当我使用验证器字符串执行访问令牌请求时,大约 10 次中有 9 次我得到 401 未授权。但偶尔它会起作用,我会取回访问令牌。

详情

代码

为了理智,我创建了单独的请求对象,我不会这样。同样,我能够获取请求令牌、重定向到授权并获取验证者字符串,而不是访问令牌。

    private static async Task FetchData()
    {  
        // Values
        string consumerKey = "...";
        string consumerSecret = "...";
        string requestTokenUrl = "https://api.etrade.com/oauth/request_token";
        string authorizeUrl = "https://us.etrade.com/e/t/etws/authorize";
        string accessTokenUrl = "https://api.etrade.com/oauth/access_token";
        string quoteUrl = "https://api.etrade.com/v1/market/quote/NVDA,DJI";

        // Create the request 
        var request = new OAuthRequest
        {
            Type = OAuthRequestType.RequestToken,
            ConsumerKey = consumerKey,
            ConsumerSecret = consumerSecret,
            Method = "GET",
            RequestUrl = requestTokenUrl,
            Version = "1.0",
            Realm = "etrade.com",
            CallbackUrl = "oob",
            SignatureMethod = OAuthSignatureMethod.HmacSha1
        };

        // Make call to fetch session token
        try
        {
            HttpClient client = new HttpClient();
            
            var requestTokenUrlWithQuery = $"{requestTokenUrl}?{request.GetAuthorizationQuery()}";
            var responseString = await client.GetStringAsync(requestTokenUrlWithQuery);
            var tokenParser = new TokenParser(responseString, consumerKey);

            // Call authorization API
            var authorizeUrlWithQuery = $"{authorizeUrl}?{tokenParser.GetQueryString()}";
            
            // Open browser with the above URL 
            ProcessStartInfo psi = new ProcessStartInfo
            {
                FileName = authorizeUrlWithQuery,
                UseShellExecute = true
            };
            Process.Start(psi);

            // Request input of token, copied from browser
            Console.Write("Provide auth code:");
            var authCode = Console.ReadLine();
           
            // Need auth token and verifier
            var secondRequest = new OAuthRequest
            {
                Type = OAuthRequestType.AccessToken,
                ConsumerKey = consumerKey,
                ConsumerSecret = consumerSecret,
                SignatureMethod = OAuthSignatureMethod.HmacSha1,
                Method = "GET",
                Token = tokenParser.Token,
                TokenSecret = tokenParser.Secret,
                Verifier = authCode,
                RequestUrl = accessTokenUrl,
                Version = "1.0",
                Realm = "etrade.com"
            };

            // Make access token call
            var accessTokenUrlWithQuery = $"{accessTokenUrl}?{secondRequest.GetAuthorizationQuery()}";
            responseString = await client.GetStringAsync(accessTokenUrlWithQuery);

            Console.WriteLine("Access token: " + responseString);

            // Fetch quotes
            tokenParser = new TokenParser(responseString, consumerKey);
            var thirdRequest = new OAuthRequest
            {
                Type = OAuthRequestType.ProtectedResource,
                ConsumerKey = consumerKey,
                ConsumerSecret = consumerSecret,
                SignatureMethod = OAuthSignatureMethod.HmacSha1,
                Method = "GET",
                Token = tokenParser.Token,
                TokenSecret = tokenParser.Secret,
                RequestUrl = quoteUrl,
                Version = "1.0",
                Realm = "etrade.com"
            };
            
            var quoteUrlWithQueryString = $"{quoteUrl}?{thirdRequest.GetAuthorizationQuery()}";
            responseString = await client.GetStringAsync(quoteUrlWithQueryString);

            // Dump data to console 
            Console.WriteLine(responseString);
            
        }
        catch (Exception ex)
        {
            Console.WriteLine("\n"+ ex.Message);
        }
    }

    class TokenParser {
        private readonly string consumerKey;

        public TokenParser(string responseString, string consumerKey)
        {
            NameValueCollection queryStringValues = HttpUtility.ParseQueryString(responseString);
            Token = HttpUtility.UrlDecode(queryStringValues.Get("oauth_token"));
            Secret = HttpUtility.UrlDecode(queryStringValues.Get("oauth_token_secret"));
            this.consumerKey = consumerKey;
        }

        public string Token { get; set; }
        public string Secret { get; private set; }

        public string GetQueryString()
        {
            return $"key={consumerKey}&token={Token}";
        }
    }

举个例子,在写这篇文章时,我运行了几次应用程序,它运行了一次,失败了一次。我根本没有更改代码。

【问题讨论】:

  • 取回401需要多长时间?如果是 30 秒,您可能正在寻找代理并在 30 秒后超时。您可能需要禁用代理。
  • 感谢 jdweng,每次 401 都会立即返回。在这种情况下
  • 我想知道旧连接是否正在关闭。服务器可能不允许第二次连接。如果您使用 from cmd.exe >netstat -a 您可以查看连接是否已存在。当您的代码完成时,连接应该关闭。当你失败时,我想你会看到连接仍然存在。

标签: c# oauth access-token http-status-code-401 etrade-api


【解决方案1】:

作为健全性检查,我将我的身份验证参数插入到一个会生成签名的站点中,以查看它是否与我从 OAuthRequest 中得到的相同。它不是。我决定尝试一些不同的东西。我使用 RestSharp 实现了我的逻辑,并且几乎立即就可以工作了。这是代码。

// Values
        string consumerKey = "...";
        string consumerSecret = "...";
        string baseEtradeApiUrl = "https://api.etrade.com";
        string baseSandboxEtradeApiUrl = "https://apisb.etrade.com";
        string authorizeUrl = "https://us.etrade.com";  
        
        try
        {
            // Step 1: fetch the request token
            var client = new RestClient(baseEtradeApiUrl);
            client.Authenticator = OAuth1Authenticator.ForRequestToken(consumerKey, consumerSecret, "oob");
            IRestRequest request = new RestRequest("oauth/request_token");
            var response = client.Execute(request);
            Console.WriteLine("Request tokens: " + response.Content);

            // Step 1.a: parse response 
            var qs = HttpUtility.ParseQueryString(response.Content);
            var oauthRequestToken = qs["oauth_token"];
            var oauthRequestTokenSecret = qs["oauth_token_secret"];

            // Step 2: direct to authorization page
            var authorizeClient = new RestClient(authorizeUrl);
            var authorizeRequest = new RestRequest("e/t/etws/authorize");
            authorizeRequest.AddParameter("key", consumerKey);
            authorizeRequest.AddParameter("token", oauthRequestToken);
            ProcessStartInfo psi = new ProcessStartInfo
            {
                FileName = authorizeClient.BuildUri(authorizeRequest).ToString(),
                UseShellExecute = true
            };
            Process.Start(psi);

            Console.Write("Provide auth code:");
            var verifier = Console.ReadLine();

            // Step 3: fetch access token
            var accessTokenRequest = new RestRequest("oauth/access_token");
            client.Authenticator = OAuth1Authenticator.ForAccessToken(consumerKey, consumerSecret, oauthRequestToken, oauthRequestTokenSecret, verifier);
            response = client.Execute(accessTokenRequest);
            Console.WriteLine("Access tokens: " + response.Content);

            // Step 3.a: parse response 
            qs = HttpUtility.ParseQueryString(response.Content);
            var oauthAccessToken = qs["oauth_token"];
            var oauthAccessTokenSecret = qs["oauth_token_secret"];

            // Step 4: fetch quote
            var sandboxClient = new RestClient(baseSandboxEtradeApiUrl);
            var quoteRequest = new RestRequest("v1/market/quote/GOOG.json");
            sandboxClient.Authenticator = OAuth1Authenticator.ForProtectedResource(consumerKey, consumerSecret, oauthAccessToken, oauthAccessTokenSecret);
            response = sandboxClient.Execute(quoteRequest);
            Console.WriteLine("Quotes: " + response.Content);

        } catch(Exception ex)
        {
            Console.WriteLine(ex.Message);
        }

上述逻辑有效。我对上一个问题的唯一工作理论是签名定期无效。老实说,我不知道根本原因,但这个解决方案有效,所以我很擅长。

【讨论】:

    【解决方案2】:

    我遇到了类似的问题(虽然我使用的是 JavaScript)。

    Get Request Token call (/request_token) 调用会起作用,我可以在网络浏览器中成功打开Authorize Application 页面,用户可以在其中成功授权并接收oauth_verifier 令牌。 但是,当我尝试签署 Get Access Token 请求时,我会收到 401 - oauth_problem=signature_invalid。

    原因原来是oauth_signature 和其他参数必须是百分比编码(rfc3986)。 在 Authorize Application 流程中,我们很幸运 Web 浏览器会自动对 URL 栏中的参数进行百分比编码。 但是,对于 Get Access Token 调用,这不涉及 Web 浏览器,因此 URL 参数没有得到百分比编码。

    例如,不是oauth_signature 等于abc123=,我们需要oauth_signature 等于abc123%3D。

    这可以通过 rfc3986 编码 HTTP 请求中的参数来解决。

    它在 10 次中有 1 次起作用的原因可能是因为您很幸运,参数不包含任何需要 rfc3986 编码的字符。

    【讨论】:

      猜你喜欢
      • 2021-05-26
      • 2020-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-11
      • 1970-01-01
      • 1970-01-01
      • 2017-07-12
      相关资源
      最近更新 更多