Επεξεργασία

Κοινή χρήση μέσω


Walkthrough: Creating Custom Client and Service Credentials

This topic shows how to implement custom client and service credentials and how to use custom credentials from application code.

Credentials Extensibility Classes

The ClientCredentials and ServiceCredentials classes are the main entry points to the Windows Communication Foundation (WCF) security extensibility. These credentials classes provide the APIs that enable application code to set credentials information and to convert credential types into security tokens. (Security tokens are the form used to transmit credential information inside SOAP messages.) The responsibilities of these credentials classes can be divided into two areas:

  • Provide the APIs for applications to set credentials information.

  • Perform as a factory for SecurityTokenManager implementations.

The default implementations provided in WCF support the system-provided credential types and create a security token manager that is capable of handling those credentials types.

Reasons to Customize

There are multiple reasons for customizing either client or service credential classes. Foremost is the requirement to change the default WCF security behavior with regard to handling system-provided credential types, especially for the following reasons:

  • Changes that are not possible using other extensibility points.

  • Adding new credential types.

  • Adding new custom security token types.

This topic describes how to implement custom client and service credentials and how to use them from application code.

First in a Series

Creating a custom credentials class is only the first step, because the reason for customizing credentials is to change WCF behavior regarding credentials provisioning, security token serialization, or authentication. Other topics in this section describe how to create custom serializers and authenticators. In this regard, creating custom credential class is the first topic in the series. Subsequent actions (creating custom serializers and authenticators) can be done only after creating custom credentials. Additional topics that build upon this topic include:

Procedures

To implement custom client credentials

  1. Define a new class derived from the ClientCredentials class.

  2. Optional. Add new methods or properties for new credential types. If you do not add new credential types, skip this step. The following example adds a CreditCardNumber property.

  3. Override the CreateSecurityTokenManager method. This method is automatically called by WCF security infrastructure when the custom client credential is used. This method is responsible for creating and returning an instance of an implementation of the SecurityTokenManager class.

    Important

    It is important to note that the CreateSecurityTokenManager method is overridden to create a custom security token manager. The security token manager, derived from ClientCredentialsSecurityTokenManager, must return a custom security token provider, derived from SecurityTokenProvider, to create the actual security token. If you do not follow this pattern for creating security tokens, your application may function incorrectly when ChannelFactory objects are cached (which is the default behavior for WCF client proxies), potentially resulting in an elevation of privilege attack. The custom credential object is cached as part of the ChannelFactory. However, the custom SecurityTokenManager is created on every invocation, which mitigates the security threat as long as the token creation logic is placed in the SecurityTokenManager.

  4. Override the CloneCore method.

    public class MyClientCredentials : ClientCredentials
    {
        string creditCardNumber;
    
        public MyClientCredentials()
        {
            // Perform client credentials initialization.
        }
    
        protected MyClientCredentials(MyClientCredentials other)
            : base(other)
        {
            // Clone fields defined in this class.
            this.creditCardNumber = other.creditCardNumber;
        }
    
        public string CreditCardNumber
        {
            get
            {
                return this.creditCardNumber;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                this.creditCardNumber = value;
            }
        }
    
        public override SecurityTokenManager CreateSecurityTokenManager()
        {
            // Return your implementation of the SecurityTokenManager.
            return new MyClientCredentialsSecurityTokenManager(this);
        }
    
        protected override ClientCredentials CloneCore()
        {
            // Implement the cloning functionality.
            return new MyClientCredentials(this);
        }
    }
    
    Public Class MyClientCredentials
        Inherits ClientCredentials
        Private creditCardNumberValue As String
    
        Public Sub New()
    
        End Sub
    
        ' Perform client credentials initialization.    
        Protected Sub New(ByVal other As MyClientCredentials)
            MyBase.New(other)
            ' Clone fields defined in this class.
            Me.creditCardNumberValue = other.creditCardNumberValue
    
        End Sub
    
        Public Property CreditCardNumber() As String
            Get
                Return Me.creditCardNumberValue
            End Get
            Set
                If value Is Nothing Then
                    Throw New ArgumentNullException("value")
                End If
                Me.creditCardNumberValue = value
            End Set
        End Property
    
        Public Overrides Function CreateSecurityTokenManager() As SecurityTokenManager
            ' Return your implementation of the SecurityTokenManager.
            Return New MyClientCredentialsSecurityTokenManager(Me)
    
        End Function
    
        Protected Overrides Function CloneCore() As ClientCredentials
            ' Implement the cloning functionality.
            Return New MyClientCredentials(Me)
    
        End Function
    End Class
    

