В двух предыдущих статьях этой серии « Facebook в приложении для Windows Phone 7» и « Использование идентификатора Windows Live ID в приложении для Windows Phone» вы увидели, как использовать «OAuth 2» для аутентификации с использованием идентификатора Facebook и Windows Live Id. Другие платформы социальных сетей используют «OAuth 1», который гораздо более сложен, как вы увидите в этом посте, когда мы проводим аутентификацию на Twitter. После аутентификации пользователя ваше приложение получит токен доступа, который используется для доступа к любому из API Twitter.
Прежде чем мы начнем кодировать наше приложение, я думаю, что стоит рассмотреть основы «OAuth 1». В предыдущих постах мы не делали этого с «OAuth 2», поскольку этот процесс относительно прост: создайте URL-адрес для входа в систему, попросите пользователя пройти аутентификацию и получить токен доступа. С «OAuth 1» этот процесс гораздо более сложный, и если у вас нет представления о том, что мы пытаемся сделать, может быть трудно следовать многочисленным фрагментам кода.
Процесс аутентификации с помощью «OAuth 1» происходит следующим образом (подробности см. В документации по Twitter OAuth ):
- Получить токен запроса (и связанный с ним секрет токена запроса)
- Используйте токен запроса для создания URL входа
- Отобразите элемент управления веб-браузера и перейдите к URL-адресу входа, чтобы пользователь мог ввести свои учетные данные
- После того, как пользователь аутентифицирует Пин (известный как Пин Verifier), будет отображено, что пользователь должен вырезать и вставить в приложение.
- Разобрать HTML и извлечь Verifier Pin
- Получить токен доступа с помощью запроса токена, запроса секретного токена и PIN-кода верификатора
- Используйте токен доступа в вызовах Twitter API
Шаг 1: Создание приложения Twitter
Откройте веб-браузер и перейдите на страницу разработчика Twitter . Чтобы создать приложение, вам нужно войти в систему с набором учетных данных Twitter. Опять же, мы рекомендуем создать выделенную учетную запись Twitter для управления вашими приложениями.
Нажмите на ссылку Мои приложения (наведите курсор мыши на свой профиль, чтобы увидеть эту ссылку), а затем нажмите кнопку Создать новое приложение. Вам будет предложено ввести информацию о приложении, которое вы создаете (рисунок 1). Помните, что большая часть этой информации будет видна пользователю, когда он авторизует ваше приложение для доступа к своей учетной записи Twitter.

После того, как вы нажмете кнопку «Создать приложение в Твиттере», вам будет присвоен ключ «Потребление» и «Секрет потребителя» (рисунок 2).

