Если вы пропустили предыдущие три части, вы можете легко найти их здесь:
- Создание клиента Imgur для Windows Phone — часть 1 — Core & Main Gallery
- Создание клиента Imgur для Windows Phone — часть 2 — бесконечная прокрутка изображений
- Создание клиента Imgur для Windows Phone — часть 3 — просмотр сведений об изображении
Сегодня я собираюсь поговорить об аутентификации пользователя, потому что некоторые действия требуют входа пользователя в сервис. Imgur API использует поток аутентификации OAuth 2.0 (спецификация сервиса доступна
здесь ).
Чтобы разрешить пользователю доступ к странице аутентификации, вы можете добавить кнопку на панель приложения (она уже была там для вашего удобства в предыдущей выпадающей панели ).
Но кнопка должна куда-то идти, поэтому вам нужно создать новую страницу аутентификации, которая позволила бы пользователю вводить свои учетные данные и передавать их службе Imgur для проверки. Я добавил новый AuthPage.xaml, который не содержит ничего, кроме компонента WebBrowser .
<phone:PhoneApplicationPage
x:Class="Imagine.AuthPage"
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"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d"
shell:SystemTray.IsVisible="False">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<phone:WebBrowser x:Name="authBrowser"></phone:WebBrowser>
</Grid>
</phone:PhoneApplicationPage>
При создании начального шага аутентификации нам нужно создать специальный URL-адрес, содержащий идентификатор клиента, а также секрет клиента, который бы идентифицировал приложение в качестве источника запроса. Я собираюсь перейти к этому конкретному URL-адресу в момент загрузки страницы, поскольку его единственная цель — получить учетные данные пользователя. Это можно сделать в обработчике событий AuthPage_Loaded :
void AuthPage_Loaded(object sender, RoutedEventArgs e)
{
authBrowser.Navigate(new Uri(string.Format("https://api.imgur.com/oauth2/authorize?client_id={0}&response_type=token",
ConstantContainer.IMGUR_CLIENT_ID)));
}
Помните, что внутри я храню идентификатор клиента и секрет в классе ConstantContainer . Как только страница загрузится, вы увидите приглашение ввести имя пользователя и пароль пользователя, который хочет пройти аутентификацию:
Вы можете задаться вопросом — почему я использую тип ответа токена вместо PIN или кода. Таким образом, я могу легко разобрать код из URL, не заставляя пользователя вводить PIN-код еще раз или выполнять дублирующие HTTP-запросы. На самом деле, когда дело доходит до выбора между кодом авторизации или токеном, это сводится к личным предпочтениям, так как вам все равно придется обрабатывать обратное чтение URL.
Поскольку сам по себе обратный вызов мне не нужен, я могу перехватить URL-адрес при навигации через обработчик событий Navigating и получить токены доступа и обновления:
void authBrowser_Navigating(object sender, NavigatingEventArgs e)
{
if (e.Uri.ToString().Contains("access_token="))
{
string uriString = e.Uri.ToString();
int indexOfSharp = uriString.IndexOf("#");
string query = uriString.Substring(indexOfSharp + 1, uriString.Length - indexOfSharp - 1);
string accessToken = query.Split('&')
.Where(s => s.Split('=')[0] == "access_token")
.Select(s => s.Split('=')[1])
.FirstOrDefault();
string refreshToken = query.Split('&')
.Where(s => s.Split('=')[0] == "refresh_token")
.Select(s => s.Split('=')[1])
.FirstOrDefault();
string username = query.Split('&')
.Where(s => s.Split('=')[0] == "account_username")
.Select(s => s.Split('=')[1])
.FirstOrDefault();
}
else
{
Debug.WriteLine(e.Uri.ToString());
}
}
Если вы решите использовать код авторизации, не стесняйтесь использовать мой класс AuthHelper :
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text;
namespace Imagine.Core
{
public class AuthHelper
{
public static void AuthenticateCode(string code, Action<KeyValuePair<string,string>> onTokenResponseReceived)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://api.imgur.com/oauth2/token");
string data = string.Format("client_id={0}&client_secret={1}&grant_type=authorization_code&code={2}",
ConstantContainer.IMGUR_CLIENT_ID, ConstantContainer.IMGUR_CLIENT_SECRET, code);
byte[] binaryDataContent = Encoding.UTF8.GetBytes(data);
request.ContentType = "application/x-www-form-urlencoded";
request.Method = "POST";
request.BeginGetRequestStream(new AsyncCallback((n) =>
{
using (Stream stream = (Stream)request.EndGetRequestStream(n))
{
stream.Write(binaryDataContent, 0, binaryDataContent.Length);
}
request.BeginGetResponse(new AsyncCallback((ia) =>
{
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(ia);
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
Debug.WriteLine(reader.ReadToEnd());
}
}), null);
}),null);
}
}
}
На этом этапе я могу сделать еще один шаг и получить класс ImgurAuthUser , который сохранит токены и позволит мне легко сериализовать и десериализовать контент. Вот структура класса:
namespace Imagine.ImgurAPI
{
public class ImgurAuthUser
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public string Username { get; set; }
}
}
Изменяя обработчик Navigating, мы можем получить это:
void authBrowser_Navigating(object sender, NavigatingEventArgs e)
{
if (e.Uri.ToString().Contains("access_token="))
{
string uriString = e.Uri.ToString();
int indexOfSharp = uriString.IndexOf("#");
string query = uriString.Substring(indexOfSharp + 1, uriString.Length - indexOfSharp - 1);
ImgurAuthUser user = new ImgurAuthUser();
user.AccessToken = query.Split('&')
.Where(s => s.Split('=')[0] == "access_token")
.Select(s => s.Split('=')[1])
.FirstOrDefault();
user.RefreshToken = query.Split('&')
.Where(s => s.Split('=')[0] == "refresh_token")
.Select(s => s.Split('=')[1])
.FirstOrDefault();
user.Username = query.Split('&')
.Where(s => s.Split('=')[0] == "account_username")
.Select(s => s.Split('=')[1])
.FirstOrDefault();
}
else
{
Debug.WriteLine(e.Uri.ToString());
}
}
Отлично, теперь, когда у меня есть метаданные аутентификации, мне нужно их сохранить. Для этого я создал вспомогательную функцию SerializeAuthMetadata в классе AuthHelper :
/// <summary>
/// Serializes the authentication metadata returned from the service request
/// to local storage.
/// </summary>
/// <param name="user">The filled instance of the user metadata carrier class.</param>
/// <returns>If successful, is true.</returns>
public static bool SerializeAuthMetadata(ImgurAuthUser user)
{
try
{
IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("auth.xml", FileMode.Create, file))
{
XmlSerializer serializer = new XmlSerializer(typeof(ImgurAuthUser));
serializer.Serialize(stream, user);
}
return true;
}
catch
{
return false;
}
}
На этом этапе имеет смысл на самом деле вызывать эту функцию при сохранении данных:
if (AuthHelper.SerializeAuthMetadata(user))
{
if (NavigationService.CanGoBack)
NavigationService.GoBack();
}
else
{
MessageBox.Show("Ooops! Couldn't store the authentication metadata. Make sure that you have enought free space on the phone.",
"Imagine", MessageBoxButton.OK);
}
Как только данные успешно сохранены, я просто возвращаюсь на главную страницу, где отображаются изображения из главной галереи. Но здесь есть проблема — я все еще вижу кнопку, которая используется для входа в систему. Есть несколько подходов, которые можно использовать здесь. Я могу рассматривать ее как универсальную кнопку, которая позволила бы мне получить доступ к странице информации об учетной записи, когда учетные данные уже доступны, или она могла бы направить пользователя на страницу аутентификации, когда ее нет. Или я мог бы иметь отдельные кнопки для информации об учетной записи и выходить из системы всякий раз, когда сохраняются учетные данные.
Я собираюсь перейти к первому сценарию и переназначить его на кнопку « учетная запись ».
Для обработчика событий Click я могу просто проверить, существует ли файл auth.xml — он будет удален, когда пользователь выйдет из системы.
private void ApplicationBarIconButton_Click_1(object sender, System.EventArgs e)
{
IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();
if (!file.FileExists("auth.xml"))
{
NavigationService.Navigate(new Uri("/AuthPage.xaml", UriKind.Relative));
}
else
{
// Placeholder for navigation to the account page.
}
}
Вот и все. Теперь вы можете выполнять аутентифицированные звонки. Как обычно, вы можете скачать последнюю версию исходного кода здесь .

