如何解决E*Trade API 在获取访问令牌时经常返回 HTTP 401 Unauthorized 但并非总是如此
总结
我编写了一个简单的 C# .NET Core 应用程序来使用 OAuthv1 对 E*Trade API 进行身份验证,目的是获取股票报价。我能够进行身份验证并获取请求令牌,重定向到授权页面并获取验证器字符串。但是,当我使用验证器字符串执行访问令牌请求时,大约 10 次中有 9 次我得到 401 未授权。但偶尔它会起作用,我会取回访问令牌。
详情
- 我正在使用 .NET OAuth 类 OAuthRequest 来生成查询 字符串授权参数。
- 我正在使用这个 API https://apisb.etrade.com/docs/api/authorization/get_access_token.html#
- 我已经下载了这个示例应用程序并比较了 URL 使用并没有发现可以解释这一点的重大差异 行为。 https://cdn2.etrade.net/1/18122609420.0/aempros/content/dam/etrade/developer-site/en_US/document/downloads/EtradePythonClient.zip
- 示例应用程序每次都可以使用我的凭据,所以我知道它们可以正常工作。 C# 代码生成导致此问题的签名(可能)的方式存在一些差异,这显然是不确定的,因为有时我的应用程序可以工作。
- 我比较了示例应用和我的用于身份验证的 URL,它们是相同的。
为了理智起见,我已经创建了单独的请求对象,我不会这样做。同样,我能够获取请求令牌,重定向到授权并获取验证器字符串,而不是访问令牌。
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,SignatureMethod = OAuthSignatureMethod.HmacSha1,Token = tokenParser.Token,TokenSecret = tokenParser.Secret,Verifier = authCode,RequestUrl = accesstokenUrl,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,RequestUrl = quoteUrl,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}";
}
}
举个例子,在写这篇文章时,我运行了几次应用程序,它运行了一次,失败了一次。我根本没有更改代码。
解决方法
作为健全性检查,我将我的身份验证参数插入一个站点,该站点将生成签名,只是为了查看它是否与我从 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,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,oauthAccessToken,oauthAccessTokenSecret);
response = sandboxClient.Execute(quoteRequest);
Console.WriteLine("Quotes: " + response.Content);
} catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
上述逻辑有效。我对上一个问题的唯一工作理论是签名定期无效。老实说,我不知道根本原因,但这个解决方案很有效,所以我很满意。
,我遇到了类似的问题(虽然我使用的是 JavaScript)。
Get Request Token call (/request_token
) 调用有效,我可以在网络浏览器中成功打开 Authorize Application 页面,用户可以在其中成功授权并接收 {{1}令牌。
但是,当我尝试签署 Get Access Token 请求时,我会收到 401 - oauth_problem=signature_invalid。
原因原来是 oauth_verifier
和其他参数必须是百分比编码的 (rfc3986)。
在授权应用程序流的情况下,我们很幸运,Web 浏览器会自动对 URL 栏中的参数进行百分比编码。
但是,对于 Get Access Token 调用,这不涉及 Web 浏览器,因此 URL 参数未进行百分比编码。
例如,不是 oauth_signature
等于 oauth_signature
,我们需要 abc123=
等于 oauth_signature
。
这可以通过对 HTTP 请求中的参数进行 rfc3986 编码来解决。
它运行 10 次中有 1 次的原因可能是因为您很幸运参数不包含任何需要 rfc3986 编码的字符。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。