Шаг 2. Создание приложения для Windows Phone в оболочке
Чтобы проиллюстрировать процесс аутентификации на Twitter, мы будем использовать приложение, аналогичное тому, которое мы использовали в предыдущем посте. Опять же, у нас есть элемент управления WebBrowser
, Button
для запуска процесса аутентификации и два элемента управления TextBlock
для отображения информации о пользователе.
<phone:PhoneApplicationPage x:Class="TwitterSampleApp.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"> <Grid> <StackPanel x:Name="LayoutRoot" Background="Transparent"> <Button Content="Authenticate" Click="AuthenticateClick" /> <TextBlock x:Name="UserNameText" /> <TextBlock x:Name="UserIdText" /> </StackPanel> <phone:WebBrowser x:Name="AuthenticationBrowser" Visibility="Collapsed" Navigated="BrowserNavigated" IsScriptEnabled="True" /> </Grid> </phone:PhoneApplicationPage>
Шаг 3: Получить токен запроса
Чтобы получить токен доступа для использования с API-интерфейсами Twitter, сначала необходимо получить токен запроса. Это будет использоваться при подготовке URL-адреса для входа, на который вы отправите пользователя, чтобы Twitter аутентифицировал пользователя и авторизовал ваше приложение для доступа к учетной записи пользователя в Twitter. Получение токена запроса — это просто вопрос создания надлежащим образом отформатированного HttpWebRequest
. Я заранее приношу свои извинения, поскольку для выполнения такой простой задачи достаточно кода!
Мы начнем с метода AuthenticateClick
который вызывается, когда пользователь нажимает кнопку Authenticate. Я оставил этот метод коротким, чтобы вы могли увидеть основные шаги: создать HttpWebRequest
(для получения токена запроса), проанализировать ответ для запроса токена и секретного запроса токена, создать URL-адрес для входа с помощью токена и, наконец, перейдите к WebBrowser
управления WebBrowser
по URL-адресу входа.
private const string OAuthConsumerKeyKey = "oauth_consumer_key"; private const string OAuthVersionKey = "oauth_version"; private const string OAuthSignatureMethodKey = "oauth_signature_method"; private const string OAuthSignatureKey = "oauth_signature"; private const string OAuthTimestampKey = "oauth_timestamp"; private const string OAuthNonceKey = "oauth_nonce"; private const string OAuthTokenKey = "oauth_token"; private const string OAuthTokenSecretKey = "oauth_token_secret"; private const string OAuthVerifierKey = "oauth_verifier"; private const string OAuthPostBodyKey = "post_body"; private const string RequestUrl = "http://api.twitter.com/oauth/request_token"; private const string AuthorizeUrl = "http://api.twitter.com/oauth/authorize"; private const string AccessUrl = "http://api.twitter.com/oauth/access_token"; private string token; private string tokenSecret; private string pin; private void AuthenticateClick(object sender, RoutedEventArgs e) { // Create the Request var request = CreateRequest("POST", RequestUrl); request.BeginGetResponse(result => { try { var req = result.AsyncState as HttpWebRequest; if (req == null) throw new ArgumentNullException("result", "Request parameter is null"); using (var resp = req.EndGetResponse(result)) using (var strm = resp.GetResponseStream()) using (var reader = new StreamReader(strm)) { var responseText = reader.ReadToEnd(); // Parse out the request token ExtractTokenInfo(responseText); // Navigate to the authorization Url var loginUrl = new Uri(AuthorizeUrl + "?" + OAuthTokenKey + "=" + token); Dispatcher.BeginInvoke(() => AuthenticationBrowser.Navigate(loginUrl)); } } catch { Dispatcher.BeginInvoke(() => MessageBox.Show("Unable to retrieve request token")); } }, request); }
Давайте разберем каждый из этих шагов немного дальше, начиная с метода CreateRequest
. Параметры — это способ создания веб-запроса, в данном случае «POST», и URL-адрес для запроса. К сожалению, это не так просто, как просто создать HttpWebRequest
и установить «Метод». Перед этим нам нужно сгенерировать сигнатуру (по сути, хэш отправляемых параметров), который используется для установки заголовка «Авторизация».
private WebRequest CreateRequest(string httpMethod, string requestUrl) { var requestParameters = new Dictionary<string, string>(); var secret = ""; if (!string.IsNullOrEmpty(token)) { requestParameters[OAuthTokenKey] = token; secret = tokenSecret; } if (!string.IsNullOrEmpty(pin)) { requestParameters[OAuthVerifierKey] = pin; } var url = new Uri(requestUrl); var normalizedUrl = requestUrl; if (!string.IsNullOrEmpty(url.Query)) { normalizedUrl = requestUrl.Replace(url.Query, ""); } var signature = GenerateSignature(httpMethod, normalizedUrl, url.Query, requestParameters, secret); requestParameters[OAuthSignatureKey] = UrlEncode(signature); var request = WebRequest.CreateHttp(normalizedUrl); request.Method = httpMethod; request.Headers[HttpRequestHeader.Authorization] = GenerateAuthorizationHeader(requestParameters); return request; }
GenerateSignature
метод GenerateSignature
вы заметите, что он использует константы ConsumerKey
и ConsumerSecret
. Убедитесь, что вы заменили оба и
(включая угловые скобки) с «Ключом потребителя» и «Секретом потребителя», выпущенным Twitter. На этом этапе я еще раз повторю, что вам не нужно жестко кодировать их или показывать их в рабочем приложении; вместо этого разместите их позади защищенного веб-сервиса и извлекайте их по мере необходимости.
Метод GenerateSignature
включает создание базы сигнатур (которая, по сути, является конкатенацией параметров запроса), которая затем используется для вычисления сигнатуры с использованием алгоритма «HMACSHA1».
private const string OAuthVersion = "1.0"; private const string Hmacsha1SignatureType = "HMAC-SHA1"; private const string ConsumerKey = "<consumer_key>"; private const string ConsumerSecret = "<consumer_secret>"; public string GenerateSignature(string httpMethod, string normalizedUrl, string queryString, IDictionary<string, string> requestParameters, string secret = null) { requestParameters[OAuthConsumerKeyKey] = ConsumerKey; requestParameters[OAuthVersionKey] = OAuthVersion; requestParameters[OAuthNonceKey] = GenerateNonce(); requestParameters[OAuthTimestampKey] = GenerateTimeStamp(); requestParameters[OAuthSignatureMethodKey] = Hmacsha1SignatureType; string signatureBase = GenerateSignatureBase(httpMethod, normalizedUrl, queryString, requestParameters); var hmacsha1 = new HMACSHA1(); var key = string.Format("{0}&{1}", UrlEncode(ConsumerSecret), string.IsNullOrEmpty(secret) ? "" : UrlEncode(secret)); hmacsha1.Key = Encoding.UTF8.GetBytes(key); var signature = ComputeHash(signatureBase, hmacsha1); return signature; } private static readonly Random Random = new Random(); public static string GenerateNonce() { // Random number between 123456 and 9999999 return Random.Next(123456, 9999999).ToString(); } public static string GenerateTimeStamp() { var now = DateTime.UtcNow; TimeSpan ts = now - new DateTime(1970, 1, 1, 0, 0, 0, 0); return Convert.ToInt64(ts.TotalSeconds).ToString(); } public static string GenerateSignatureBase(string httpMethod, string normalizedUrl, string queryString, IDictionary<string, string> requestParameters) { var parameters = new List<KeyValuePair<string, string>>(GetQueryParameters(queryString)) { new KeyValuePair<string, string>(OAuthVersionKey, requestParameters[OAuthVersionKey]), new KeyValuePair<string, string>(OAuthNonceKey, requestParameters[OAuthNonceKey]), new KeyValuePair<string, string>(OAuthTimestampKey, requestParameters[OAuthTimestampKey]), new KeyValuePair<string, string>(OAuthSignatureMethodKey, requestParameters[OAuthSignatureMethodKey]), new KeyValuePair<string, string>(OAuthConsumerKeyKey, requestParameters[OAuthConsumerKeyKey]) }; if (requestParameters.ContainsKey(OAuthVerifierKey)) { parameters.Add(new KeyValuePair<string, string>(OAuthVerifierKey, requestParameters[OAuthVerifierKey])); } if (requestParameters.ContainsKey(OAuthTokenKey)) { parameters.Add(new KeyValuePair<string, string>(OAuthTokenKey, requestParameters[OAuthTokenKey])); } parameters.Sort((kvp1, kvp2) => { if (kvp1.Key == kvp2.Key) { return string.Compare(kvp1.Value, kvp2.Value); } return string.Compare(kvp1.Key, kvp2.Key); }); var parameterString = BuildParameterString(parameters); if (requestParameters.ContainsKey(OAuthPostBodyKey)) { parameterString += "&" + requestParameters[OAuthPostBodyKey]; } var signatureBase = new StringBuilder(); signatureBase.AppendFormat("{0}&", httpMethod); signatureBase.AppendFormat("{0}&", UrlEncode(normalizedUrl)); signatureBase.AppendFormat("{0}", UrlEncode(parameterString)); return signatureBase.ToString(); } private static IEnumerable<KeyValuePair<string, string>> GetQueryParameters(string queryString) { var parameters = new List<KeyValuePair<string, string>>(); if (string.IsNullOrEmpty(queryString)) return parameters; queryString = queryString.Trim('?'); return (from pair in queryString.Split('&') let bits = pair.Split('=') where bits.Length == 2 select new KeyValuePair<string, string>(bits[0], bits[1])).ToArray(); } private static string BuildParameterString(IEnumerable<KeyValuePair<string, string>> parameters) { var sb = new StringBuilder(); foreach (var parameter in parameters) { if (sb.Length > 0) sb.Append('&'); sb.AppendFormat("{0}={1}", parameter.Key, parameter.Value); } return sb.ToString(); } /// <summary> /// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986. /// </summary> private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" }; private static readonly char[] HexUpperChars = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; public static string UrlEncode(string value) { // Start with RFC 2396 escaping by calling the .NET method to do the work. // This MAY sometimes exhibit RFC 3986 behavior (according to the documentation). // If it does, the escaping we do that follows it will be a no-op since the // characters we search for to replace can't possibly exist in the string. var escaped = new StringBuilder(Uri.EscapeDataString(value)); foreach (string t in UriRfc3986CharsToEscape) { escaped.Replace(t, HexEscape(t[0])); } return escaped.ToString(); } public static string HexEscape(char character) { var to = new char[3]; int pos = 0; EscapeAsciiChar(character, to, ref pos); return new string(to); } private static void EscapeAsciiChar(char ch, char[] to, ref int pos) { to[pos++] = '%'; to[pos++] = HexUpperChars[(ch & 240) >> 4]; to[pos++] = HexUpperChars[ch & 'x000f']; } private static string ComputeHash(string data, HashAlgorithm hashAlgorithm) { byte[] dataBuffer = Encoding.UTF8.GetBytes(data); byte[] hashBytes = hashAlgorithm.ComputeHash(dataBuffer); return Convert.ToBase64String(hashBytes); }
Возвращаясь к методу CreateRequest
оставшимся шагом является создание заголовка «Авторизация». Метод GenerateAuthorizationHeader
объединяет параметры запроса, включая только что созданную подпись, с префиксом OAuth. Вернувшись в метод CreateRequest
он назначается заголовку «Авторизация».
public static string GenerateAuthorizationHeader(IDictionary<string, string> requestParameters) { var paras = new StringBuilder(); foreach (var param in requestParameters) { if (paras.Length > 0) paras.Append(","); paras.Append(param.Key + "="" + param.Value + """); } return "OAuth " + paras; }
Возвращаясь к методу AuthorizeClick
, после возвращения веб-запроса ExtractTokenInfo
запроса и секретный ExtractTokenInfo
запроса могут быть извлечены из возвращенной строки с помощью метода ExtractTokenInfo
. Строка ответа представляет собой серию пар «ключ = значение», разделенных «&», что делает извлечение пар относительно прямым.
private IEnumerable<KeyValuePair<string, string>> ExtractTokenInfo(string responseText) { if (string.IsNullOrEmpty(responseText)) return null; var responsePairs = (from pair in responseText.Split('&') let bits = pair.Split('=') where bits.Length == 2 select new KeyValuePair<string, string>(bits[0], bits[1])).ToArray(); token = responsePairs .Where(kvp => kvp.Key == OAuthTokenKey) .Select(kvp => kvp.Value).FirstOrDefault(); tokenSecret = responsePairs .Where(kvp => kvp.Key == OAuthTokenSecretKey) .Select(kvp => kvp.Value).FirstOrDefault(); return responsePairs; }
Сюда входит код для получения токена запроса. В методе AuthorizeClick
этот токен затем используется для создания loginUrl
. Чтобы заставить пользователя войти в систему, все, что вам нужно сделать, это переместить элемент управления WebBrowser
на loginUrl
, что также делается в методе AuthorizeClick
.
Шаг 4. Пользователь входит в систему и утверждает приложение
Пользователю будет предложено ввести свои учетные данные, а затем экран, содержащий PIN-код (PIN-код верификатора), который ему предположительно придется вырезать и вставить в приложение (рисунок 3).

Шаг 5: Извлеките булавку проверки
После того, как пользователь ввел свои учетные данные, WebBrowser
браузер возвращается к AuthorizeUrl
. Обратите внимание, что когда вы подписались на приложение в Twitter, вы не указали URL обратного вызова; в этом случае Twitter предполагает, что вы хотите, чтобы пользователь был перенаправлен обратно на AuthorizeUrl
(см. константу, определенную ранее в коде) с выводом Pin Verifier для пользователя.
Вместо того, чтобы пользователь скопировал ПИН-код в приложение, ваше приложение может просто проанализировать HTML-код и извлечь ПИН-код Verifier. Обратите внимание, что в следующем коде мы используем регулярное выражение для определения местоположения Pin. Если Twitter может изменить структуру этой страницы, возможно, вам придется изменить это регулярное выражение. Таким образом, может случиться так, что вы захотите, чтобы регулярное выражение находилось где-то на сервере и чтобы приложение периодически получало его. Если вам нужно изменить регулярное выражение, вы можете сделать это на сервере без необходимости изменять и повторно публиковать ваше приложение.
private void BrowserNavigated(object sender, NavigationEventArgs e){ if (AuthenticationBrowser.Visibility == Visibility.Collapsed) { AuthenticationBrowser.Visibility = Visibility.Visible; } if (e.Uri.AbsoluteUri.ToLower().Replace("https://", "http://") == AuthorizeUrl) { var htmlString = AuthenticationBrowser.SaveToString(); var pinFinder = new Regex(@"<DIV id=oauth_pin>(?<pin>[A-Za-z0-9_]+)</DIV>", RegexOptions.IgnoreCase); var match = pinFinder.Match(htmlString); if (match.Length > 0) { var group = match.Groups["pin"]; if (group.Length > 0) { pin = group.Captures[0].Value; if (!string.IsNullOrEmpty(pin)) { RetrieveAccessToken(); } } } if (string.IsNullOrEmpty(pin)){ Dispatcher.BeginInvoke(() => MessageBox.Show("Authorization denied by user")); } // Make sure pin is reset to null pin = null; AuthenticationBrowser.Visibility = Visibility.Collapsed; } }
Шаг 6: Извлеките токен доступа
В предыдущем коде после извлечения Pin вызывается метод RetrieveAccessToken
. Это выполняет еще один HttpWebRequest
для Twitter, обменивая HttpWebRequest
на HttpWebRequest
доступа. Это соответствует тому же шаблону, который использовался ранее, хотя, если вы посмотрите код для создания заголовка «Авторизация», вы увидите, что теперь он включает в себя Pin Verifier. Ответ анализируется, чтобы получить токен доступа и соответствующий секретный токен доступа, а также идентификатор и имя пользователя, прошедшего проверку подлинности.
public void RetrieveAccessToken() { var request = CreateRequest("POST", AccessUrl); request.BeginGetResponse( result => { try { var req = result.AsyncState as HttpWebRequest; if (req == null) throw new ArgumentNullException("result", "Request is null"); using (var resp = req.EndGetResponse(result)) using (var strm = resp.GetResponseStream()) using (var reader = new StreamReader(strm)) { var responseText = reader.ReadToEnd(); var userInfo = ExtractTokenInfo(responseText); Dispatcher.BeginInvoke(() => { MessageBox.Show("Access granted"); UserIdText.Text = userInfo.Where(kvp => kvp.Key == "user_id") .Select(kvp => kvp.Value).FirstOrDefault(); UserNameText.Text = userInfo.Where(kvp => kvp.Key == "screen_name") .Select(kvp => kvp.Value).FirstOrDefault(); }); } } catch { Dispatcher.BeginInvoke(() => MessageBox.Show("Unable to retrieve Access Token")); } }, request); }
В этом посте вы видели, как пройти аутентификацию на Twitter. Вы можете использовать токен доступа, полученный в этом процессе, для отправки запросов или публикации обновлений в Twitter. Более подробная информация доступна через портал разработчика Twitter .