共用方式為


HOW TO:使用傳輸安全性

更新:2007 年 11 月

.NET Compact Framework 3.5 版支援使用 HTTPS 傳輸連接到桌面上的 Windows Communication Foundation (WCF) 服務。它包含伺服器驗證和用戶端驗證的支援。

本主題提供服務組態的範例,並說明如何修改雙向驗證的用戶端程式碼。

注意事項:

若只有伺服器驗證,則不需要用戶端憑證。.NET Compact Framework 3.5 也支援訊息安全性,但是本範例中將不使用。

建立桌面的 WCF 服務

  1. 建立及安裝伺服器憑證和用戶端憑證。

    這些步驟為您使用的憑證產生工具專用 (例如 Makecert.exe),不過不在本主題範圍內。以下為必要的工作:

    • 建立自我簽署憑證並指定名稱 (例如,使用您的公司名稱:company)。

    • 建立 company 簽署的伺服器憑證。伺服器憑證名稱必須符合用來存取服務的 URL 主機名稱。

    • 建立 company 簽署的用戶端憑證。

    注意事項:

    建議您將伺服器憑證安裝到本機電腦上,而不要安裝到目前使用者。否則,如果服務裝載於 Internet Information Services (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 Utility 工具 (NetCFSvcUtil.exe) 產生用戶端程式碼。這個工具會將許多參數的支援建置到以 .NET Compact Framework 為基礎的應用程式中。NetCFSvcUtil.exe 會在 .NET Compact Framework 的 Power Toys 中提供。如需詳細資訊,請參閱 .NET Compact Framework 的 Power Toy (英文)。

    以下範例將說明簡化的計算機應用程式的 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) 輸出並在 localhost 上執行服務,請瀏覽至 https://localhost/CalculatorService/Service.svc?wsdl。使用您為 WCF 服務指定的相同 Web 專案名稱。

  7. 確認您可以使用 HTTPS 從桌面瀏覽器和裝置瀏覽器存取目錄。

    您必須確定憑證已正確設定,才能存取服務。Web 伺服器可能還需要設定為處理 WCF 服務的要求。

建立 .NET Compact Framework 用戶端

  1. 服務執行時,開啟命令列並巡覽至 WCF 服務所在的目錄。

  2. 從命令列執行 WCF ServiceModel Desktop Utility 工具 (SvcUtil.exe) 產生 WCF 用戶端 Proxy。以下範例將說明 SvcUtil 的命令列引動過程 (服務裝載於 localhost 上的該工具中):

    svcutil.exe /language:c# https://localhost/CalculatorService/Service.svc
    
  3. 移除所產生用戶端 Proxy 程式碼中不支援的屬性和項目,包括下列各項:

    • 所有 System.ServiceModel 屬性。

    • IClientChannel 類別的參考。

    • <endpoint> 組態名稱的參考。

    • 方法實作,該實作會呼叫內部通道上 ServiceContract 介面的方法。

    如需這個步驟的範例,請參閱 HOW TO:使用 HTTP 傳輸

  4. 建立用戶端專案。

  5. 將產生的用戶端 Proxy 加入專案中。

  6. 在產生的 Proxy 程式碼中,將完整的 ClientBase<TChannel> 參考變更為使用者定義的 ClientBase 類別。

  7. 在產生的 Proxy 程式碼中,叫用使用者定義的 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. 將 Proxy 的基底類別加入專案中。這個類別的名稱為 ClientBase。

    變更用戶端 Proxy 的基底類別參考,以指向您的 ClientBase 實作。

    注意事項:

    在這個範例中,ClientBase 中的 CustomBodyWriter 類別僅支援基本型別。若要支援非基本型別,則須延伸 OnWriteBodyContents 方法。例如,您可呼叫自訂的序列化程式,以序列化訊息資料。在這種情況下,您會將所產生的用戶端 Proxy 中的程式碼屬性,轉譯成 XML 序列化程式可使用的屬性。在這個案例中,您必須先在執行 SvcUtil 時加入下列參數:/serializer:xmlserializer http://endpoint。

    下列程式碼會示範 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. 加入類別以執行個體化及使用用戶端 Proxy。

    以下範例會使用繫結物件指定透過 HTTPS 的傳輸安全性,以及使用用戶端憑證進行驗證。此外還會示範叫用用戶端 Proxy 的程式碼。

    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