#c# #oauth #access-token #http-status-code-401 #etrade-api
#c# #oauth #токен доступа #http-status-code-401 #etrade-api
Вопрос:
Краткие сведения
Я написал простое приложение на C # .NET Core для аутентификации в E * Trade API с использованием OAuthv1 с целью получения котировок акций. Я могу пройти аутентификацию и получить токен запроса, перенаправить на страницу авторизации и получить строку проверки. Однако, когда я использую строку проверки для выполнения запроса токена доступа, примерно в 9 случаях из 10 я получаю 401 несанкционированный. Но иногда это работает, и я получаю токен доступа обратно.
Подробные сведения
- Я использую .ЧИСТЫЙ класс 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,
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}amp;token={Token}";
}
}
В качестве примера, во время написания этого поста я пару раз запускал приложение, и оно сработало один раз и один раз потерпело неудачу. Я вообще не менял код.
Комментарии:
1. Сколько времени потребуется, чтобы вернуть 401-й номер? Если это 30 секунд, вы можете искать прокси и получать тайм-аут через 30 секунд. Возможно, вам придется отключить прокси-сервер.
2. Спасибо jdweng, 401-й возвращается немедленно каждый раз. В данном случае
3. Мне интересно, закрывается ли старое соединение. Сервер может не разрешить 2-е соединение. Если вы используете из cmd.exe > netstat -a вы можете увидеть, существует ли уже соединение. Когда ваш код завершится, соединение должно закрыться. Когда вы потерпите неудачу, я думаю, вы увидите, что связь все еще существует.
Ответ №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
) будет работать, и я мог бы успешно открыть страницу авторизации приложения в веб-браузере, где пользователь мог бы успешно авторизовать и получить oauth_verifier
токен. Однако, когда я пытался подписать запрос на получение токена доступа, я получал 401 — oauth_problem=signature_invalid .
Причина оказалась в том, что oauth_signature
и другие параметры должны быть закодированы в процентах (rfc3986). В случае потока авторизации приложения нам повезло, что веб-браузер автоматически закодирует параметры в процентах в строке URL. Однако для вызова Get Access Token это не связано с веб-браузером, поэтому параметры URL не кодировались в процентах.
Например, вместо oauth_signature
равно abc123=
, нам нужно oauth_signature
равно abc123=
.
Это можно исправить с помощью rfc3986-кодирования параметров в HTTP-запросах.
Причина, по которой он работал 1 раз из 10, вероятно, потому, что вам повезло, что параметры не содержали никаких символов, которые должны были быть закодированы в rfc3986.