До сих пор я дополнял архитектуру различных решений использованием Microsoft Windows Azure или SQL Azure. На этот раз мы обсудим, как скомбинировать несколько облачных сервисов в единое приложение. Мой пример композиции облачных сервисов будет включать Windows Azure, Windows Azure AppFabric Access Control, Bing Maps и Facebook.
Для тех, кто слегка пугается, когда слышит о федеративной идентификации или о практической ценности социальных сетей, я хотел бы представить Маркелуса (Marcelus). Это мой друг, которому принадлежит компания по уборке бытовых и промышленных помещений. Как и мой отец, у которого есть свой бизнес и личные связи, он знает людей, которые могут что-то сделать или достать почти все, что угодно, обычно в той или иной форме бартера. Некоторые могут счесть такое неэтичным, но в реальной жизни я могу положиться на помощь Маркелуса и ему подобных. Я смотрю на него и вижу живой пример тому, как можно было бы скомбинировать сервис AppFabric Access Control (ACS для краткости) в Windows Azure с обширной социальной сетью.
Однако в виртуальном мире, когда используется несколько облачных сервисов, им зачастую нужно знать, кто я такой, прежде чем открывать доступ к своей функциональности. Поскольку я не могу запрограммировать Маркелуса на обслуживание веб-страниц, я задействую облачные сервисы, перечисленные в табл. 1.
Табл. 1. Облачные сервисы и их функциональность
Сервис | Функциональность |
Windows Azure | Хостинг моего сайта и обслуживание страниц |
AppFabric Access Control | Управление и согласование метода аутентификации между моим сайтом и Facebook |
Facebook | Аутентификация пользователей и предоставление сервисов социальной сети |
Bing Maps | Визуализация мест проживания друзей |
Сценарий примера таков:при переходе на начальную страницу моего сайта пользователь будет аутентифицироваться Facebook, а заявки будут передаваться обратно моему сайту. После этого сайт будет получать список друзей данного пользователя от Facebook, а затем извлекать информацию для выбранного друга. Если выбранный друг указывал место проживания, его название можно будет щелкнуть, и сервис Bing Maps покажет соответствующую карту.
Настройка аутентификации между сервисами
В номере MSDN Magazine за декабрь 2010 г. была хорошая обзорная статья по ACS, которую можно найти по ссылке msdn.microsoft.com/magazine/gg490345. Я опишу специфические вещи, необходимые для объединения моего сайта в федерацию с Facebook. Чтобы сделать это корректно, я использую AppFabric Labs — предварительную версию Windows Azure AppFabric для разработчиков. Кроме того, я установил Windows Azure SDK 1.3 и Windows Identity Foundation SDK 4.0. Чтобы приступить к работе, я отправился на portal.appfabriclabs.com и зарегистрировался. Получив доступ к ACS, я последовал первой части инструкций на странице CodePlex «ACS Samples and Documentation (Labs)» (bit.ly/fuxkbl) для подготовки пространства имен сервиса. Следующей целью была настройка Facebook в качестве Identity Provider (провайдера идентификации), но для этого мне пришлось сначала создать приложение Facebook (см. инструкции по ссылке bit.ly/e9yE3I).В итоге я получил сводную страницу, показанную на рис. 1.
Рис. 2. Сводка конфигурации приложения Facebook
Эта сводная страница весьма важна, так как информация с нее понадобится мне при настройке Facebook в качестве Identity Provider в ACS. В частности, мне потребуются Application ID и Application Secret, как можно увидеть в конфигурационной информации из ACS на рис. 2.
Рис. 3. Конфигурация провайдера идентификации Facebook для ACS
Заметьте, что в поле Application permissions я добавил friends_hometown. Мне понадобится выводить этот адрес на карте, и, если бы я не указал его здесь, я не смог бы получить его обратно по умолчанию. Если бы я хотел, чтобы вызовы Graph API возвращали какие-то другие данные о пользователе, мне пришлось бы искать их на сайте Facebook Developers (bit.ly/c8UoAA) и включать соответствующий элемент в список Application permissions.
При работе с ACS стоит отметить следующее: вы указываете доверяющие стороны (Relying Parties, RP), которые будут использовать Identity Provider. Мой сайт «прописан» по адресу jofultz.cloudapp.net, и он будет указан как доверяющая сторона в конфигурации Identity Provider. То же самое относится к моему localhost. Если бы я тестировал свою систему не в облаке, мне пришлось бы сконфигурировать localhost в качестве доверяющей стороны и выбрать его, как показано на рис. 3.
Рис. 4. Конфигурация провайдера идентификации Facebook для ACS: доверяющие стороны
Содержимое рис. 2 и 4 находится на одной странице для конфигурирования провайдера идентификации. С одним маркером, если бы я сконфигурировал его только для localhost, а потом попытался бы аутентифицироваться со своего веб-сайта, у меня ничего не вышло бы. Я могу создать собственную страницу входа — соответствующие руководство и пример есть в разделе Application Integration на сайте управления ACS. В данном примере я просто использую страницу по умолчанию, размещаемую в ACS.
До сих пор я настраивал ACS и свое приложение Facebook так, чтобы они «общались» прямо после вызова. Следующий шаг — сконфигурировать этот провайдер идентификации для моего сайта как механизм аутентификации. Самый простой способ сделать это заключается в установке Windows Identity Foundation SDK 4.0 (bit.ly/ew6K5z). После установки в контекстном меню будет доступна команда Add STS Reference, как показано на рис. 4.
Рис. 5. Команда меню Add STS Reference
В примере я использовал сайт ASP.NET по умолчанию, созданный в Visual Studio выбором нового проекта Web Role. После создания я щелкаю сайт правой кнопкой мыши и запускаю мастер. Сайт конфигурируется на использование существующего сервиса Security Token Service (STS); для этого я выбираю в мастере соответствующий вариант и указываю путь к метаданным WS-Federation. Для моего пространства имен управления доступом путь выглядит так:
jofultz.accesscontrol.appfabriclabs.com/
FederationMetadata/2007-06/
FederationMetadata.xml
На основе этой информации мастер добавит в конфигурацию сайта раздел <microsoft.identityModel/>. Потом под элементом <system.web/> разместит <httpRuntime requestValidationMode="2.0" />. Если я указываю localhost в качестве RP, я могу запустить приложение и при запуске увидеть размещенную в ACS страницу входа, которая будет представлять Facebook (или Windows Live, Google — все зависит от конфигурации). Элемент microsoft.identityModel полагается на существование сборки Microsoft.Identity, поэтому убедитесь, что ссылка на DLL в сайте установлена как Copy Always. Если это не так, то при переносе в Windows Azure сайт потеряет DLL и не сможет работать. Возвращаясь к моему предыдущему утверждению о необходимости конфигурации для localhost и размещенного сайта Windows Azure, после завершения работы мастера необходимо выполнить дополнительную настройку. Поэтому, если в мастере был задан путь localhost, по окончании его работы нужно вручную добавить в элемент <audienceUris> путь для сайта в Windows Azure:
<microsoft.identityModel>
<service>
<audienceUris>
<add value="http://jofultz.cloudapp.net/" />
<add value="http://localhost:81/" />
</audienceUris>
Кроме того, атрибут realm элемента wsFederation в конфигурационном файле должен отражать текущее местонахождение исполняющей среды. Таким образом, в моем случае при развертывании в Windows Azure он выглядит следующим образом:
<federatedAuthentication>
<wsFederation passiveRedirectEnabled="true" issuer=
"https://jofultz.accesscontrol.appfabriclabs.com/v2/wsfederation"
realm="http://jofultz.cloudapp.net/" requireHttps="false" />
<cookieHandler requireSsl="false" />
</federatedAuthentication>
Однако, если требуется отладка и корректная работа в период выполнения на localhost (для локальной отладки), нужно изменить realm так, чтобы он указывал, где локально размещен сайт, например:
<federatedAuthentication>
<wsFederation passiveRedirectEnabled="true"
issuer="https://jofultz.accesscontrol.
appfabriclabs.com/v2/wsfederation"
realm="http://localhost:81/"
requireHttps="false" />
<cookieHandler requireSsl="false" />
</federatedAuthentication>
Все правильно настроив, я должен получить возможность выполнять сайт, а при попытке перейти к странице по умолчанию я буду перенаправляться к размещенной в ACS странице входа, где смогу выбрать Facebook в качестве провайдера идентификации. Щелкнув Facebook, я перехожу на страницу входа в Facebook для аутентификации (рис. 6).
Рис. 6. Страница входа в Facebook
Поскольку я еще не использовал свое приложение, Facebook выведет диалог Request for Permission, показанный на рис. 7.
Рис. 7. Request for Permission для приложения
Не желая остаться вне круга тех, кто использует столь фантастично шикарное приложение, я быстро щелкаю Allow, после чего Facebook, ACS и мое приложение обмениваются информацией (через перенаправления браузера), и в конечном счете я попадаю в свое приложение. В этот момент я просто получаю пустую страницу, но ей известно, кто я такой, и поэтому в верхнем правом углу страницы появляется сообщение «Welcome Joseph Fultz».
Facebook Graph API
В приложении нужно получать список друзей, образующих мою социальную сеть, а затем получать информацию об этих друзьях. Facebook предоставляет Graph API, который позволяет разработчикам делать подобные вещи. Он весьма тщательно документирован и, что самое замечательное, имеет линейную и простую реализацию, упрощающую его понимание и применение. Для выдачи запросов мне понадобится маркер доступа (Access Token). К счастью, он возвращается в заявках, и с помощью Windows Identity Foundation SDK заявки помещаются в идентификацию участника системы безопасности (principal identity). Заявки (claims) выглядят примерно так:
http://schemas.xmlsoap.org/ws/2005/05/
identity/claims/nameidentifier
http://schemas.microsoft.com/ws/2008/06/
identity/claims/expiration
http://schemas.xmlsoap.org/ws/2005/05/
identity/claims/emailaddress
http://schemas.xmlsoap.org/ws/2005/05/
identity/claims/name
http://www.facebook.com/claims/AccessToken
http://schemas.microsoft.com/
accesscontrolservice/2010/07/claims/
identityprovider
Из них мне нужно извлечь последнюю часть полного имени (например, nameidentifier, expiration и т. д.) и соответствующее значение. Поэтому я создаю метод ParseClaims для разбора заявок и размещения этих заявок и их значений в хеш-таблицу для дальнейшего использования, а затем вызываю его в обработчике события загрузки страницы:
protected void ParseClaims()
{
string username = default(string);
username = Page.User.Identity.Name;
IClaimsPrincipal Principal = (IClaimsPrincipal) Thread.CurrentPrincipal;
IClaimsIdentity Identity = (IClaimsIdentity) Principal.Identity;
foreach (Claim claim in Identity.Claims)
{
string[] ParsedClaimType = claim.ClaimType.Split('/');
string ClaimKey = ParsedClaimType[ParsedClaimType.Length - 1];
_Claims.Add(ClaimKey, claim.Value);
}
}
Я также создаю класс FBHelper с методами для доступа к нужной мне информации от Facebook. И первым делом я создаю метод, помогающий выдавать все необходимые запросы. При выдаче каждого запроса используется объект WebClient, а ответ разбирается с помощью JavaScript Serializer:
public static Hashtable MakeFBRequest(string RequestUrl)
{
Hashtable ResponseValues = default(Hashtable);
WebClient WC = new WebClient();
Uri uri = new Uri(String.Format(RequestUrl, fbAccessToken));
string WCResponse = WC.DownloadString(uri);
JavaScriptSerializer JSS = new JavaScriptSerializer();
ResponseValues = JSS.Deserialize<Hashtable>(WCResponse);
return ResponseValues;
}
Как видно в этом фрагменте кода, у каждого запроса должен быть маркер доступа, который был возвращен в заявках. Закончив с методом выдачи запросов, я создаю метод для выборки друзей и разбора их в хеш-таблицу, содержащую каждый из их идентификаторов и имен Facebook:
public static Hashtable GetFBFriends(string AccessToken)
{
Hashtable FinalListOfFriends = new Hashtable();
Hashtable FriendsResponse = MakeFBRequest(_fbFriendsListQuery, AccessToken);
object[] friends = (object[])FriendsResponse["data"];
for (int idx = 0; idx < friends.Length;idx++ )
{
Dictionary<string, object> FriendEntry =
(Dictionary<string, object>)friends[idx];
FinalListOfFriends.Add(FriendEntry["id"], FriendEntry["name"]);
}
return FinalListOfFriends;
}
Десериализация списка друзей приводит к созданию вложенной структуры Hashtable->Hashtable->Dictionary. Благодаря этому мне остается проделать минимум работы и поместить ее содержимое в свою хеш-таблицу. После этого я переключаюсь на страницу default.aspx, добавляю ListBox, пишу код, получающий список друзей, и связываю результат с новым ListBox:
protected void GetFriends()
{
_Friends = FBHelper.GetFBFriends((string)_
Claims["AccessToken"]);
this.ListBox1.DataSource = _Friends;
ListBox1.DataTextField = "value";
ListBox1.DataValueField = "key";
ListBox1.DataBind();
}
Если запустить приложение на этом этапе, то после аутентификации я увижу список всех своих друзей на Facebook. Но постойте-ка, это еще не все! Мне нужно получать доступную информацию о любом выбранном друге, чтобы у меня была возможность показывать место его проживания на карте. Возвращаемся к классу FBHelper и добавляем простой метод, который будет принимать маркер доступа и идентификатор выбранного друга:
public static Hashtable GetFBFriendInfo(string AccessToken, string ID)
{
Hashtable FriendInfo =
MakeFBRequest(String.Format(_fbFriendInfoQuery, ID) +
"?access_token={0}", AccessToken);
return FriendInfo;
}
Заметьте, что в обоих созданных мной вспомогательных методах для Facebook я ссылаюсь на строку-константу, содержащую необходимый запрос Graph API:
public const string _fbFriendsListQuery =
"https://graph.facebook.com/me/friends?access_token={0}";
public const string _fbFriendInfoQuery = "https://graph.facebook.com/{0}/";
Создав последний метод для Facebook, я добавляю на страницу GridView и настраиваю его на связывание с хеш-таблицей, а затем — в отделенном коде в методе SelectedIndexChanged для ListBox — я связываю его с Hashtable, возвращаемым методом GetFBFriendInfo, как показано на рис. 8.
Рис. 8. Добавление GridView
protected void ListBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Debug.WriteLine(ListBox1.SelectedValue.ToString());
Hashtable FriendInfo =
FBHelper.GetFBFriendInfo((string)_Claims["AccessToken"],
ListBox1.SelectedValue.ToString());
GridView1.DataSource = FriendInfo;
GridView1.DataBind();
try
{
Dictionary<string, object> HometownDict =
(Dictionary<string, object>) FriendInfo["hometown"];
_Hometown = HometownDict["name"].ToString();
}
catch (Exception ex)
{
_Hometown = "";//Not Specified";
}
}
А теперь, закончив с получением списка друзей и информации о них из Facebook, я перехожу к отображению мест их проживания на карте.
Нет ничего лучше дома
Для тех друзей, которые указали место своего проживания, я хочу иметь возможность щелкать названия их городов и видеть их на карте. Первый шаг — добавление карты к странице. Это довольно простая задача, так как Bing предоставляет отличный интерактивный SDK, который демонстрирует функциональность и позволяет копировать исходный код. Вы найдете этот SDK по ссылке microsoft.com/maps/isdk/ajax/. В страницу default.aspx я добавляю тег div, в котором будет храниться карта:
<div id="myMap" style="position:relative; width:400px; height:400px;" ></div>
Однако, чтобы получить карту, я добавляю ссылку в тег script и пишу скриптовый код для страницы SiteMaster:
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/
mapcontrol/mapcontrol.ashx?v=6.2"></script>
<script type="text/javascript">
var map = null;
function GetMap() {
map = new VEMap('myMap');
map.LoadMap();
}
</script>
Теперь, когда я получу страницу, я увижу карту в исходной позиции, но мне нужно, чтобы при выборе друга карта смещалась к месту его проживания. При обработке события SelectedIndexChanged (обсуждалось ранее) я также связываю метку в странице с названием и добавляю событие щелчка на клиентской стороне, чтобы карта находила местоположение на основе значения метки:
onclick="map.Find(null, hometown.innerText,
null, null, null, null, true, null, true);
map.SetZoomLevel(6);"
Вызывая map.Find, вы можете при желании оставить большую часть концевых параметров выключенными (null). Описание метода Find см. по ссылке msdn.microsoft.com/library/bb429645. Вот и все, что нужно для отображения и взаимодействия с картой в этом простом примере. Теперь я готов запустить сайт во всей его красе.
Если identityModel был сконфигурирован для корректной работы с localhost, как описывалось ранее, то можно нажать клавишу F5 и запустить сайт локально для отладки. И вот я жму F5, вижу всплывающее окно браузера, и появляются варианты входа. Я выбираю Facebook и перехожу на страницу входа, которая была показана на рис. 6. После входа приложение перенаправляет меня на страницу default.aspx, которая теперь отображает список моих друзей и исходную карту, как на рис. 9.
Рис. 9. Демонстрационная начальная страница
Далее я просматриваю список друзей и выбираю одного из них. В результате мне становится доступной информация о нем в зависимости от его настроек защиты и разрешений, запрошенных для моего приложения при настройке провайдера идентификации (рис. 2). Затем я щелкаю название его города, показываемое над картой, и карта смещается к центру его города (рис. 10).
Рис. 10. Карта города в Bing Maps
Заключительные соображения
Надеюсь, мне удалось показать, как связать воедино платформу Windows Azure, Bing Maps и Facebook и насколько это легко. Используя ACS, я сумел создать приложение-пример на основе облачных технологий. Приложив еще немного усилий, так же несложно подключить собственный сервис идентификации, который будет работать так, как вам нужно. Изящество этой федерации идентификаций в том, что применение Windows Azure позволяет вести разработку на различных платформах и включать сервисы от сторонних поставщиков, не ограничивая вас единственным провайдером и его сервисами и не заставляя придумывать собственные способы интеграции. Платформа Microsoft Windows Azure обладает колоссальной мощью и дает возможность легко объединять ваши приложения с облачными сервисами.