如何:使用传输安全

更新:2007 年 11 月

.NET Compact Framework 3.5 版 支持使用 HTTPS 传输连接到桌面上的 Windows Communication Foundation (WCF) 服务。这包括对服务器身份验证和客户端身份验证的支持。

本主题提供了服务配置的示例,并演示如何修改客户端代码以实现相互身份验证。

说明:

仅限服务器身份验证,不需要客户端证书。.NET Compact Framework 3.5 中还支持消息安全性,但本例中未使用消息安全性。

为台式机创建 WCF 服务

  1. 创建并安装服务器证书和客户端证书。

    这些步骤特定于您正在使用的证书生成工具(例如,Makecert.exe),并且超出了本主题的讨论范围。必须完成以下任务:

    • 创建自签名证书并为其命名(例如,使用您的公司名称:公司)。

    • 创建由公司签名的服务器证书。该服务器证书名称必须与用于访问服务的 URL 主机名相匹配。

    • 创建由公司签名的客户端证书。

    说明:

    我们建议您将服务器证书安装到本地计算机中,而不是安装到当前用户。否则,如果将该服务寄宿在 Internet 信息服务 (IIS) 中,并且将其安装到当前用户,那么该服务将不能运行。

  2. 创建新的 Web 服务项目。

  3. 使用本步骤中的示例替换 Web.config 文件。修改文件中的以下元素和属性:

    • 将 service name 属性更改为您正在使用的新服务。

    • 将 behaviorConfiguration 属性更改为引用新行为名称。

    • 将 endpoint contract 属性更改为引用服务接口。

    说明:

    确保 <endpoint> 元素的 binding 属性值为“basicHttpBinding”。.NET Compact Framework 支持文本编码,但不支持二进制编码。

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.serviceModel>
        <services>
          <service 
              name="CalculatorService"
              behaviorConfiguration="MyServiceTypeBehaviors">
            <endpoint address=""
                      binding="basicHttpBinding"
                      bindingConfiguration="transport"
                      contract="ICalculatorService" />
            <endpoint address="mex"
                      binding="basicHttpBinding"
                      bindingConfiguration="transport"
                      contract="IMetadataExchange" />
          </service>
        </services>
        <bindings>
          <basicHttpBinding>
            <binding name="transport">
              <security mode="Transport">
                <transport clientCredentialType="Certificate" />
              </security>
            </binding>
          </basicHttpBinding>
        </bindings>
        <!--For debugging purposes set the includeExceptionDetailInFaults attribute to true-->
        <behaviors>
          <serviceBehaviors>
            <behavior name="MyServiceTypeBehaviors">
              <serviceMetadata httpsGetEnabled="True" httpsGetUrl=""/>
              <serviceDebug includeExceptionDetailInFaults="False" />
              <serviceCredentials>
                <clientCertificate>
                   <authentication trustedStoreLocation="LocalMachine"
                               revocationMode="NoCheck"
                               certificateValidationMode="ChainTrust"/>
                </clientCertificate>
              </serviceCredentials>
            </behavior>
          </serviceBehaviors>
        </behaviors>
    
      </system.serviceModel>
    
    </configuration>
    
  4. 在 WCF 服务的源代码中,从您的代码中移除在 ServiceContract 和 OperationContract 属性中指定的任何参数。

    说明:

    此示例未实现对协定(如 ServiceContract 和 OperationContract)中所指定的参数的支持。如果需要对这些协定的参数支持,可以使用 WCF .NET Compact Framework ServiceModel 实用工具 (NetCFSvcUtil.exe) 生成客户端代码。此工具将在基于 .NET Compact Framework 的应用程序中生成对其中许多参数的支持。Power Toys for .NET Compact Framework 中提供了 NetCFSvcUtil.exe。有关更多信息,请参见 Power Toys for .NET Compact Framework

    下面的示例演示了一个简化的计算器应用程序的 WCF 服务源代码。

    <ServiceContract()>  _
    Public Interface ICalculatorService
        <OperationContract()>  _
        Function Add(ByVal n1 As Double, ByVal n2 As Double) As Double 
        '<OperationContract()>  _
        Function Subtract(ByVal n1 As Double, ByVal n2 As Double) As Double
    End Interface
    
    
    Public Class CalculatorService
        Implements ICalculatorService
    
        Public Function Add(ByVal n1 As Double, ByVal n2 As Double) As Double Implements ICalculatorService.Add
            Return n1 + n2
    
        End Function
    
        Public Function Subtract(ByVal n1 As Double, ByVal n2 As Double) As Double Implements ICalculatorService.Subtract
            Return n1 - n2
    
        End Function
    End Class
    
    [ServiceContract()]
    public interface ICalculatorService
    {
        [OperationContract]
        double Add(double n1, double n2);
        [OperationContract]
        double Subtract(double n1, double n2);
    }
    
    public class CalculatorService : ICalculatorService
    {
        public double Add(double n1, double n2) { return n1 + n2; }
        public double Subtract(double n1, double n2) { return n1 - n2; }
    }
    
  5. 创建网站或虚拟目录并引用您的 Web 服务项目。在 Web 服务器上,将服务配置为要求 HTTPS 和客户端证书。

    说明:

    在 IIS 中,必须指定服务器证书和客户端证书。

  6. 启动 Web 服务器。

    如果您希望查看 Web 服务描述语言 (WSDL) 输出并在本地主机上运行服务,请浏览到 https://localhost/CalculatorService/Service.svc?wsdl。使用与您为 WCF 服务指定的名称相同的 Web 项目名称。

  7. 验证是否可使用 HTTPS 通过桌面浏览器和设备浏览器访问目录。

    必须确保正确地配置证书,这样才能访问服务。可能还必须配置 Web 服务器才能处理对 WCF 服务的请求。

