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).
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.
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.
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.
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()
.
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 Set
ServiceBusCredentials<T>()
extensions. Notice the use of the private Set
Service
Bus
Credentials()
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 }
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>
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.
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>
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();
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 Set
Service
Bus
Credentials()
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 }
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 Bus→Log in... in the menu. See Figure C-6.
18.188.216.249