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