Last week, I filled in for Mike Benkovich on his weekly Cloud Computing: Soup to Nuts series with a presentation on the Windows Azure Service Bus. The recording should be available soon, if not already, but I thought I’d follow up with a multi-part blog series that covers the material I presented.
The context for the talk was pretty simple, an application running “on-premises” and behind the firewall (your local machine, in this case) that echoes messages sent via a public website, hosted on Windows Azure (but it could be anywhere). When the message arrives at the service host, a window pops up showing the message.
The ‘on-premises’ piece of this example is a very simple WPF application that listens for messages, either on a WCF endpoint or via the Windows Azure Service Bus.. Messages in this context have three fields: the message text, name of the sender, and a string indicating the color of the window that displays the message on-premises. The result looks something like the following, where the browser on the left is the client, and the simple WPF application at the bottom right is the service host. The Metro-esque windows are the result of three messages having been sent and processed by the service hosted by the WPF application.
Service Contract
The contract for the WCF service is pretty simple and defined in a standalone assembly referenced by both the Web site client project and the WPF application that hosts the service endpoint.
using System.ServiceModel; namespace RelayService.Interfaces { [ServiceContract(Name = "IEchoContract", Namespace = "http://samples.microsoft.com/ServiceModel/Relay/")] public interface IEchoContract { [OperationContract] void Echo(string sender, string text, string color); } public interface IEchoChannel : IEchoContract, IClientChannel { } }
On-Premises WCF Sample
Before we get to the Service Bus, let’s take a quick look at the implementation of this scenario using a self-hosted WCF application.
WPF Implementation
The WPF application in the sample project can either self-host a WCF service or open an endpoint exposed on the Service Bus; a simple checkbox on the main window controls the behavior. To keep things simple, the WCF service endpoint is set up with a simple BasicHttpBinding (that means no authentication on the service, not something you’d do in production!).
ServiceHost host = default(ServiceHost); // local WCF service end point host = new ServiceHost(typeof(EchoService)); host.AddServiceEndpoint("RelayService.Interfaces.IEchoContract", new BasicHttpBinding(), http://localhost:7777/EchoService);
ASP.NET Client Implementation
The client implementation is straightforward, and here as in the rest of the examples in the series, I’m using code (versus configuration) to provide all of the details. You can, of course, use the web.config file to specify the binding and endpoint.
ChannelFactory<IEchoContract> myChannelFactory = new ChannelFactory<IEchoContract>( new BasicHttpBinding(), "http://localhost:7777"); IEchoContract client = myChannelFactory.CreateChannel(); client.Echo(userName, txtMessage.Text, ddlColors.SelectedItem.Text); ((IClientChannel)client).Close(); myChannelFactory.Close();
Service Bus Relay Example
When you use a Service Bus relay, you’re essentially establishing an endpoint that’s hosted in the Windows Azure cloud, secured by the Access Control Service. The main difference in the code is the endpoint configuration. But before you can configure the endpoint in your application code, you’ll need to establish that relay endpoint in the cloud, so let’s walkthrough that now.
Configuring a Service Bus namespace
You create Service Bus namespaces via the Windows Azure portal; it’s a six step process.
- Select the Service Bus, Access Control & Caching portion of the portal.
- Select Service Bus
- Select New on the ribbon to create a new namespace. This will bring up the Create a new Service Namespace dialog. The same namespace can be used for Access Control, Service Bus, and Cache, but here we need only the Service Bus.
- Enter a unique namespace name; the resulting Service Bus endpoint will be uniquenamespacename.servicebus.windows.net, which being a public endpoint must be unique across the internet.
- Select the Windows Azure data center you wish to host your service. As of this writing, the East and West US data centers do not yet support the Service Bus functionality, so you’ll see only six options.
- Create the namespace. This will set up a Service Bus namespace at uniquenamespacename.servicebus.windows.net and an Access Control Service namespace at uniquenamespacename-sb.accesscontrol.windows.net. A service identity is also created with the name of owner and the privileges (claims) to manage, listen, and send (messages) on that Service Bus endpoint.
Controlling Access to the Service Bus namespace
Authentication and authorization of the Service Bus is managed by the Windows Azure Access Control Service (ACS) via the namespace (the one with a –sb suffix) that is set up when you create the Service Bus namespace. You can manage the associated ACS namespace from the Window Azure portal by highlighting the desired Service Bus namespace and selecting the Access Control Service option from the ribbon.
That will bring you to yet another part of the portal with a number of somewhat daunting options:
- Trust relationships is where you configure identity providers, such as Windows Live ID, Google, Yahoo!, Facebook, or Active Directory Federation Services 2.0. You can use these providers to authenticate and authorize access to the Service Bus endpoint. The Service Bus endpoint itself is a relying party application, namely an application that is secured by ACS. Each relying party application has at least one set of rules associated with it; these rules essentially define what operations a given identity can perform on the relying party application.
For a Service Bus endpoint, there are three operations that can be carried out:
- Send – send messages to an endpoint, queue or topic.
- Listen – receive messages from an endpoint, queue, or subscription.
- Manage – set up queues, topics, and subscriptions.
- Service settings is where you manage certificates and keys as well as service identities that are not managed by other identity providers (like Live ID or Google). For the Service Bus, the ACS automatically manages the certificates and keys (so you don’t ever worry about that part), and it also creates a service identity with the name owner and claims that allow owner to perform all three of the operations (send, listen, and manage). You can think of owner as the sa, root, or superuser of the ACS namespace, and from that it should follow that you’ll rarely – if ever – want to use owner as part of your application. Instead you should create additional user(s) and apply the principle of least privilege.
- Administration is where you control who can administer the ACS endpoint. The default administrator will be the Service Administrator of the subscription under which the namespace was created. That last statement has specific implications when co-administrators of an Windows Azure subscription are in play. The co-admin can create the Service Bus namespace, but she cannot manage its access and will be greeted by the rather cryptic error below if she tries:
The remedy is to add the co-admin as a portal administrator, by logging into the ACS portal with the identity of the subscription’s Service Administrator and adding the Live ID of the co-admin:
- The Development section provides guidance and code snippets to integrate the ACS ‘magic’ into your applications.
Adding a new Service Identity
So what’s the workflow to granting access to a new user? There are two different answers here, one for creating a service identity and the other for handling identity federation via a third-party identity provider (like Live ID). We’ll tackle the first (and easier) case here, and we’ll look at the federation scenario in a subsequent post.
First you need a new service identity,so select the Service identities option on the sidebar. (Note that the auto-generated owner identity is in place)
Select the Add link, and create a new identity:
- Specify the user identity, use ‘guest’ here to mesh with the default identity assumed by the sample code.
- Provide an optional description visible only in the portal.
- Select “Symmetric Key” for the credential type. There are two other options: Password and X.509 Certificate, but both require a bit more code and configuration to implement. Consult the ACS samples on CodePlex for scenarios that make use of these alternative credential types.
- Generate a key for the guest identity. You’ll also need to add this key value in the web.config file for the RelayServiceClient application. While you’re at it, you may as well change the SBNamespace value to the Service Bus namespace you just created.
- Save the new identity. By default the credentials will expire in a year.
Adding Authorization Claims for the New Service Identity
What we’ve done so far is create a new service identity with the name guest, but that identity has no privileges, so if you try using it (with code we haven’t quite looked at yet), you’d get an HTTP 403 (Forbidden) error. That’s in contrast to a 401 (Unauthorized) error that you would get when attempting to access the Service Bus with a non-existent user. So let’s set the guest user up with the ability to make calls against the Service Bus namespace.
First let’s create a new rule group. Strictly speaking we could just use the default group (which includes the authorization rules for owner), but a new group can provide a bit more flexibility and reinforce that the owner identity is “special.”
Enter a name for the group:
After you save the new group, a list of all of the rules associated with this group appears. Of course, there are none yet, so you’ll need to add at least one. There is an option to generate rules based on identity providers you’ve associated with the Service Bus namespace, but we’re using service identities, so you’ll need to use the Add link to set up the authorization rules.
What you’ll be adding here is a new claim rule. The concept behind claims is actually rather simple, although the user interface belies that! A claim is some statement about a user or identity that is vouched for by a given identity provider. For instance, when you successfully login via your Google ID, your application is passed back a list of claims associated with your identity that Google is willing to vouch for, so if your relying party application is willing to trust Google, then it can trust the bits of information that are returned as claims.
Claims can be just about any statement or fact about the user/identity that the identity provider is storing and willing to share. Google, for instance, will provide a name, nameidentifier, and email address as claims; Windows Live ID provides only a nameidentifier (a tokenized version of your Live ID). The types and number of claims emitted by different identity providers vary, and that’s where claim rules come in. You want to insulate your relying application from the vagaries of all the identity provider options, so rather than tapping into the raw claims that each of those providers sends, you set up claims that are meaningful to your application and then map the various incoming claims to the contextual claims your application understands.
In this case, the relying party is the Service Bus, and it understands three claims: Send, Manage, and Listen. Our specific goal though is to allow the guest identity only the capability to Send messages on the Service Bus, not manage or listen, so you’ll need to create one claim that authorizes that function. Each claim rule is essentially an IF-THEN statement:.
- The input claim issuer refers to the identifying party. The dropdown contains whatever identity providers you have enabled for the ACS controlling the Service Bus endpoint (via the Identity providers option under Trust Relationships in the left sidebar (not shown)). In this case, we’re using a service identity (guest) that is managed by the Access Control Service.
- The input claim type refers to one of potentially many claims that the identity provider is willing to offer up about the user/identity that it has authenticated. The list of claims relevant to the selected provider is prepopulated. For ACS-controlled service identities, the user name is provided as a nameidentifier claim; you’ll need to consult your identity provider’s documentation to determine what each claim type actually contains. Claims that aren’t pre-populated can be specified manually in the Enter type: text box. If you only care that the identity provider authenticates a user, you can select Any for the claim type, which simply indicates you don’t care about the specifics of the claims.
- To complete the “IF statement,” you specify what the specific claim value should be in order to fire the rule. If you don’t care about the specific value a claim has (and only that the identity provider offers up the given claim), you can specify Any here. In our case, the requestor must be guest, so that’s added as a specific value. If you have multiple other identities that you want to allow access, you would create additional rules. And if you have more complex rules (with “AND” conditions) you can add a second input claim as a compound condition to govern the claim rule.
So that’s the IF part! Now you need to map that input claim to output claims that the relying party (Service Bus) is interested in. We don’t really care at this point that the user’s name is guest, but we do want to know what that user is allowed to do. Granted you could do all that checking in code – if (user == ‘guest’ ) then let user call methods, etc. – but that’s horribly hard-coded and very difficult to maintain from a code and security management perspective (what happens if the guest password is compromised? how do you revoke access?) So what you’ll do instead is map the input claim to an output claim, namely one that gives Send privileges to guest.
- The output claim type needed here is unique to the Service Bus, so the various options in the drop down aren’t applicable, and we specify the type net.windows.servicebus.action explicitly.
- There are three possible values: Send, Listen, and Manage; of course, we want just Send for guest.
- You can specify a description for the claim here; it will appear in the list of rules for the given Rule Group. It’s a good idea to put something meaningful here, because the Rule Group page listing will show only the output claim type. If you have multiple claims for different action values, they won’t otherwise be distinguishable in the listing.
- Save the rule!
Note that the Used by the following relying party application text box is still empty; that’s because we haven’t associated this rule group with the relying party (the Service Bus). There’s essentially a many-to-many relationship between relying party applications and the rule groups, so we’ll need to revisit the Relying party applications entry screen to associate the new rule group with the Service Bus. Note that the default rule group created (for owner) is already in selected.
sample code associated with this blog series assumes that you’ve created a service identity with the name
guest and provided the claim rule as detailed above.
The code also requires a second service identity with the name wpfsample that has an output claim rule of Listen; that is the user that initiates listening on the Service Bus endpoint within the WPF client application. You can follow the same steps above to create this second identity and associate it with a new (or existing rule group). You can use the default
owner as well, but per the earlier recommendation, it’s best to reserve that service identity for administrative use and not incorporate it into your application logic or configuration.
Блокировка служебной шины с помощью ACS была, по общему признанию, небольшим трудом и отчасти отклонением от нашей реальной цели (вероятно, поэтому многие примеры просто используют владельца, даже если они этого не делают!). Вооружившись новыми удостоверениями guest и wpfsample и их необходимыми утверждениями, давайте теперь посмотрим на код для использования конечной точки служебной шины вместо конечной точки WCF, с которой мы начали.
Изменение реализации WPF ServiceHost для использования ретранслятора служебной шины
Вот код для прослушивания приложением WPF конечной точки служебной шины по сравнению с настройкой конечной точки WCF в локальной среде:
ServiceHost host = default(ServiceHost); // ServiceBusEndpoint host = new ServiceHost(typeof(EchoService)); host.AddServiceEndpoint("RelayService.Interfaces.IEchoContract", new BasicHttpRelayBinding(), ServiceBusEnvironment.CreateServiceUri("https", Properties.Settings.Default.SBNamespace, "EchoService")); // Add the Service Bus credentials to all endpoints specified in configuration. foreach (ServiceEndpoint endpoint in host.Description.Endpoints) { endpoint.Behaviors.Add(new TransportClientEndpointBehavior() { TokenProvider = TokenProvider.CreateSharedSecretTokenProvider( "wpfsample", Properties.Settings.Default.SBListenerCredentials) }); }
Он начинается так же, как чистый пример WCF, но есть некоторые заметные различия:
- Строка 6: мы используем новый тип привязки, BasicHttpRelayBinding , который вы можете рассматривать как BasicHttpBinding, настроенный и адаптированный для служебной шины. Существует ряд других привязок служебной шины, которые соответствуют традиционным локальным привязкам WCF, а также определенным для облачной среды.
- Строки 7-9: CreateServiceUri метод является предпочтительным механизмом для построения конечной точки Service Bus URI; однако в этом случае вы можете просто создать строку типа «https://yoursbnamespace.servicebus.windows.net/EchoService». Свойство SBNamespace в строке 8 относится к параметру, который вы указываете в приложении WPF ( App.config ), который указывает имя пространства имен служебной шины, созданного ранее.
- В строках 12–20 учетные данные служебной шины добавляются к конечным точкам, предоставляемым служебной шиной. Строка 17 показывает имя идентификатора службы (жестко закодировано в wpfsample ), а строка 18 указывает симметричный ключ, связанный с этим идентификатором службы на портале ACS. Вам нужно будет скопировать и вставить учетные данные из портала в параметр SBListenerCredentials в файле App.config приложения WPF (или через диалоговое окно свойств в Visual Studio).
И все, теперь, когда нажата кнопка « Запустить службу» (и установлен флажок « Использовать служебную шину» в пользовательском интерфейсе WPF), клиент откроет точку ретрансляции в любом центре обработки данных Windows Azure, в котором размещена эта конечная точка служебной шины. В зависимости от местоположения вы можете заметить задержку в несколько секунд после установления соединения.
Теперь, когда приложение WPF прослушивает запросы на конечной точке, давайте отправим несколько сообщений через клиент ASP.NET.
Ориентация на конечную точку служебной шины в клиенте ASP.NET
// ServiceBus endpoint constructed using App.config setting ChannelFactory<IEchoContract> myChannelFactory = new ChannelFactory<IEchoContract>( new BasicHttpRelayBinding(), new EndpointAddress(ServiceBusEnvironment.CreateServiceUri( "https", ConfigurationManager.AppSettings["SBNameSpace"], "EchoService"))); // add credentials for user (hardcoded in App.config for demo purposes) myChannelFactory.Endpoint.Behaviors.Add( new TransportClientEndpointBehavior() { TokenProvider = TokenProvider.CreateSharedSecretTokenProvider( userName, ConfigurationManager.AppSettings["SBGuestCredentials"]) } ); // traditional WCF invocation IEchoContract client = myChannelFactory.CreateChannel(); client.Echo(txtUser.Text, txtMessage.Text, ddlColors.SelectedItem.Text); ((IClientChannel)client).Close(); myChannelFactory.Close();
Выше приведен код, который клиентское приложение (ASP.NET) использует для отправки сообщения на служебную шину для передачи в приложение WPF, размещенное «локально». Вы можете запустить веб-сайт локально (нажав F5 в Visual Studio), и вы все равно будете использовать служебную шину в облаке; хотя для большего удовольствия вы можете развернуть приложение ASP.NET как облачную службу Windows Azure, чтобы другие могли открыть сайт и отправить сообщение на ваш локальный компьютер. Для вас предусмотрен проект облачных сервисов, но вам необходимо настроить параметры публикации для своей собственной учетной записи в облаке.
- Строки 2-8 настраивают клиент ChannelFactory . Как и в приложении WPF, BasicHttpRelayBinding используется, а конечная точка Service Bus строится с помощью CreateServiceUri метода. Вам нужно изменить файл app.config, указав имя конечной точки служебной шины в качестве параметра SBNamespace.
- Строки 11-17 добавляют учетные данные служебной шины для идентификатора, переданного через userName (по умолчанию это значение guest ). Ключ учетных данных, который вы настроили ранее на портале ACS, необходимо указать в файле app.config в качестве параметра SBGuestCredentials.
- Строки 18-24 ничем не отличаются от традиционного вызова WCF; здесь вызывается метод Echo, передавая значения, предоставленные пользователем в форме ASP.NET.
Дайте ему Спин!
На данный момент, вы (наконец-то) готовы запустить образец. Загрузив решение в Visual Studio, вы можете просто нажать F5, чтобы запустить его. Веб-сайт ASP.NET и приложение WPF настроены на запуск проектов, поэтому вам не нужно (и не нужно) запускать эмулятор Windows Azure, чтобы увидеть служебную шину в действии. Вот игра за игрой взаимодействия с пользователем; имейте в виду, что в данном случае используются две разные машины в совершенно разных сетях, разделенных межсетевыми экранами.
- Установите флажок Использовать служебную шину ; при этом ServiceHost в приложении WPF откроет конечную точку на служебной шине (используя настроенное вами пространство имен, именем которого вы также указали файл App.config ).
- Нажмите кнопку « Запустить службу» (она не скажет « Остановить службу», пока служба не будет запущена). Если это не помогло , вероятно, есть какая-то проблема с установленным вами идентификатором службы wpfsample (вы сделали это, верно?)
- В клиенте ASP.NET выберите свой любимый цвет из списка.
- Введите сообщение, которое вы хотите отправить через служебную шину.
- Выберите параметр для вызова сообщения через конечную точку служебной шины по сравнению с локальной конечной точкой WCF.
- Отправьте сообщение и…
- Вы должны увидеть окно сообщения на том же компьютере, на котором запущено приложение WPF.
Рабочий пример великолепен, но я склонен больше учиться на приложениях, которые не работают! Вот несколько сценариев для рассмотрения:
- Попробуйте отправить сообщение под именем пользователя «Джо». Вы должны увидеть ошибку 401, отображаемую в верхней части страницы, поскольку такая идентификационная информация не известна служебной шине.
- Укажите имя пользователя «владельца». владелец существует (вы получили его бесплатно, когда было создано пространство имен), но учетные данные, которые вы указали в app.config , не будут совпадать. Вы также получите ошибку 401.
- Укажите имя пользователя «wpfsample» и измените учетные данные в app.config на симметричный ключ, связанный с wpfsample (его можно получить на портале ACS). Вы должны увидеть FaultException, указывающее, что действие Send запрещено; это потому, что wpfsample имеет только привилегии Listen.
- Остановите службу с помощью кнопки в приложении WPF и попробуйте отправить сообщение. В этом случае клиент получает либо FaultException с сообщением «Никакие службы не размещены по указанному адресу», либо ProtocolException и скрытый в сообщении этого исключения является признаком того, что для конечной точки не было зарегистрированных активных прослушивателей.
Этот последний момент поведения — когда вся операция зависит как от клиента, так и от сервера, одновременно работающего и прослушивающего — может быть немного сдерживающим, особенно если у вас есть независимые сущности, общающиеся через служебную шину. Что если одна из сторон вышла из строя на техническое обслуживание? Отправитель обязан обнаружить ошибки и повторить попытку позже или предпринять какие-либо другие меры. Разве не было бы здорово, если бы вы могли гарантировать, что сообщения будут поступать и обрабатываться (асинхронно), даже если обе стороны не были «в сети» одновременно? Ну, вы можете, и именно здесь приходят очереди Service Bus — тема следующего выпуска в этой серии.