ASP NET Основные проблемы с токеном запроса OAuth в Twitter - PullRequest
1 голос
/ 15 марта 2020

Фон

У меня есть внутреннее приложение с настройкой приложения Twitter, и я могу запрашивать и извлекать пользовательские твиты / данные. Это здорово, однако, сейчас у меня нет полной настройки интеграции с Twitter. Под этим я подразумеваю, что на внешнем интерфейсе пользователь может ввести любое имя пользователя Twitter, и я хочу точно знать, что введенное имя пользователя Twitter действительно принадлежит пользователю. С помощью ключа приложения Twitter вы можете получить публичные c данные Twitter для любой учетной записи Twitter, которая хорошо подходит для загрузки больших объемов данных и, в моем случае, является доказательством концептуальной работы. На данный момент мне нужно, чтобы на бэк-энде было реализовано предположение, что данные, анализируемые для определенного псевдонима в Twitter, также принадлежат пользователю учетной записи в моем веб-приложении.

предлагаемое решение для Twitter

Вот куча справочной документации, которой я пытался следовать.

https://developer.twitter.com/en/docs/basics/authentication/guides/log-in-with-twitter https://developer.twitter.com/en/docs/basics/authentication/api-reference/request_token https://oauth.net/core/1.0/#anchor9 https://oauth.net/core/1.0/#auth_step1

Я пытался следовать этому, и у меня были различные перестановки кода, опубликованного ниже (один без URL-адреса обратного вызова в качестве параметров, один с et c.) но на данный момент не сильно отличается. У меня не было никакого успеха, и это было больше чем пару дней, что убивает меня.

Код

Это моя попытка следовать спецификации OAuth, предложенной выше в документации. Обратите внимание, что это ASP. NET Core 2.2 + code. Кроме того, это код только шага 1 в руководстве Twitter для аутентификации и авторизации OAuth.

public async Task<string> GetUserOAuthRequestToken()
{
    int timestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
    string nonce = Convert.ToBase64String(Encoding.ASCII.GetBytes(timestamp.ToString()));

    string consumerKey = twitterConfiguration.ConsumerKey;
    string oAuthCallback = twitterConfiguration.OAuthCallback;

    string requestString =
        twitterConfiguration.EndpointUrl +
        OAuthRequestTokenRoute;

    string parameterString =
        $"oauth_callback={WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}&" +
        $"oauth_consumer_key={twitterConfiguration.ConsumerKey}&" +
        $"oauth_nonce={nonce}&" +
        $"oauth_signature_method=HMAC_SHA1&" +
        $"oauth_timestamp={timestamp}" +
        $"oauth_version=1.0";

    string signatureBaseString =
        "POST&" +
        WebUtility.UrlEncode(requestString) +
        "&" +
        WebUtility.UrlEncode(parameterString);

    string signingKey =
        twitterConfiguration.ConsumerSecret +
        "&" + twitterConfiguration.AccessTokenSecret;

    byte[] signatureBaseStringBytes = Encoding.ASCII.GetBytes(signatureBaseString);
    byte[] signingKeyBytes = Encoding.ASCII.GetBytes(signingKey);

    HMACSHA1 hmacSha1 = new HMACSHA1(signingKeyBytes);
    byte[] signature = hmacSha1.ComputeHash(signatureBaseStringBytes);

    string authenticationHeaderValue =
        $"oauth_nonce=\"{nonce}\", " +
        $"oauth_callback=\"{WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}\", " +
        $"oauth_signature_method=\"HMAC_SHA1\", " +
        $"oauth_timestamp=\"{timestamp}\", " +
        $"oauth_consumer_key=\"{twitterConfiguration.ConsumerKey}\", " +
        $"oauth_signature=\"{Convert.ToBase64String(signature)}\", " +
        $"oauth_version=\"1.0\"";

    HttpRequestMessage request = new HttpRequestMessage();
    request.Method = HttpMethod.Post;
    request.RequestUri = new Uri(
        baseUri: new Uri(twitterConfiguration.EndpointUrl),
        relativeUri: OAuthRequestTokenRoute);
    request.Content = new FormUrlEncodedContent(
        new Dictionary<string, string>() {
            { "oauth_callback", twitterConfiguration.OAuthCallback }
        });
    request.Headers.Authorization = new AuthenticationHeaderValue("OAuth",
        authenticationHeaderValue);

    HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request);

    if (httpResponseMessage.IsSuccessStatusCode)
    {
        return await httpResponseMessage.Content.ReadAsStringAsync();
    }
    else
    {
        return null;
    }
}

