Summary
我编写了一个简单的 C# .NET Core 应用程序,使用 OAuthv1 对 E*Trade API 进行身份验证,目的是获取股票报价。我能够进行身份验证并获取请求令牌,重定向到授权页面并获取验证程序字符串。但是,当我使用验证程序字符串执行访问令牌请求时,大约十分之九的情况是我收到 401 未经授权。但有时它会起作用,我会取回访问令牌。
Details
- 我正在使用 .NET OAuth 类 OAuthRequest 来生成查询
字符串授权参数。
- 我正在使用这个APIhttps://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,它们是相同的。
Code
为了理智起见,我创建了单独的请求对象,我不会这样。同样,我能够获取请求令牌、重定向以授权并获取验证程序字符串,但不能获取访问令牌。
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}";
}
}
举个例子,在写这篇文章时,我运行了该应用程序几次,它运行了一次,失败了一次。我根本没有改变代码。