Статьи

Создание клиента Twitter для Windows Phone — аутентификация по ПИН

Теперь, когда у нас есть готовая базовая инфраструктура , пришло время протестировать сам механизм аутентификации. Таким образом, у нас есть основной экран, который просит пользователя получить PIN-код. В конечном итоге нам необходимо сохранить два значения — токен доступа и секрет токена, оба из которых получены через API аутентификации. В качестве вторичного идентификатора мы могли бы также хранить идентификатор Twitter, связанный с приложением.

Для кнопки Получить PIN-код мы можем легко вызвать метод PerformRequest на основе OAuthClient . Однако нам нужно внести некоторые изменения в этот механизм из-за того факта, что несколько запросов проходят через один и тот же метод, и нам нужна различная асинхронная обработка в зависимости от данных, которые мы получаем.

Внутри OAuthClient создайте новое перечисление с именем RequestType :

public enum RequestType
{
    InitialToken
}

Основной метод PerformRequest также неожиданно претерпел изменения — вместо байтового типа в конце я использую RequestType, поэтому объявление метода теперь выглядит так:

public void PerformRequest(Dictionary<string, string> parameters, string url, string consumerSecret, string token, RequestType type)

Метод GetToken теперь может оставаться с этим именем, потому что он действительно будет использоваться для получения токена, или обобщен для GetResponse — все операции Twitter будут возвращать строку в любом случае. Поэтому для повторного использования существующих компонентов я собираюсь переименовать его.

Теперь возникает другая проблема — как именно я собираюсь передать тип запроса асинхронному обратному вызову? В конце концов, общим требованием для нового экземпляра AsyncCallback является передача метода с подписью void (IAsyncResult) . Не забывайте, что вы также передаете объект AsyncState — в данном случае это просто запрос, но мы можем сделать его массивом объектов. Именно так:

request.BeginGetResponse(new AsyncCallback(GetResponse), new object[] { request, type });

Теперь сам GetResponse должен правильно обрабатывать данные, учитывая входящие объекты.

static void GetResponse(IAsyncResult result)
{
    HttpWebRequest request = (HttpWebRequest)(((object[])result.AsyncState)[0]);
    HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);

    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
    {
        RequestType currentType = (RequestType)(((object[])result.AsyncState)[1]);
        string completeString = reader.ReadToEnd();

        switch (currentType)
        {
            case RequestType.InitialToken:
                {
                    string[] data = completeString.Split(new char[] { '&' });
                    int index = data[0].IndexOf("=");
                    string token = data[0].Substring(index + 1, data[0].Length - index - 1);
                    Debug.WriteLine("TOKEN OBTAINED");

                    WebBrowserTask task = new WebBrowserTask();
                    task.Uri = new Uri("http://api.twitter.com/oauth/authorize?oauth_token=" + token);
                    task.Show();
                    break;
                }
        }
    }
}

Обратите внимание, что теперь я беру массив, переданный в качестве состояния, и назначаю соответствующие объекты, учитывая индекс, переданный в самом начале. Тип InitialToken — это запрос на получение токена и отображение PIN-кода.

Теперь мы готовы вернуться к пользовательскому интерфейсу. Нам нужно выполнить запрос на начальный токен и показать пин-код. Итак, вот оно:

private void btnAcquire_Click(object sender, RoutedEventArgs e)
{
    Dictionary<string, string> parameters = new Dictionary<string, string>();
    parameters.Add("oauth_consumer_key", AuthConstants.ConsumerKey);
    parameters.Add("oauth_callback", "oob");
    parameters.Add("oauth_signature_method", "HMAC-SHA1");
    parameters.Add("oauth_timestamp", StringHelper.UNIXTimestamp);
    parameters.Add("oauth_nonce", Guid.NewGuid().ToString().Replace("-", ""));
    parameters.Add("oauth_version", "1.0");

    OAuthClient.PerformRequest(parameters, "http://api.twitter.com/oauth/request_token", AuthConstants.ConsumerSecret, string.Empty,OAuthClient.RequestType.InitialToken);
}

Согласно документации OAuth (в сочетании с тем, что заявляет Twitter), нам нужны параметры, которые я указал выше, чтобы запросить уникальный токен приложения. Для функции обратного вызова установлено значение oob — в конечном итоге экран PIN будет отображаться, поскольку это единственный способ, которым MangoTree будет аутентифицировать пользователя.

Метка времени должна быть в формате времени UNIX, поэтому я создал свойство в классе StringHelper, которое всегда будет возвращать правильную метку времени.

public static string UNIXTimestamp
{
    get
    {
        return Convert.ToString((int)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds);
    }
}