Примечания Я также попытался удалить URL-адрес обратного вызова из параметров и это не сработало. Я перепробовал все виды немного разных перестановок (urlencoded моя подпись, добавил URL обратного вызова в строку запроса, удалил ее и т. Д. c), но в этот момент я потерял отслеживание того, которое я пробовал и не сделал (кодировки , цитаты и т. д. c.).

Не обращайте внимания на тот факт, что я еще не сериализовал ответ в модель, поскольку цель состоит в том, чтобы сначала ввести код состояния успеха!

У меня есть Настройка интеграционного теста также для этого метода, и я продолжаю получать 400 Bad Request без дополнительной информации (что имеет смысл), но абсолютно не помогает с отладкой.

[Fact]
public async Task TwitterHttpClientTests_GetOAuthRequestToken_GetsToken()
{
    var result = await twitterHttpClient.GetUserOAuthRequestToken();

    Assert.NotNull(result);
}

Кроме того, у меня были некоторые другие вопросы а также:

  1. Есть ли способ проверить учетную запись пользователя в Твиттере, не проходя через поток OAuth? Причина, по которой я спрашиваю об этом, заключается в том, что прохождение потока OAuth оказывается трудным
  2. Безопасно ли выполнять первый шаг рабочего процесса входа в Twitter на серверной части и возвращать ответ клиентской части? Ответ будет содержать чувствительный токен и секрет токена. (Если бы я сам ответил на этот вопрос, я бы сказал, что вы должны сделать это таким образом, в противном случае вам придется жестко кодировать секреты приложения в конфигурации внешнего интерфейса, что хуже). Я спрашиваю об этом, потому что это было в моем сознании, так как я начал это, и я немного волнуюсь.
  3. Есть ли вспомогательная библиотека OAuth для C# ASP. NET Ядро, которое может сделать это проще?

Ответы [ 2 ]

1 голос
/ 18 марта 2020

После нескольких часов просмотра документации я нашел ответ. Оказывается, я пропустил некоторые мелкие детали из руководств.

  1. При отправке запроса на oauth / request_token , когда вы подписываете запрос, вы не используете секрет токена доступа. (для этого уточняется c запрос). Также см. Раздел «Получение ключа подписи» в руководстве по подписанию запроса и прочитайте последние несколько параграфов. Поэтому ключ подписи не имеет секретного ключа доступа
  2. . Вы должны UrlEncode для каждого ключа и значения. Вы также должны UrlEncode код авторизации.

Я опубликую обновленный код для вас здесь, на случай, если вам понадобится это в C#. Обратите внимание, что этот код не является чистым. Вы должны разделить функциональность OAuth на какой-то другой класс. Это была моя попытка заставить его работать.

public async Task<string> GetUserOAuthRequestToken()
{
    int timestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
    string nonce = Convert.ToBase64String(Encoding.ASCII.GetBytes(timestamp.ToString()));

    string consumerKey = twitterConfiguration.ConsumerKey;
    string oAuthCallback = twitterConfiguration.OAuthCallback;

    string requestString =
        twitterConfiguration.EndpointUrl +
        OAuthRequestTokenRoute;

    string parameterString =
        $"oauth_callback={WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}&" +
        $"oauth_consumer_key={WebUtility.UrlEncode(twitterConfiguration.ConsumerKey)}&" +
        $"oauth_nonce={WebUtility.UrlEncode(nonce)}&" +
        $"oauth_signature_method={WebUtility.UrlEncode(OAuthSigningAlgorithm)}&" +
        $"oauth_timestamp={WebUtility.UrlEncode(timestamp.ToString())}&" +
        $"oauth_version={WebUtility.UrlEncode("1.0")}";

    string signatureBaseString =
        "POST&" +
        WebUtility.UrlEncode(requestString) +
        "&" +
        WebUtility.UrlEncode(parameterString);

    string signingKey =
        WebUtility.UrlEncode(twitterConfiguration.ConsumerSecret) +
        "&";

    byte[] signatureBaseStringBytes = Encoding.ASCII.GetBytes(signatureBaseString);
    byte[] signingKeyBytes = Encoding.ASCII.GetBytes(signingKey);

    HMACSHA1 hmacSha1 = new HMACSHA1(signingKeyBytes);
    byte[] signature = hmacSha1.ComputeHash(signatureBaseStringBytes);
    string base64Signature = Convert.ToBase64String(signature);

    string authenticationHeaderValue =
        $"oauth_nonce=\"{WebUtility.UrlEncode(nonce)}\", " +
        $"oauth_callback=\"{WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}\", " +
        $"oauth_signature_method=\"{WebUtility.UrlEncode(OAuthSigningAlgorithm)}\", " +
        $"oauth_timestamp=\"{WebUtility.UrlEncode(timestamp.ToString())}\", " +
        $"oauth_consumer_key=\"{WebUtility.UrlEncode(twitterConfiguration.ConsumerKey)}\", " +
        $"oauth_signature=\"{WebUtility.UrlEncode(base64Signature)}\", " +
        $"oauth_version=\"{WebUtility.UrlEncode("1.0")}\"";

    HttpRequestMessage request = new HttpRequestMessage();
    request.Method = HttpMethod.Post;
    request.RequestUri = new Uri(
        baseUri: new Uri(twitterConfiguration.EndpointUrl),
        relativeUri: OAuthRequestTokenRoute);
    request.Headers.Authorization = new AuthenticationHeaderValue("OAuth",
        authenticationHeaderValue);

    HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request);

    if (httpResponseMessage.IsSuccessStatusCode)
    {
        string response = await httpResponseMessage.Content.ReadAsStringAsync();
        return response;
    }
    else
    {
        return null;
    }
}
1 голос
/ 16 марта 2020

