Service Bus Authentication

If anyone were allowed to relay messages to your service, or if any service could receive your client calls, the service bus would be a dangerous proposition. The service bus mandates that the service must always authenticate itself in order to connect to the service bus and receive relayed messages. Clients, on the other hand, may or may not authenticate themselves. Typically (and by default), the clients do authenticate against the service bus, but the relayed service may decide to waive the client’s service bus authentication. Note that this kind of authentication is application authentication, rather than individual user authentication.

As mentioned previously, the service bus utilizes the ACS of the Windows Azure AppFabric platform. The client and the service need to present a security token issued by the ACS. Using the service namespace portal pages, the service namespace administrator creates and assigns such tokens in the form of keys (see Figure 11-16).

Configuring secret keys

Figure 11-16. Configuring secret keys

Each of the keys is a string of 47 characters long representing the shared secret.

These keys (and their issuer) must be known to the client and the service when authenticating against the service bus. The keys can be used to both authenticate and authorize sending messages and receiving messages, as well as managing the service namespace. In addition, the service and client may not use the same key. Presently, using the ACS raw keys is the default form of authentication available. This may change in future release, allowing you to map other credentials such as passwords or certificates to ACS roles, claims and tokens. In addition, the ACS allowed for integrating ADFS (Active Directory Federated Solution) for authenticating and authorizing the clients.

Configuring Authentication

The enum TransportClientCredentialType represents the type of credentials used:

public enum TransportClientCredentialType
{
   SharedSecret,
   SimpleWebToken,
   Saml,
   Unauthenticated
}

The word Client in TransportClientCredentialType refers to a client of the service bus; that is, both the client and the relayed service.

You can configure the desired authentication mechanism and even the credentials themselves using an endpoint behavior called TransportClientEndpointBehavior, defined in Example 11-14, along with the TransportClientCredentials class to provide the credentials themselves.

Example 11-14. The TransportClientEndpointBehavior

public sealed class TransportClientEndpointBehavior : IEndpointBehavior
{
   public TransportClientCredentials Credentials
   {get;}
   public TransportClientCredentialType CredentialType
   {get;set;}
}
public class TransportClientCredentials
{
   public SharedSecretCredential SharedSecret
   {get;}
   //More members
}

Using an endpoint behavior (as opposed to a service behavior) provides two advantages. First, a service host can choose a different authentication mechanism for each endpoint. This may become relevant in future releases with other types of credentials. Second, it offers a unified programming model for both the client and the service, since there are only endpoint behaviors on the client side.

Shared Secret Authentication

The simplest form of credentials is to use the key assigned to the service namespace as a shared secret. All the samples in this chapter use this form of credentials:

public class SharedSecretCredential : TransportClientCredentialBase
{
   public string IssuerName
   {get;set;}

   public string IssuerSecret
   {get;set;}

   //More members
}

You need to programmatically provide the issuer of the secret and the secret itself to the TransportClientEndpointBehavior. The host and the client follow similar steps in providing the credentials.

Providing the credentials on the host side

It is important to note that TransportClientEndpointBehavior defaults CredentialType to TransportClientCredentialType.Unauthenticated. Consequently, all calls will fail by default, so you must configure a different value for the host. When using a shared secret, you first need to instantiate a new TransportClientEndpointBehavior object and set the CredentialType property to TransportClientCredentialType.SharedSecret. The credentials themselves are provided to the Credentials property. You then add this behavior to every endpoint of the host which uses the relay service, as shown in Example 11-15.

Example 11-15. Providing the host with shared secret credentials

string issuer = "owner";
string secret = "QV3...9M8=";

TransportClientEndpointBehavior credentials =
                                            new TransportClientEndpointBehavior();

credentials.CredentialType = TransportClientCredentialType.SharedSecret;
credentials.Credentials.SharedSecret.IssuerName   = issuer;
credentials.Credentials.SharedSecret.IssuerSecret = secret;

ServiceHost host = new ServiceHost(typeof(MyService));

foreach(ServiceEndpoint endpoint in host.Description.Endpoints)
{
   endpoint.Behaviors.Add(credentials);
}
host.Open();

You can encapsulate and automate the steps in Example 11-15 using extension methods, such as my SetServiceBusCredentials() methods of the ServiceBusHelper static class:

public static class ServiceBusHelper
{
   public static void SetServiceBusCredentials(this ServiceHost host,
                                               string secret);
   public static void SetServiceBusCredentials(this ServiceHost host,
                                               string issuer,string secret);
}

Unspecified, SetServiceBusCredentials() defaults the issuer to owner.

Using SetServiceBusCredentials(), Example 11-15 is reduced to:

ServiceHost host = new ServiceHost(typeof(MyService));
host.SetServiceBusCredentials("QV3...9M8=");
host.Open();

Example 11-16 shows the implementation of the SetServiceBusCredentials() methods without error handling.

Example 11-16. Implementing SetServiceBusCredentials ()

//Error handling removed for brevity
public static class ServiceBusHelper
{
   internal const string DefaultIssuer = "owner";

   public static void SetServiceBusCredentials(this ServiceHost host,
                                               string secret)
   {
      SetServiceBusCredentials(host.Description.Endpoints,DefaultIssuer,secret);
   }

   public static void SetServiceBusCredentials(this ServiceHost host,
                                               string issuer,string secret)
   {
      SetServiceBusCredentials(host.Description.Endpoints,issuer,secret);
   }

   static void SetServiceBusCredentials(IEnumerable<ServiceEndpoint> endpoints,
                                        string issuer,string secret)
   {
      TransportClientEndpointBehavior behavior =
                                            new TransportClientEndpointBehavior();
      behavior.CredentialType = TransportClientCredentialType.SharedSecret;
      behavior.Credentials.SharedSecret.IssuerName = issuer;
      behavior.Credentials.SharedSecret.IssuerSecret = secret;
      SetBehavior(endpoints,behavior);
   }

   static void SetBehavior(IEnumerable<ServiceEndpoint> endpoints,
                           TransportClientEndpointBehavior credential)
   {
      foreach(ServiceEndpoint endpoint in endpoints)
      {
         endpoint.Behaviors.Add(credential);
      }
   }
}

ServiceBusHelper defines the helper private method SetBehavior(), which accepts a collection of endpoints, and assigns a provided TransportClientEndpointBehavior object to all endpoints in the collection. The private SetServiceBusCredentials() helper methods accept a collection of endpoints, and the credentials it uses to create a TransportClientEndpointBehavior used to call SetBehavior().

Providing the credentials on the client side

The client needs to follow similar steps as the host, except there is only one endpoint to configure—the one the proxy is using, as shown in Example 11-17.

Example 11-17. Setting the credentials on the proxy

string issuer = "owner";
string secret = "QV3...9M8=";

TransportClientEndpointBehavior credentials =
                                            new TransportClientEndpointBehavior();

credentials.CredentialType = TransportClientCredentialType.SharedSecret;
credentials.Credentials.SharedSecret.IssuerName   = issuer;
credentials.Credentials.SharedSecret.IssuerSecret = secret;

MyContractClient proxy = new MyContractClient();
proxy.Endpoint.Behaviors.Add(credentials);

proxy.MyMethod();

proxy.Close();

Again, you should encapsulate this repetitive code with extension methods, and offer similar support for working with class factories:

public static partial class ServiceBusHelper
{
   public static void SetServiceBusCredentials<T>(this ClientBase<T> proxy,
                                                  string secret) where T : class;
   public static void SetServiceBusCredentials<T>(this ClientBase<T> proxy,
                                     string issuer,string secret) where T : class;

   public static void SetServiceBusCredentials<T>(this ChannelFactory<T> factory,
                                                   string secret) where T : class;
   public static void SetServiceBusCredentials<T>(this ChannelFactory<T> factory,
                                     string issuer,string secret) where T : class;
}

Using these extensions, Example 11-17 is reduced to:

MyContractClient proxy = new MyContractClient();
proxy.SetServiceBusCredentials("QV3...9M8=");
proxy.MyMethod();
proxy.Close();

Example 11-18 shows the implementation of two of the client-side SetServiceBusCredentials<T>() extensions. Notice the use of the private SetServiceBusCredentials() helper method by wrapping the single endpoint the proxy has with an array of endpoints.

Example 11-18. Implementing SetServiceBusCredentials<T>()