To implement a custom client security token manager

  1. Define a new class derived from ClientCredentialsSecurityTokenManager.

  2. Optional. Override the CreateSecurityTokenProvider(SecurityTokenRequirement) method if a custom SecurityTokenProvider implementation must be created. For more information about custom security token providers, see How to: Create a Custom Security Token Provider.

  3. Optional. Override the CreateSecurityTokenAuthenticator(SecurityTokenRequirement, SecurityTokenResolver) method if a custom SecurityTokenAuthenticator implementation must be created. For more information about custom security token authenticators, see How to: Create a Custom Security Token Authenticator.

  4. Optional. Override the CreateSecurityTokenSerializer method if a custom SecurityTokenSerializer must be created. For more information about custom security tokens and custom security token serializers, see How to: Create a Custom Token.

    internal class MyClientCredentialsSecurityTokenManager :
        ClientCredentialsSecurityTokenManager
    {
        MyClientCredentials credentials;
    
        public MyClientCredentialsSecurityTokenManager(MyClientCredentials credentials)
            : base(credentials)
        {
            this.credentials = credentials;
        }
    
        public override SecurityTokenProvider CreateSecurityTokenProvider(
            SecurityTokenRequirement tokenRequirement)
        {
            // Return your implementation of the SecurityTokenProvider, if required.
            // This implementation delegates to the base class.
            return base.CreateSecurityTokenProvider(tokenRequirement);
        }
    
        public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(
            SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
        {
            // Return your implementation of the SecurityTokenAuthenticator, if required.
            // This implementation delegates to the base class.
            return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
        }
    
        public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version)
        {
            // Return your implementation of the SecurityTokenSerializer, if required.
            // This implementation delegates to the base class.
            return base.CreateSecurityTokenSerializer(version);
        }
    }
    
    
    Friend Class MyClientCredentialsSecurityTokenManager
        Inherits ClientCredentialsSecurityTokenManager
        Private credentials As MyClientCredentials
    
    
        Public Sub New(ByVal credentials As MyClientCredentials)
            MyBase.New(credentials)
            Me.credentials = credentials
    
        End Sub
    
    
        Public Overrides Function CreateSecurityTokenProvider( _
        ByVal tokenRequirement As SecurityTokenRequirement) As SecurityTokenProvider
            ' Return your implementation of the SecurityTokenProvider, if required.
            ' This implementation delegates to the base class.
            Return MyBase.CreateSecurityTokenProvider(tokenRequirement)
    
        End Function
    
    
        Public Overrides Function CreateSecurityTokenAuthenticator( _
        ByVal tokenRequirement As SecurityTokenRequirement, _
        ByRef outOfBandTokenResolver As SecurityTokenResolver) As SecurityTokenAuthenticator
            ' Return your implementation of the SecurityTokenAuthenticator, if required.
            ' This implementation delegates to the base class.
            Return MyBase.CreateSecurityTokenAuthenticator(tokenRequirement, outOfBandTokenResolver)
    
        End Function
    
    
        Public Overrides Function CreateSecurityTokenSerializer(ByVal version As SecurityTokenVersion) _
        As SecurityTokenSerializer
            ' Return your implementation of the SecurityTokenSerializer, if required.
            ' This implementation delegates to the base class.
            Return MyBase.CreateSecurityTokenSerializer(version)
    
        End Function
    End Class
    