Я решил эту проблему, написав юнит-тесты и проработав в Твиттере документацию по Создание подписи . Поскольку в этом примере представлены ключи и результаты, можно проверить правильность вашего кода.

Поскольку вы спрашивали о библиотеках - я написал LINQ to Twitter с надеждой помочь другим, таким как я, в это трудная задача.

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

  1. Во-первых, у меня есть OAuthController с действием Begin для перенаправления пользователя для запуска процесса аутентификации:
public async Task<ActionResult> Begin()
{
    //var auth = new MvcSignInAuthorizer
    var auth = new MvcAuthorizer
    {
        CredentialStore = new SessionStateCredentialStore(HttpContext.Session)
        {
            ConsumerKey = configuration["Twitter:ConsumerKey"],
            ConsumerSecret = configuration["Twitter:ConsumerSecret"]
        }
    };

    string twitterCallbackUrl = Request.GetDisplayUrl().Replace("Begin", "Complete");
    return await auth.BeginAuthorizationAsync(new Uri(twitterCallbackUrl));
}

Обратите внимание, что он использует MvcSignInAuthorizer, передавая учетные данные через свойство CredentialStore. Если бы вы использовали свой собственный необработанный код, вы бы настроили HTTP-запрос с заголовком Authorization.

Далее, обратите внимание, что я изменяю текущий URL, чтобы он ссылался на тот же контроллер, но с конечной точкой Complete. Это oauth_callback, которое отправляется на авторизацию в Twitter.

Этот процесс перенаправляет пользователя на веб-сайт Twitter, он авторизует ваше приложение, а затем использует oauth_callback, чтобы перенаправить пользователя обратно на ваш сайт. Вот как вы справляетесь с этим:
public async Task<ActionResult> Complete()
{
    var auth = new MvcAuthorizer
    {
        CredentialStore = new SessionStateCredentialStore(HttpContext.Session)
    };

    await auth.CompleteAuthorizeAsync(new Uri(Request.GetDisplayUrl()));

    // This is how you access credentials after authorization.
    // The oauthToken and oauthTokenSecret do not expire.
    // You can use the userID to associate the credentials with the user.
    // You can save credentials any way you want - database, 
    //   isolated storage, etc. - it's up to you.
    // You can retrieve and load all 4 credentials on subsequent 
    //   queries to avoid the need to re-authorize.
    // When you've loaded all 4 credentials, LINQ to Twitter will let 
    //   you make queries without re-authorizing.
    //
    //var credentials = auth.CredentialStore;
    //string oauthToken = credentials.OAuthToken;
    //string oauthTokenSecret = credentials.OAuthTokenSecret;
    //string screenName = credentials.ScreenName;
    //ulong userID = credentials.UserID;
    //

    return RedirectToAction("Index", "Home");
}

Опять же, вы можете видеть, что я использую MvcAuthorizer и выполняю запрос. После выполнения запроса вы сможете вытащить oauth_token и oauth_token_secret, а также screen_name и user_id. Вы можете сохранить эти артефакты и повторно использовать их для всех последующих действий этого пользователя, улучшая их работу, поскольку им не нужно входить в систему каждый раз, когда вам нужно сделать запрос.

По вашему вопросу о для проверки есть Проверка учетных данных конечная точка.

LINQ to Twitter имеет ASP. NET Пример ядра , Примеры API со 100% API и полной документацией на Wiki , если вы хотите узнать больше.

...