Теперь, когда у нас есть готовая базовая инфраструктура , пришло время протестировать сам механизм аутентификации. Таким образом, у нас есть основной экран, который просит пользователя получить 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 — круг проверки подлинности завершен.