To use a custom client credentials from application code

  1. Either create an instance of the generated client that represents the service interface, or create an instance of the ChannelFactory pointing to a service you want to communicate with.

  2. Remove the system-provided client credentials behavior from the Behaviors collection, which can be accessed through the Endpoint property.

  3. Create a new instance of a custom client credentials class and add it to the Behaviors collection, which can be accessed through the Endpoint property.

    // Create a client with the client endpoint configuration.
    CalculatorClient client = new CalculatorClient();
    
    // Remove the ClientCredentials behavior.
    client.ChannelFactory.Endpoint.Behaviors.Remove<ClientCredentials>();
    
    // Add a custom client credentials instance to the behaviors collection.
    client.ChannelFactory.Endpoint.Behaviors.Add(new MyClientCredentials());
    
    ' Create a client with the client endpoint configuration.
    Dim client As New CalculatorClient()
    
    ' Remove the ClientCredentials behavior.
    client.ChannelFactory.Endpoint.Behaviors.Remove(Of ClientCredentials)()
    
    ' Add a custom client credentials instance to the behaviors collection.
    client.ChannelFactory.Endpoint.Behaviors.Add(New MyClientCredentials())
    

The previous procedure shows how to use client credentials from application code. WCF credentials can also be configured using the application configuration file. Using application configuration is often preferable to hard-coding because it enables modification of application parameters without having to modify the source, recompiling, and redeployment.

The next procedure describes how to provide support for configuration of custom credentials.

Creating a configuration handler for custom client credentials

  1. Define a new class derived from ClientCredentialsElement.

  2. Optional. Add properties for all additional configuration parameters that you want to expose through application configuration. The example below adds one property named CreditCardNumber.

  3. Override the BehaviorType property to return the type of the custom client credentials class created with the configuration element.

  4. Override the CreateBehavior method. The method is responsible for creating and returning an instance of the custom credential class based on the settings loaded from the configuration file. Call the base ApplyConfiguration(ClientCredentials) method from this method to retrieve the system-provided credentials settings loaded into your custom client credentials instance.

  5. Optional. If you added additional properties in step 2, you need to override the Properties property in order to register your additional configuration settings for the configuration framework to recognize them. Combine your properties with the base class properties to allow the system-provided settings to be configured through this custom client credentials configuration element.

    public class MyClientCredentialsConfigHandler : ClientCredentialsElement
    {
        ConfigurationPropertyCollection properties;
    
        public override Type BehaviorType
        {
            get { return typeof(MyClientCredentials); }
        }
    
        public string CreditCardNumber
        {
            get { return (string)base["creditCardNumber"]; }
            set
            {
                if (String.IsNullOrEmpty(value))
                {
                    value = String.Empty;
                }
                base["creditCardNumber"] = value;
            }
        }
    
        protected override ConfigurationPropertyCollection Properties
        {
            get
            {
                if (this.properties == null)
                {
                    ConfigurationPropertyCollection properties = base.Properties;
                    properties.Add(new ConfigurationProperty(
                        "creditCardNumber",
                        typeof(System.String),
                        string.Empty,
                        null,
                        new StringValidator(0, 32, null),
                        ConfigurationPropertyOptions.None));
                    this.properties = properties;
                }
                return this.properties;
            }
        }
    
        protected override object CreateBehavior()
        {
            MyClientCredentials creds = new MyClientCredentials();
            creds.CreditCardNumber = CreditCardNumber;
            base.ApplyConfiguration(creds);
            return creds;
        }
    }
    
    
    Public Class MyClientCredentialsConfigHandler
        Inherits ClientCredentialsElement
        Private propertiesValue As ConfigurationPropertyCollection
    
    
        Public Overrides ReadOnly Property BehaviorType() As Type
            Get
                Return GetType(MyClientCredentials)
            End Get
        End Property
    
        Public Property CreditCardNumber() As String
            Get
                Return CStr(MyBase.Item("creditCardNumber"))
            End Get
            Set
                If String.IsNullOrEmpty(value) Then
                    value = String.Empty
                End If
                MyBase.Item("creditCardNumber") = value
            End Set
        End Property
    
    
        Protected Overrides ReadOnly Property Properties() As ConfigurationPropertyCollection
            Get
                If Me.propertiesValue Is Nothing Then
                    Dim myProperties As ConfigurationPropertyCollection = MyBase.Properties
                    myProperties.Add(New ConfigurationProperty( _
                    "creditCardNumber", _
                    GetType(System.String), _
                    String.Empty, _
                    Nothing, _
                    New StringValidator(0, 32, Nothing), _
                    ConfigurationPropertyOptions.None))
                    Me.propertiesValue = myProperties
                End If
                Return Me.propertiesValue
            End Get
        End Property
    
    
        Protected Overrides Function CreateBehavior() As Object
            Dim creds As New MyClientCredentials()
            creds.CreditCardNumber = Me.CreditCardNumber
            MyBase.ApplyConfiguration(creds)
            Return creds
    
        End Function
    End Class
    