创建 .NET Compact Framework 客户端

  1. 在服务运行时,打开命令行并导航到 WCF 服务所在的目录。

  2. 从命令行运行 WCF ServiceModel Desktop 实用工具 (SvcUtil.exe) 生成 WCF 客户端代理。下面的示例演示 SvcUtil 的命令行调用,其中服务寄宿在本地主机上:

    svcutil.exe /language:c# https://localhost/CalculatorService/Service.svc
    
  3. 从生成的客户端代理代码中移除不受支持的属性和元素,包括:

    • 所有 System.ServiceModel 属性。

    • IClientChannel 类的引用。

    • 对 <endpoint> 配置名称的引用。

    • 在内部通道上调用 ServiceContract 接口方法的方法实现。

    有关此步骤的示例,请参见如何:使用 HTTP 传输

  4. 创建客户端项目。

  5. 将生成的客户端代理添加到该项目中。

  6. 在生成的代理代码中,将对 ClientBase<TChannel> 的完全限定引用更改为用户定义的 ClientBase 类。

  7. 在生成的代理代码中,通过调用用户定义的 ClientBase 类中的 Call 方法来添加方法实现。

    Public Function Add(ByVal n1 As Double, ByVal n2 As Double) As Double Implements ICalculatorService.Add
        Return System.Convert.ToDouble(MyBase.Call("Add", "https://fabrikam.com/CalcService/ICalculatorService/Add", New String() {"n1", "n2"}, New Object() {n1, n2}, GetType(Double)))
    
    End Function
    
    public double Add(double n1, double n2)
    {
        return (double)base.Call("Add", "https://fabrikam.com/CalcService/ICalculatorService/Add", new string[] { "n1", "n2" }, new object[] { n1, n2 }, typeof(double));
    }
    
  8. 将代理的基类添加到项目中。此类名为 ClientBase。

    将您的客户端代理的基类引用更改为指向您的 ClientBase 实现。

    说明:

    在本例中,ClientBase 中的 CustomBodyWriter 类仅支持基元类型。若要支持非基元类型,您必须扩展 OnWriteBodyContents 方法。例如,可以调用自定义序列化程序来序列化消息数据。在本例中,您要将所生成的客户端代理中的代码属性转换为 XML 序列化程序可以使用的属性。在这种情况下,当您运行 SvcUtil 时必须先添加以下开关:/serializer:xmlserializer http://终结点。

    下面的代码演示了 ClientBase 类的示例。ClientCredentials 对象用于指定客户端所使用的 X.509 证书,在本例中名为 testuser。

    Public Class ClientBase(Of TChannel As Class)
    
        Private requestChannel As IRequestChannel
        Private messageVersion As MessageVersion
    
    
        Public Sub New(ByVal binding As System.ServiceModel.Channels.Binding, ByVal remoteAddress As System.ServiceModel.EndpointAddress)
            'this.remoteAddress = remoteAddress;
            Me.messageVersion = binding.MessageVersion
    
            Dim parameters = New System.ServiceModel.Channels.BindingParameterCollection()
    
            ' Specifies the X.509 certificate used by the client.
            Dim cc As New ClientCredentials()
            cc.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "testuser")
            parameters.Add(cc)
    
            Dim channelFactory As IChannelFactory(Of IRequestChannel)
            channelFactory = binding.BuildChannelFactory(Of IRequestChannel)(parameters)
            channelFactory.Open()
            Me.requestChannel = channelFactory.CreateChannel(remoteAddress)
    
        End Sub
    
    
        Public Function [Call](ByVal op As String, ByVal action As String, ByVal varnames() As String, ByVal varvals() As Object, ByVal returntype As Type) As Object
            requestChannel.Open(TimeSpan.MaxValue)
    
            'Message msg =
            'Message.CreateMessage(MessageVersion.<FromBinding>,
            '      action,
            '      new CustomBodyWriter(op, varnames, varvals,
            '"<ns passed in from Proxy>"));
            Dim msg As Message = Message.CreateMessage(Me.messageVersion, action, New CustomBodyWriter(op, varnames, varvals, "<ns passed in from Proxy>"))
    
            Dim reply As Message = requestChannel.Request(msg, TimeSpan.MaxValue)
            Dim reader As XmlDictionaryReader = reply.GetReaderAtBodyContents()
            reader.ReadToFollowing(op + "Result")
            Return reader.ReadElementContentAs(returntype, Nothing)
    
        End Function
    End Class
    
    
    Friend Class CustomBodyWriter
        Inherits BodyWriter
        Private op As String
        Private varnames() As String
        Private varvals() As Object
        Private ns As String
    
    
        Public Sub New(ByVal op As String, ByVal varnames() As String, ByVal varvals() As Object, ByVal ns As String)
            MyBase.New(True)
            Me.op = op
            Me.varnames = varnames
            Me.varvals = varvals
            Me.ns = ns
    
        End Sub
    
    
        Protected Overrides Sub OnWriteBodyContents(ByVal writer As XmlDictionaryWriter)
            writer.WriteStartElement(op, ns)
            Dim i As Integer
            For i = 0 To varnames.Length
                writer.WriteElementString(varnames(i), varvals(i).ToString())
            Next i
            writer.WriteEndElement()
    
        End Sub
    End Class
    
    public class ClientBase<TChannel>
        where TChannel : class
    {
        private IRequestChannel requestChannel;
        private MessageVersion messageVersion;
    
        public ClientBase(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress)
        {
            //this.remoteAddress = remoteAddress;
            this.messageVersion = binding.MessageVersion;
    
            BindingParameterCollection parameters = new System.ServiceModel.Channels.BindingParameterCollection();
    
            // Specifies the X.509 certificate used by the client.
            ClientCredentials cc = new ClientCredentials();
            cc.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "testuser");
            parameters.Add(cc);
    
            IChannelFactory<IRequestChannel> channelFactory = binding.BuildChannelFactory<IRequestChannel>(
                parameters);
            channelFactory.Open();
            this.requestChannel = channelFactory.CreateChannel(remoteAddress);
        }
    
        public object Call(string op, string action, string[] varnames, object[] varvals, Type returntype)
        {
            requestChannel.Open(TimeSpan.MaxValue);
    
            //Message msg =
            //Message.CreateMessage(MessageVersion.<FromBinding>,
            //      action,
            //      new CustomBodyWriter(op, varnames, varvals,
            //"<ns passed in from Proxy>"));
    
            Message msg =                   
            Message.CreateMessage(this.messageVersion, action,
                  new CustomBodyWriter(op, varnames, varvals,               
            "<ns passed in from Proxy>"));
    
            Message reply = requestChannel.Request(msg, TimeSpan.MaxValue);
            XmlDictionaryReader reader = reply.GetReaderAtBodyContents();
            reader.ReadToFollowing(op + "Result");
            return reader.ReadElementContentAs(returntype, null);
        }
    
    }
    
    internal class CustomBodyWriter : BodyWriter
    {
        private string op;
        private string[] varnames;
        private object[] varvals;
        private string ns;
    
        public CustomBodyWriter(string op, string[] varnames, object[] varvals, string ns)
            : base(true)
        {
            this.op = op;
            this.varnames = varnames;
            this.varvals = varvals;
            this.ns = ns;
        }
    
        protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
        {
            writer.WriteStartElement(op, ns);
            for (int i = 0; i < varnames.Length; i++)
                writer.WriteElementString(varnames[i], varvals[i].ToString());
            writer.WriteEndElement();
        }
    }
    
  9. 将以下引用添加到 ClientBase.cs 中:

  10. 添加一个要实例化的类,并使用客户端代理。

    下面的示例使用绑定对象来指定基于 HTTPS 的传输安全以及将客户端证书用于身份验证。该示例还演示了调用客户端代理的代码。

    Class Program
    
        ''' <summary>
        ''' The main entry point for the application.
        ''' </summary>
        <MTAThread()> _
        Shared Sub Main()
    
            Dim serverAddress As String = CalculatorServiceClient.ServiceEndPoint.Uri.AbsoluteUri
    
            Dim binding As New BasicHttpBinding()
    
            ' Specifies transport security over HTTPS and the use of a
            ' client certificate for authentication.
            binding.Security.Mode = BasicHttpSecurityMode.Transport
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate
    
            Dim proxy = New CalculatorServiceClient(binding, New EndpointAddress(serverAddress))
    
            MessageBox.Show("Add 3 + 6...")
            MessageBox.Show(proxy.Add(3, 6).ToString())
            MessageBox.Show("Subtract 8 - 3...")
            MessageBox.Show(proxy.Subtract(8, 3).ToString())
    
        End Sub
    End Class
    
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [MTAThread]
    
        static void Main()
        {
            string serverAddress = CalculatorServiceClient.ServiceEndPoint.Uri.AbsoluteUri;
    
            BasicHttpBinding binding = new BasicHttpBinding();
    
            // Specifies transport security over HTTPS and the use of a
            // client certificate for authentication.
            binding.Security.Mode = BasicHttpSecurityMode.Transport;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
    
            ICalculatorService proxy = new CalculatorServiceClient(binding, new EndpointAddress(serverAddress));
    
            MessageBox.Show("Add 3 + 6...");
            MessageBox.Show((proxy.Add(3, 6)).ToString());
            MessageBox.Show("Subtract 8 - 3...");        
            MessageBox.Show((proxy.Subtract(8, 3)).ToString());
    
        }
    }
    
  11. 请确保客户端证书已放置在设备上当前用户的证书存储区中。

  12. 生成客户端应用程序并将其部署到您的设备中。

  13. 当 WCF 服务正在运行并且您的设备已连接到网络时,在设备上启动客户端应用程序。

编译代码

WCF 服务的源代码需要引用以下命名空间:

ClientBase 类的源代码需要引用以下命名空间:

客户端应用程序中包含 Main 方法的类的源代码需要引用以下命名空间:

安全性

本示例实现基于相互身份验证的传输安全。它不实现消息安全。

请参见

其他资源

Windows Communication Foundation (WCF) 开发与 .NET Compact Framework