Одноразовый номер должен быть уникальной строкой, и на данный момент я решил использовать GUID — в конечном счете, мы изменим его в целях безопасности, но сейчас это большое уникальное значение для экспериментов.

Когда запрос выполнен, я получаю три значения:

  • oauth_token
  • oath_token_secret
  • oauth_callback_confirmed 

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

Именно здесь Twitter расскажет пользователю, каково мое намерение с приложением. В тот момент, когда пользователь нажимает на Авторизовать приложение с правильными введенными учетными данными, приложение получит все разрешения, описанные выше.

После этого PIN-код появится на экране.

Пользователь должен сохранить этот код вручную — в настоящее время у меня нет возможности получить доступ к содержимому в IE9. В конечном счете, я мог бы использовать свой собственный компонент WebBrowser в самом приложении, но это создаст еще один ненужный слой. Когда пользователь скопирует этот ПИН-код, он будет использован для выполнения другого запроса, на этот раз для получения токена доступа.

Таким образом, сценарий в этом случае, скорее всего, будет выглядеть следующим образом — пользователь получит PIN-код, а затем нажмет кнопку «Назад», чтобы вернуться к приложению и ввести только что полученный код. Все это время токен и секрет токена должны быть сохранены, потому что они нам понадобятся сейчас.

Если приложение автоматически захоронено, а затем оно активируется с помощью кнопки «назад», нам не нужно ничего делать с существующими значениями, поскольку они останутся там, где они были.

Что я собираюсь сделать, это сохранить эти значения в классе приложения на данный момент:

Это, как говорится, некоторые небольшие модификации должны быть добавлены в метод GetResponse, чтобы установить правильные значения. Посмотрите на изменения:

case RequestType.InitialToken:
    {
        string[] data = completeString.Split(new char[] { '&' });
        int index = data[0].IndexOf("=");
        App.Token = data[0].Substring(index + 1, data[0].Length - index - 1);
        index = data[1].IndexOf("=");
App.TokenSecret = data[1].Substring(index + 1, data[1].Length - index - 1); WebBrowserTask task = new WebBrowserTask(); task.Uri = new Uri("http://api.twitter.com/oauth/authorize?oauth_token=" + App.Token); task.Show(); break; }

Как только я получу эти данные, давайте предположим, что пользователь ввел PIN-код. Время для другого запроса.

private void btnConfirm_Click(object sender, RoutedEventArgs e)
{
    Dictionary<string, string> parameters = new Dictionary<string, string>();
    parameters.Add("oauth_consumer_key", AuthConstants.ConsumerKey);
    parameters.Add("oauth_signature_method", "HMAC-SHA1");
    parameters.Add("oauth_timestamp", StringHelper.UNIXTimestamp);
    parameters.Add("oauth_nonce", Guid.NewGuid().ToString().Replace("-", ""));
    parameters.Add("oauth_version", "1.0");
    parameters.Add("oauth_verifier", txtPIN.Text);
    parameters.Add("oauth_token", App.Token);

     OAuthClient.PerformRequest(parameters, "https://api.twitter.com/oauth/access_token", AuthConstants.ConsumerKey, App.Token, OAuthClient.RequestType.AccessToken);
}

Здесь я должен упомянуть несколько вещей. Прежде всего, параметр верификатора — это ПИН-код, полученный пользователем. Возможно, вы также заметили, что я использую RequestType.AccessToken — новое дополнение к перечислению, которое мы имеем в OAuthClient .

Теперь мне просто нужно добавить пользовательское поведение асинхронного обратного вызова на основе нового типа.

case RequestType.AccessToken:
    {
        string[] data = completeString.Split(new char[] { '&' });
        int index = data[0].IndexOf("=");
        App.Token = data[0].Substring(index + 1, data[0].Length - index - 1);
        index = data[1].IndexOf("=");
        App.TokenSecret = data[1].Substring(index + 1, data[1].Length - index - 1);
        index = data[2].IndexOf("=");
        App.UserID = data[2].Substring(index + 1, data[2].Length - index - 1);
        index = data[3].IndexOf("=");
        App.UserName = data[3].Substring(index + 1, data[3].Length - index - 1);
        break;
    }

Обратите внимание, что я заменил существующий токен и секрет токена новыми — они будут основными элементами на нашем пути. Кроме того, я добавил еще два статических строковых поля — UserID и UserName , чтобы временно сохранить идентификатор пользователя (числовой) и его дескриптор Twitter. Как только запрос завершен с правильным PIN-кодом, вы готовы выполнить любые другие запросы в API Twitter — круг проверки подлинности завершен.