Once you have the configuration handler class, it can be integrated into the WCF configuration framework. That enables the custom client credentials to be used in the client endpoint behavior elements, as shown in the next procedure.

To register and use a custom client credentials configuration handler in the application configuration

  1. Add an <extensions> element and a <behaviorExtensions> element to the configuration file.

  2. Add an <add> element to the <behaviorExtensions> element and set the name attribute to an appropriate value.

  3. Set the type attribute to the fully-qualified type name. Also include the assembly name and other assembly attributes.

    <system.serviceModel>
      <extensions>
        <behaviorExtensions>
          <add name="myClientCredentials" type="Microsoft.ServiceModel.Samples.MyClientCredentialsConfigHandler, CustomCredentials, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        </behaviorExtensions>
      </extensions>
    </system.serviceModel>
    
  4. After registering your configuration handler, the custom credentials element can be used inside the same configuration file instead of the system-provided <clientCredentials> element. You can use both the system-provided properties and any new properties that you have added to your configuration handler implementation. The following example sets the value of a custom property using the creditCardNumber attribute.

    <behaviors>
      <endpointBehaviors>
        <behavior name="myClientCredentialsBehavior">
          <myClientCredentials creditCardNumber="123-123-123"/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    

To implement custom service credentials

  1. Define a new class derived from ServiceCredentials.

  2. Optional. Add new properties to provide APIs for new credential values that are being added. If you do not add new credential values, skip this step. The following example adds an AdditionalCertificate property.

  3. Override the CreateSecurityTokenManager method. This method is automatically called by the WCF infrastructure when the custom client credential is used. The method is responsible for creating and returning an instance of an implementation of the SecurityTokenManager class (described in the next procedure).

  4. Optional. Override the CloneCore method. This is required only if adding new properties or internal fields to the custom client credentials implementation.

    public class MyServiceCredentials : ServiceCredentials
    {
        X509Certificate2 additionalCertificate;
    
        public MyServiceCredentials()
        {
        }
    
        protected MyServiceCredentials(MyServiceCredentials other)
            : base(other)
        {
            this.additionalCertificate = other.additionalCertificate;
        }
    
        public X509Certificate2 AdditionalCertificate
        {
            get
            {
                return this.additionalCertificate;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                this.additionalCertificate = value;
            }
        }
    
        public override SecurityTokenManager CreateSecurityTokenManager()
        {
            return base.CreateSecurityTokenManager();
        }
    
        protected override ServiceCredentials CloneCore()
        {
            return new MyServiceCredentials(this);
        }
    }
    
    Public Class MyServiceCredentials
        Inherits ServiceCredentials
        Private additionalCertificateValue As X509Certificate2
    
        Public Sub New()
    
        End Sub
    
        Protected Sub New(ByVal other As MyServiceCredentials)
            MyBase.New(other)
            Me.additionalCertificate = other.additionalCertificate
        End Sub
    
    
        Public Property AdditionalCertificate() As X509Certificate2
            Get
                Return Me.additionalCertificateValue
            End Get
            Set
                If value Is Nothing Then
                    Throw New ArgumentNullException("value")
                End If
                Me.additionalCertificateValue = value
            End Set
        End Property
    
        Public Overrides Function CreateSecurityTokenManager() As SecurityTokenManager
            Return MyBase.CreateSecurityTokenManager()
    
        End Function
    
    
        Protected Overrides Function CloneCore() As ServiceCredentials
            Return New MyServiceCredentials(Me)
    
        End Function
    End Class
    

