如何:创建使用自定义证书验证程序的服务

本主题介绍如何实现自定义证书验证程序,以及如何配置客户端或服务凭据以使用自定义证书验证程序替换默认证书验证逻辑。

如果使用 X.509 证书对客户端或服务进行身份验证,则默认情况下,Windows Communication Foundation (WCF) 使用 Windows 证书存储和加密 API 来验证该证书并确保它受信任。 有时,内置证书验证功能并不足够且必须更改。 WCF 提供了一种简单的方法,允许用户添加自定义证书验证程序来更改验证逻辑。 如果指定了自定义证书验证程序,则 WCF 不会使用内置证书验证逻辑,而是依赖于自定义验证程序。

过程

创建自定义证书验证程序

  1. 定义一个从 X509CertificateValidator 派生的新类。

  2. 实现抽象 Validate 方法。 将必须验证的证书作为自变量传递给该方法。 如果根据验证逻辑,传递的证书无效,则此方法引发 SecurityTokenValidationException。 如果证书有效,则此方法返回到调用方。

    备注

    若要将身份验证错误返回到客户端,应在 FaultException 方法中引发 Validate

public class MyX509CertificateValidator : X509CertificateValidator
{
    string allowedIssuerName;

    public MyX509CertificateValidator(string allowedIssuerName)
    {
        if (allowedIssuerName == null)
        {
            throw new ArgumentNullException("allowedIssuerName");
        }

        this.allowedIssuerName = allowedIssuerName;
    }

    public override void Validate(X509Certificate2 certificate)
    {
        // Check that there is a certificate.
        if (certificate == null)
        {
            throw new ArgumentNullException("certificate");
        }

        // Check that the certificate issuer matches the configured issuer.
        if (allowedIssuerName != certificate.IssuerName.Name)
        {
            throw new SecurityTokenValidationException
              ("Certificate was not issued by a trusted issuer");
        }
    }
}
Public Class MyX509CertificateValidator
    Inherits X509CertificateValidator
    Private allowedIssuerName As String

    Public Sub New(ByVal allowedIssuerName As String)
        If allowedIssuerName Is Nothing Then
            Throw New ArgumentNullException("allowedIssuerName")
        End If

        Me.allowedIssuerName = allowedIssuerName

    End Sub

    Public Overrides Sub Validate(ByVal certificate As X509Certificate2)
        ' Check that there is a certificate.
        If certificate Is Nothing Then
            Throw New ArgumentNullException("certificate")
        End If

        ' Check that the certificate issuer matches the configured issuer.
        If allowedIssuerName <> certificate.IssuerName.Name Then
            Throw New SecurityTokenValidationException _
              ("Certificate was not issued by a trusted issuer")
        End If

    End Sub
End Class

指定服务配置中的自定义证书验证程序

  1. <system.serviceModel> 元素添加一个 <behaviors> 元素和一个 <serviceBehaviors>

  2. 添加一个 <behavior>,并将 name 属性设置为适当的值。

  3. <serviceCredentials> 添加到 <behavior> 元素中。

  4. <clientCertificate> 元素中添加一个 <serviceCredentials> 元素。

  5. <clientCertificate> 元素添加一个 <authentication>

  6. customCertificateValidatorType 属性设置为验证程序类型。 下面的示例将该属性设置为类型的命名空间和名称。

  7. certificateValidationMode 属性设置为 Custom

    <configuration>  
     <system.serviceModel>  
      <behaviors>  
       <serviceBehaviors>  
        <behavior name="ServiceBehavior">  
         <serviceCredentials>  
          <clientCertificate>  
          <authentication certificateValidationMode="Custom" customCertificateValidatorType="Samples.MyValidator, service" />  
          </clientCertificate>  
         </serviceCredentials>  
        </behavior>  
       </serviceBehaviors>  
      </behaviors>  
    </system.serviceModel>  
    </configuration>  
    

使用客户端上的配置指定自定义证书验证程序

  1. <system.serviceModel> 元素添加一个 <behaviors> 元素和一个 <serviceBehaviors>

  2. 添加一个 <endpointBehaviors> 元素。

  3. 添加一个 <behavior> 元素,并将 name 属性设置为适当的值。

  4. 添加一个 <clientCredentials> 元素。

  5. 添加一个 <serviceCertificate>

  6. 添加一个 <authentication>,如以下示例所示。

  7. customCertificateValidatorType 属性设置为验证程序类型。

  8. certificateValidationMode 属性设置为 Custom。 下面的示例将该属性设置为类型的命名空间和名称。

    <configuration>  
     <system.serviceModel>  
      <behaviors>  
       <endpointBehaviors>  
        <behavior name="clientBehavior">  
         <clientCredentials>  
          <serviceCertificate>  
           <authentication certificateValidationMode="Custom"
                  customCertificateValidatorType=  
             "Samples.CustomX509CertificateValidator, client"/>  
          </serviceCertificate>  
         </clientCredentials>  
        </behavior>  
       </endpointBehaviors>  
      </behaviors>  
     </system.serviceModel>  
    </configuration>  
    

