方法 : トランスポート セキュリティを使用する

更新 : 2007 年 11 月

.NET Compact Framework version 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 は Power Toys for .NET Compact Framework に含まれています。詳しくは、「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 サービス プロジェクトを参照します。Web サーバー上で、HTTPS およびクライアント証明書を要求するようにサービスを構成します。

    メモ :

    IIS で、サーバー証明書とクライアント証明書を指定する必要があります。

  6. Web サーバーを起動します。

    localhost 上で Web サービス記述言語 (WSDL: Web Services Description Language) 出力を表示しサービスを実行するには、https://localhost/CalculatorService/Service.svc?wsdl を参照します。WCF サービス用に指定したのと同じ Web プロジェクト名を使用してください。

  7. HTTPS を使用してデスクトップ ブラウザおよびデバイス ブラウザからディレクトリにアクセスできることを確認します。

    サービスにアクセスする前に、証明書が正しく構成されていることを確認する必要があります。また、WCF サービスの要求を処理するよう Web サーバーを構成する必要が生じる場合もあります。

.NET Compact Framework クライアントを作成するには

  1. サービスの実行中に、コマンド ラインを開いて、WCF サービスが入っているディレクトリに移動します。

  2. コマンド ラインから、WCF ServiceModel Desktop Utility ツール (SvcUtil.exe) を実行して WCF クライアント プロキシを生成します。SvcUtil を呼び出すためのコマンド ラインの例を次に示します。ここではサービスが localhost でホストされます。

    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://endpoint というスイッチを追加する必要があります。

    次のコードは、ClientBase クラスの例を示しています。ClientCredentials オブジェクトは、(この例では testuser という名前の) クライアントによって使用される X.509 証明書を指定するために使用されます。

    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