Прогноз: облачно

Объединение облачных сервисов в одно приложение

Джозеф Фулц

Загрузка примера кода

Joseph FultzДо сих пор я дополнял архитектуру различных решений использованием 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.

image: Facebook Application Configuration Summary

Рис. 2. Сводка конфигурации приложения Facebook

Эта сводная страница весьма важна, так как информация с нее понадобится мне при настройке Facebook в качестве Identity Provider в ACS. В частности, мне потребуются Application ID и Application Secret, как можно увидеть в конфигурационной информации из ACS на рис. 2.

image: ACS Facebook Identity Provider Configuration

Рис. 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.

image: ACS Facebook Identity Provider Configuration: Relying Parties

Рис. 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.

image: Add STS Reference Menu Option

Рис. 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).

image: Facebook Login

Рис. 6. Страница входа в Facebook

Поскольку я еще не использовал свое приложение, Facebook выведет диалог Request for Permission, показанный на рис. 7.

image: Application Permission Request

Рис. 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) выглядят примерно так:

https://schemas.xmlsoap.org/ws/2005/05/  

    identity/claims/nameidentifier

  https://schemas.microsoft.com/ws/2008/06/

    identity/claims/expiration

  https://schemas.xmlsoap.org/ws/2005/05/

    identity/claims/emailaddress

  https://schemas.xmlsoap.org/ws/2005/05/

    identity/claims/name 

  http://www.facebook.com/claims/AccessToken

  https://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.

image: Demo Homepage

Рис. 9. Демонстрационная начальная страница

Далее я просматриваю список друзей и выбираю одного из них. В результате мне становится доступной информация о нем в зависимости от его настроек защиты и разрешений, запрошенных для моего приложения при настройке провайдера идентификации (рис. 2). Затем я щелкаю название его города, показываемое над картой, и карта смещается к центру его города (рис. 10).

image: Hometown in Bing Maps

Рис. 10. Карта города в Bing Maps

Заключительные соображения

Надеюсь, мне удалось показать, как связать воедино платформу Windows Azure, Bing Maps и Facebook и насколько это легко. Используя ACS, я сумел создать приложение-пример на основе облачных технологий. Приложив еще немного усилий, так же несложно подключить собственный сервис идентификации, который будет работать так, как вам нужно. Изящество этой федерации идентификаций в том, что применение Windows Azure позволяет вести разработку на различных платформах и включать сервисы от сторонних поставщиков, не ограничивая вас единственным провайдером и его сервисами и не заставляя придумывать собственные способы интеграции. Платформа Microsoft Windows Azure обладает колоссальной мощью и дает возможность легко объединять ваши приложения с облачными сервисами.

Джозеф Фулц (Joseph Fultz) — архитектор в Microsoft Technology Center в Далласе, работает как с корпоративными заказчиками, так и с независимыми разработчиками ПО (ISV), которые проектируют и создают прототипы программных решений, отвечающих потребностям бизнеса и рынка. Выступал на различных конференциях вроде Tech·Ed, а также на внутренних мероприятиях, направленных на повышение квалификации сотрудников.

Выражаю благодарность за рецензирование статьи эксперту Стиву Лайнэну (Steve Linehan)