Nano Hash - криптовалюты, майнинг, программирование

E * Trade API часто возвращает HTTP 401 Unauthorized при получении токена доступа, но не всегда

Резюме

Я написал простое приложение C # .NET Core для аутентификации по E * Trade API с использованием OAuthv1 с целью получения котировок акций. Я могу пройти аутентификацию и получить токен запроса, перенаправить на страницу авторизации и получить строку верификатора. Однако, когда я использую строку верификатора для выполнения запроса токена доступа, примерно в 9 случаях из 10 я получаю 401 несанкционированный доступ. Но иногда это срабатывает, и я получаю обратно токен доступа.

Подробности

  • Я использую класс OAuthRequest .NET OAuth для создания параметров авторизации строки запроса.
  • Я использую этот 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}&token={Token}";
        }
    }

Например, при написании этого поста я пару раз запускал приложение, один раз оно сработало, а один раз не удалось. Я вообще не менял код.


  • Сколько времени нужно, чтобы вернуть 401? Если это 30 секунд, возможно, вы ищете прокси и получаете тайм-аут через 30 секунд. Возможно, вам потребуется отключить прокси. 24.12.2020
  • Спасибо jdweng, 401 возвращается каждый раз сразу же. В таком случае 26.12.2020
  • Мне интересно, закрывается ли старое соединение. Сервер может не разрешить второе соединение. Если вы используете from cmd.exe ›netstat -a, вы можете увидеть, существует ли уже соединение. Когда код завершится, соединение должно быть закрыто. Когда вы проиграете, я думаю, вы увидите, что связь все еще существует. 26.12.2020

Ответы:


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);
        }

Вышеупомянутая логика работает. Моя единственная рабочая теория по предыдущей проблеме заключается в том, что подпись периодически была недействительной. Честно говоря, я не знаю первопричины, но это решение работает, так что я справляюсь.

28.12.2020

2

У меня возникла аналогичная проблема (хотя я использую JavaScript).

Вызов Get Request Token (/request_token) будет работать, и я смогу успешно открыть страницу авторизовать приложение в веб-браузер, в котором пользователь мог успешно авторизоваться и получить токен oauth_verifier. Однако, когда я попытался подписать запрос получить токен доступа, я получит 401 - oauth_problem = signature_invalid.

Причина оказалась в том, что oauth_signature и другие параметры должны быть закодированы в процентах (rfc3986). В случае с потоком авторизации приложения нам повезло, что веб-браузер автоматически закодирует параметры в строке URL-адреса. Однако для вызова Get Access Token это не связано с веб-браузером, поэтому параметры URL-адреса не кодировались в процентах.

Например, вместо oauth_signature, равного abc123=, нам нужно oauth_signature, равного abc123%3D.

Это можно исправить с помощью rfc3986-кодирования параметров в HTTP-запросах.

Причина, по которой это сработало 1 раз из 10, вероятно, связана с тем, что вам повезло, что параметры не содержали никаких символов, которые необходимо было закодировать в rfc3986.

28.02.2021
Новые материалы

Кластеризация: более глубокий взгляд
Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

Как написать эффективное резюме
Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

Частный метод Python: улучшение инкапсуляции и безопасности
Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

Как я автоматизирую тестирование с помощью Jest
Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..