使用服务上的代码指定自定义证书验证程序

  1. 指定 ClientCertificate 属性的自定义证书验证程序。 您可以使用 Credentials 属性访问服务凭据。

  2. CertificateValidationMode 属性设置为 Custom

serviceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode =
    X509CertificateValidationMode.Custom;
serviceHost.Credentials.ClientCertificate.Authentication.CustomCertificateValidator =
    new MyX509CertificateValidator("CN=Contoso.com");
serviceHost.Credentials.ClientCertificate.Authentication. _
    CertificateValidationMode = X509CertificateValidationMode.Custom
serviceHost.Credentials.ClientCertificate.Authentication. _
   CustomCertificateValidator = New MyX509CertificateValidator("CN=Contoso.com")

使用客户端上的代码指定自定义证书验证程序

  1. 使用 CustomCertificateValidator 属性指定自定义证书验证程序。 您可以使用 Credentials 属性访问客户端凭据。 (由 ServiceModel 元数据实用工具 (Svcutil.exe) 生成的客户端类始终派生自 ClientBase<TChannel> 类。)

  2. CertificateValidationMode 属性设置为 Custom

示例

说明

下面的示例演示自定义证书验证程序的实现及其在服务上的用法。

代码

using System;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Security;

namespace Microsoft.ServiceModel.Samples
{
    [ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples")]
    public interface ICalculator
    {
        [OperationContract]
        double Add(double n1, double n2);
    }

    public class CalculatorService : ICalculator
    {
        public double Add(double n1, double n2)
        {
            double result = n1 + n2;
            return result;
        }
    }

    class Program
    {
        static void Main()
        {
            using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService)))
            {
                serviceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode =
                    X509CertificateValidationMode.Custom;
                serviceHost.Credentials.ClientCertificate.Authentication.CustomCertificateValidator =
                    new MyX509CertificateValidator("CN=Contoso.com");

                serviceHost.Open();
                Console.WriteLine("Service started, press ENTER to stop ...");
                Console.ReadLine();

                serviceHost.Close();
            }
        }
    }

    public class MyX509CertificateValidator : X509CertificateValidator
    {
        string allowedIssuerName;

        public MyX509CertificateValidator(string allowedIssuerName)
        {
            if (allowedIssuerName == null)
            {
                throw new ArgumentNullException("allowedIssuerName");
            }

            this.allowedIssuerName = allowedIssuerName;
        }

        public override void Validate(X509Certificate2 certificate)
        {
            // Check that there is a certificate.
            if (certificate == null)
            {
                throw new ArgumentNullException("certificate");
            }

            // Check that the certificate issuer matches the configured issuer.
            if (allowedIssuerName != certificate.IssuerName.Name)
            {
                throw new SecurityTokenValidationException
                  ("Certificate was not issued by a trusted issuer");
            }
        }
    }
}
Imports System.IdentityModel.Selectors
Imports System.Security.Cryptography.X509Certificates
Imports System.ServiceModel
Imports System.ServiceModel.Security
Imports System.IdentityModel.Tokens
Imports System.Security.Permissions

<ServiceContract([Namespace]:="http://Microsoft.ServiceModel.Samples")> _
Public Interface ICalculator
    <OperationContract()> _
    Function Add(ByVal n1 As Double, ByVal n2 As Double) As Double
End Interface


Public Class CalculatorService
    Implements ICalculator

    Public Function Add(ByVal n1 As Double, ByVal n2 As Double) As Double _
       Implements ICalculator.Add
        Dim result As Double = n1 + n2
        Return result
    End Function
End Class


Class Program

    Shared Sub Main()
        Dim serviceHost As New ServiceHost(GetType(CalculatorService))
        Try
            serviceHost.Credentials.ClientCertificate.Authentication. _
                CertificateValidationMode = X509CertificateValidationMode.Custom
            serviceHost.Credentials.ClientCertificate.Authentication. _
               CustomCertificateValidator = New MyX509CertificateValidator("CN=Contoso.com")
            serviceHost.Open()
            Console.WriteLine("Service started, press ENTER to stop ...")
            Console.ReadLine()

            serviceHost.Close()
        Finally
            serviceHost.Close()
        End Try

    End Sub
End Class

Public Class MyX509CertificateValidator
    Inherits X509CertificateValidator
    Private allowedIssuerName As String

    Public Sub New(ByVal allowedIssuerName As String)
        If allowedIssuerName Is Nothing Then
            Throw New ArgumentNullException("allowedIssuerName")
        End If

        Me.allowedIssuerName = allowedIssuerName

    End Sub

    Public Overrides Sub Validate(ByVal certificate As X509Certificate2)
        ' Check that there is a certificate.
        If certificate Is Nothing Then
            Throw New ArgumentNullException("certificate")
        End If

        ' Check that the certificate issuer matches the configured issuer.
        If allowedIssuerName <> certificate.IssuerName.Name Then
            Throw New SecurityTokenValidationException _
              ("Certificate was not issued by a trusted issuer")
        End If

    End Sub
End Class

另请参阅