public static class ServiceBusHelper
{
   public static void SetServiceBusCredentials<T>(this ClientBase<T> proxy,
                                                  string secret) where T : class
   {
      if(proxy.State == CommunicationState.Opened)
      {
         throw new InvalidOperationException("Proxy is already opened");
      }
      proxy.ChannelFactory.SetServiceBusCredentials(secret);
   }
   public static void SetServiceBusCredentials<T>(this ChannelFactory<T> factory,
                                      string issuer,string secret) where T : class
   {
      if(factory.State == CommunicationState.Opened)
      {
         throw new InvalidOperationException("Factory is already opened");
      }

      ServiceEndpoint[] endpoints = {factory.Endpoint};
      SetServiceBusCredentials(endpoints,issuer,secret);
   }
   //More members
}

Providing credentials in config file

Since TransportClientEndpointBehavior is just another endpoint behavior, you can also configure it the config file, as shown in Example 11-19.

Example 11-19. Setting the service namespace password in the config file

   <endpoint behaviorConfiguration = "SharedSecret"
      ...
   />
...
<behaviors>
   <endpointBehaviors>
      <behavior name = "SharedSecret">
         <transportClientEndpointBehavior>
            <clientCredentials>
               <sharedSecret
                  issuerName   = "owner"
                  issuerSecret = "QVMh...9M8="
               />
            </clientCredentials>
         </transportClientEndpointBehavior>
      </behavior>
   </endpointBehaviors>
</behaviors>

Warning

Storing the secret as in Example 11-19 in a text config file is highly inadvisable for user machines. For user machines, you should prompt the user for some kind of a login dialog box, authenticate the user using the user credentials against some local credentials store, and then obtain the secret from a secured location using DAPI. You may also want to call the ACS after authenticating the user, obtaining the secret, and then calling the service.

No Authentication

While the service must always authenticate against the service bus, you may decide to exempt the client and allow it unauthenticated access to the service bus. In that case, the client must set TransportClientEndpointBehavior to TransportClientCredentialType.Unauthenticated. When the clients are unauthenticated by the service bus, in the interest of security, it is now up to the relayed service to authenticate the clients. The downside is that in this case, the service is not as shielded as when the service bus was authenticating the clients. In addition, you must use Message security (or Mixed) to transfer the client credentials (as discussed later). To enable unauthenticated access by the client, you must explicitly allow it on both the service and the client by configuring the relay binding to not authenticate, using the enum RelayClientAuthenticationType:

public enum RelayClientAuthenticationType
{
   RelayAccessToken, //Default
   None
}

Assign that enum via the Security property. For example, in the case of the TCP relay binding:

public class NetTcpRelayBinding : NetTcpRelayBindingBase
{...}
public abstract class NetTcpRelayBindingBase : Binding,...
{
  public NetTcpRelaySecurity Security
   {get;}
   //More members
}
public sealed class NetTcpRelaySecurity
{
   public RelayClientAuthenticationType RelayClientAuthenticationType
   {get;set;}
   //More members
}

Example 11-20 shows how to configure the host to allow unauthenticated clients access to the relay service, and Example 11-21 shows the required client-side configuration.

Example 11-20. Configuring the host to allow unauthenticated clients

<services>
   <service ...>
      <endpoint
         binding  = "netTcpRelayBinding"
         bindingConfiguration = "NoServiceBusAuthentication"
         ...
      />
   </service>
</services>
<bindings>
   <netTcpRelayBinding>
      <binding name = "NoServiceBusAuthentication">
         <security relayClientAuthenticationType = "None"/>
      </binding>
   </netTcpRelayBinding>
</bindings>

Example 11-21. Configuring the client for unauthenticated access

<client>
   <endpoint behaviorConfiguration = "NoServiceBusCreds"
      binding = "netTcpRelayBinding"
      bindingConfiguration = "NoServiceBusAuthentication"
      ...
   />
</client>
<bindings>
   <netTcpRelayBinding>
      <binding name = "NoServiceBusAuthentication">
         <security relayClientAuthenticationType = "None"/>
      </binding>
   </netTcpRelayBinding>
</bindings>
<behaviors>
   <endpointBehaviors>
      <behavior name = "NoServiceBusCreds">
         <transportClientEndpointBehavior credentialType = "Unauthenticated"/>
      </behavior>
   </endpointBehaviors>
</behaviors>

Metadata over the Service Bus

Services that rely on the service bus can expose metadata endpoints. Unlike the metadata endpoints described in Chapter 1 for config files, there is no dedicated metadata binding tag, and the MetadataExchangeBindings class does not offer an option for the service bus. Instead, you need to use the regular service bus bindings. For example, to configure a metadata exchange endpoint over TCP:

<endpoint
   address  = "sb://MyNamespace.servicebus.windows.net/MEX1"
   binding  = "netTcpRelayBinding"
   contract = "IMetadataExchange"
/>
<endpoint
   kind     = "mexEndpoint"
   address  = "sb://MyNamespace.servicebus.windows.net/MEX2"
   binding  = "netTcpRelayBinding"
/>

And, of course, you can add the metadata exchange endpoint programmatically (before setting the service bus credential):

ServiceHost host = new ServiceHost(typeof(MyService));

host.AddServiceEndpoint(typeof(IMetadataExchange),
                        new NetTcpRelayBinding(),
                        "sb://IDesign.servicebus.windows.net/MEX3");

ServiceEndpoint endpoint = new ServiceMetadataEndpoint(new NetTcpRelayBinding(),
                 new EndpointAddress("sb://IDesign.servicebus.windows.net/MEX4"));

host.AddServiceEndpoint(endpoint);

host.SetServiceBusCredentials("QV3...9M8=");
host.Open();

Client-side metadata processing

To obtain the metadata, the client must authenticate itself first against the service bus. However, Visual Studio 2010 does not include an option for providing the credentials when adding a service reference. You can use the SvcUtil.exe command-line utility if you provide the credentials as a default endpoint behavior (similar to Example 11-9) in the SvcUtil.exe.config file, yet that is probably insecure and tedious. The client’s best option is to retrieve the metadata programmatically, as shown in Chapter 2. The problem now is that the WCF-provided helper classes (such as MetadataExchangeClient and MetadataResolver) do not expose the endpoint they interact with, so you have no simple way of setting the service bus credentials. The solution is to use reflection to set the private factory field of the MetadataExchangeClient class:

public class MetadataExchangeClient
{
   ChannelFactory<IMetadataExchange> factory;
   //More members
}

To that end, I added the versions of the extension method SetServiceBusCredentials() to ServiceBusHelper, shown in Example 11-22.

Example 11-22. Setting metadata client credentials

public static class ServiceBusHelper
{
   public static void SetServiceBusCredentials(
                                            this MetadataExchangeClient mexClient,
                                            string secret)

   {...}
   public static void SetServiceBusCredentials(
                                            this MetadataExchangeClient mexClient,
                                            string issuer,string secret)
   {
      Type type = mexClient.GetType();
      FieldInfo info = type.GetField(
                          "factory",BindingFlags.Instance|BindingFlags.NonPublic);
      ChannelFactory<IMetadataExchange> factory =
                    info.GetValue(mexClient) as ChannelFactory<IMetadataExchange>;
      factory.SetServiceBusCredentials(issuer,secret);
   }
   //More members
}

Next, I wrote the ServiceBusMetadataHelper class shown in Example 11-23. I modeled it after MetadataHelper from Chapter 2 and it offers comparable metadata queries.

Example 11-23. The ServiceBusMetadataHelper class

public static class ServiceBusMetadataHelper
{
   public static ServiceEndpoint[] GetEndpoints(string mexAddress,string secret);

   public static ServiceEndpoint[] GetEndpoints(string mexAddress,
                                                Type contractType,
                                                string secret);

   public static bool QueryContract(string mexAddress,
                                    Type contractType,string secret);

   public static ContractDescription[] GetContracts(string mexAddress,
                                                    string secret);

   public static ContractDescription[] GetContracts(Type bindingType,
                                                 string mexAddress,string secret);

   public static string[] GetAddresses(string mexAddress,Type contractType,
                                                                   string secret);

   public static string[] GetAddresses(Type bindingType,string mexAddress,
                                       Type contractType,string secret);

   public static string[] GetOperations(string mexAddress,Type contractType,
                                                                   string secret);

  //More overloaded methods that accept an issuer as well
}

Note

My Metadata Explorer tool (presented in Chapter 1) supports exploring service bus metadata exchange endpoints. After specifying the service bus address of the metadata exchange endpoint, the Metadata Explorer will prompt you for the service bus credentials. The tool caches the credentials of the service namespace, and will require them only once per service namespace. You can also log in explicitly by selecting Service BusLog in... in the menu. See Figure C-6.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.216.249