To implement a custom service security token manager

  1. Define a new class derived from the ServiceCredentialsSecurityTokenManager class.

  2. Optional. Override the CreateSecurityTokenProvider method if a custom SecurityTokenProvider implementation must be created. For more information about custom security token providers, see How to: Create a Custom Security Token Provider.

  3. Optional. Override the CreateSecurityTokenAuthenticator method if a custom SecurityTokenAuthenticator implementation must be created. For more information about custom security token authenticators, see How to: Create a Custom Security Token Authenticator topic.

  4. Optional. Override the CreateSecurityTokenSerializer(SecurityTokenVersion) method if a custom SecurityTokenSerializer must be created. For more information about custom security tokens and custom security token serializers, see How to: Create a Custom Token.

    internal class MyServiceCredentialsSecurityTokenManager :
        ServiceCredentialsSecurityTokenManager
    {
        MyServiceCredentials credentials;
    
        public MyServiceCredentialsSecurityTokenManager(MyServiceCredentials credentials)
            : base(credentials)
        {
            this.credentials = credentials;
        }
    
        public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
        {
            // Return your implementation of SecurityTokenProvider, if required.
            // This implementation delegates to the base class.
            return base.CreateSecurityTokenProvider(tokenRequirement);
        }
    
        public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
        {
            // Return your implementation of SecurityTokenProvider, if required.
            // This implementation delegates to the base class.
            return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
        }
    
        public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version)
        {
            // Return your implementation of SecurityTokenProvider, if required.
            // This implementation delegates to the base class.
            return base.CreateSecurityTokenSerializer(version);
        }
    }
    
    Friend Class MyServiceCredentialsSecurityTokenManager
        Inherits ServiceCredentialsSecurityTokenManager
        Private credentials As MyServiceCredentials
    
        Public Sub New(ByVal credentials As MyServiceCredentials)
            MyBase.New(credentials)
            Me.credentials = credentials
    
        End Sub
    
    
        Public Overrides Function CreateSecurityTokenProvider(ByVal tokenRequirement As SecurityTokenRequirement) _
        As SecurityTokenProvider
            ' Return your implementation of SecurityTokenProvider, if required.
            ' This implementation delegates to the base class.
            Return MyBase.CreateSecurityTokenProvider(tokenRequirement)
    
        End Function
    
        Public Overrides Function CreateSecurityTokenAuthenticator( _
        ByVal tokenRequirement As SecurityTokenRequirement, _
        ByRef outOfBandTokenResolver As SecurityTokenResolver) _
        As SecurityTokenAuthenticator
            ' Return your implementation of SecurityTokenProvider, if required.
            ' This implementation delegates to the base class.
            Return MyBase.CreateSecurityTokenAuthenticator(tokenRequirement, outOfBandTokenResolver)
    
        End Function
    
    
        Public Overrides Function CreateSecurityTokenSerializer(ByVal version As SecurityTokenVersion) _
        As SecurityTokenSerializer
            ' Return your implementation of SecurityTokenProvider, if required.
            ' This implementation delegates to the base class.
            Return MyBase.CreateSecurityTokenSerializer(version)
    
        End Function
    End Class
    

To use custom service credentials from application code

  1. Create an instance of the ServiceHost.

  2. Remove the system-provided service credentials behavior from the Behaviors collection.

  3. Create a new instance of the custom service credentials class and add it to the Behaviors collection.

    // Create a service host with a service type.
    ServiceHost serviceHost = new ServiceHost(typeof(Service));
    
    // Remove the default ServiceCredentials behavior.
    serviceHost.Description.Behaviors.Remove<ServiceCredentials>();
    
    // Add a custom service credentials instance to the collection.
    serviceHost.Description.Behaviors.Add(new MyServiceCredentials());
    
    ' Create a service host with a service type.
    Dim serviceHost As New ServiceHost(GetType(Service))
    
    ' Remove the default ServiceCredentials behavior.
    serviceHost.Description.Behaviors.Remove(Of ServiceCredentials)()
    
    ' Add a custom service credentials instance to the collection.
    serviceHost.Description.Behaviors.Add(New MyServiceCredentials())
    

Add support for configuration using the steps described previously in the procedures "To create a configuration handler for custom client credentials" and "To register and use a custom client credentials configuration handler in the application configuration." The only difference is to use the ServiceCredentialsElement class instead of the ClientCredentialsElement class as a base class for the configuration handler. The custom service credential element can then be used wherever the system-provided <serviceCredentials> element is used.

See also