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

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

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

过程

创建自定义证书验证程序

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

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

    ms733806.note(zh-cn,VS.100).gif注意:
    若要将身份验证错误返回到客户端,应在 Validate 方法中引发 FaultException

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 
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");
        }
    }
}

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

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

  2. 添加一个 Behavior element并将 name 属性设置为适当的值。

  3. <behavior> 元素中添加一个 <serviceCredentials> Element

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

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

  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 section

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

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

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

  5. 添加一个 <serviceCertificate> of <clientCredentials> Element

  6. 添加一个 <authentication> of <serviceCertificate> Element,如下面的示例所示。

  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 类派生而来的。)

  2. CertificateValidationMode 属性设置为 Custom

示例

说明

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

代码

Imports System
Imports System.IdentityModel.Selectors
Imports System.Security.Cryptography.X509Certificates
Imports System.ServiceModel
Imports System.ServiceModel.Security
Imports System.IdentityModel.Tokens
Imports System.Security.Permissions

<assembly: SecurityPermission(SecurityAction.RequestMinimum, Execution := True)>
<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 
using System;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Security;

using System.Security.Permissions;

[assembly: SecurityPermission(
   SecurityAction.RequestMinimum, Execution = true)]
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");
            }
        }
    }
}

另请参见

参考

X509CertificateValidator