サービス証明書を使用したMessageレベルのセキュリティを使用したWCF Sample 023の続きです。クライアント認証を行いませんでいしたが、ユーザ名とパスワードを使用してクライアント認証を行うサンプルを掲載します。認証にはSystem.IdentityModel.Selectors.UserNamePasswordValidatorを継承したカスタムUserNamePasswordValidatorを作成して行います。認証されたユーザのIdentityはGenericIdentityクラスのインスタンスとして表わされます。

カスタムユーザ認証を行うと、アクセス権限チェックに使用するロール情報が設定されません。今回はさらに加えて、サービスアクセスチェックに使われるカスタムユーザロールを設定するために、System.IdentityModel.Policy.IAuthorizationPolicyを実装したカスタム承認ポリシークラスを作成し、Evaluateメソッド内で、GenericPrincipalを設定します。この処理によって、ServiceAuthorizationManagerによってサービスオペレーションのアクセス許可のチェックにロール情報が使用されるようになります。

今回はWCF Sample023 : 証明書を使用したMessageレベルセキュリティ(クライアント認証なしの)サービスを実装する から続いての処理ですので、証明書を使用したメッセージレベルセキュリティの認証の実装方法はそちらを参照して下さい。

確認環境

  • クライアント、ホスト同一マシン。(IIS7.0でサービスをホスト,クライアントはコンソールアプリケーション)
  • 開発環境 Visual Studio 2008 Professional
  • .NET 3.5

ServiceAuthorizationManagerを継承した、カスタムアクセス権限チェックの作成方法は掲載していませんので、カスタム権限チェックを作成する場合は、参考リンクを参照ください。
- ServiceAuthorizationManager クラス
http://msdn.microsoft.com/ja-jp/library/system.servicemodel.serviceauthorizationmanager(VS.85).aspx
- 承認ポリシー
http://msdn.microsoft.com/ja-jp/library/ms751416(VS.85).aspx

1.WCFサービスサイトの修正

WCF Service ApplicationプロジェクトHelloServiceを修正します。System.IdentityModel名前空間を使用するので、参照の追加でSystem.Identity.dllを追加します。

1.1 カスタムユーザパスワード認証クラスの作成

HelloService.svc.csを開き、カスタムユーザパスワード認証クラスを作成します。カスタムクラスはUserNamePasswordValidatorを継承して実装します。認証はユーザ名とパスワードが一致した場合に成功するようにしています。認証が成功するとユーザはGenericIdentityクラスのインスタンスで表わされます。

namespace HelloService
{
    public class HelloService : IHelloService
    {
        public string HelloWorld(string name)
        {
           /// 省略
        }
    }
    public class CustomUserNamePasswordValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
    {
        /// <summary>
        /// ユーザ名とパスワードが同じの場合に認証成功とする
         /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        public override void Validate(string userName, string password)
        {
            if (!userName.Equals(password))
            {
                throw new FaultException("Invalid user name and password");
            }
        }
    }
}

1.2 カスタム承認ポリシーの作成

同様にHelloService.svc.cs内で、カスタム承認ポリシーを作成します。カスタム承認ポリシークラスはIAuthrizationPolicyを実装することで作成します。承認プロセス内で、GenericPrincipalをEvaluationContextのプロパティ"Principal"に設定します。プリンシパルはGenericPrincipalクラスのインスタンスで表わされます。

namespace HelloService
{
    public class HelloService : IHelloService
    {
        public string HelloWorld(string name)
        {
      // 省略
        }
    }
    public class CustomUserNamePasswordValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
    {
        // 省略
    }
    public class CustomAuthorizationPolicy : System.IdentityModel.Policy.IAuthorizationPolicy
    {
        string _id = Guid.NewGuid().ToString();
        #region IAuthorizationPolicy Members

        public bool Evaluate(System.IdentityModel.Policy.EvaluationContext evaluationContext, ref object state)
        {
            object obj;
            if (!evaluationContext.Properties.TryGetValue("Identities", out obj))
                return false;

            IList<System.Security.Principal.IIdentity> identities = obj as IList<System.Security.Principal.IIdentity>;
            if (obj == null || identities.Count <= 0)
                return false;

            evaluationContext.Properties["Principal"] = new System.Security.Principal.GenericPrincipal(identities[0], new string[] { "role1", "role2" });
            return true;


        }

        public System.IdentityModel.Claims.ClaimSet Issuer
        {
            get { return System.IdentityModel.Claims.ClaimSet.System; }
        }

        #endregion

        #region IAuthorizationComponent Members

        public string Id
        {
            get { return _id; }
        }

        #endregion
    }
}

1.3 構成ファイルの修正

構成ファイルWeb.configを編集して、クライントの認証をUserNameにし、クライアント認証にCustomUserNamePasswordValidatorを使用し、使用する承認ポリシーにCustomAuthorizationPolicyを追加します。system.serviceModelの部分のみ掲載します。

serviceAuthorization要素でprincipalMermissionModeをCustomに設定し,authorizationPolicies要素内でカスタム認証ポリシーを追加します。userNameAuthentication要素ではuserNamePasswordValidationModeにCustomを設定し、クライアント認証を行うクラスをcustomUserNamePasswordValidatorTypeで指定しています。

クライアント認証を行うためにsecurity要素のmessage要素のclientCredentialType属性にUserNameを指定します。

<system.serviceModel>
  <behaviors>
   <serviceBehaviors>
    <behavior name="HelloServiceBehavior">
     <serviceCredentials>
      <serviceCertificate findValue="TestWCFServiceCert" x509FindType="FindBySubjectName" />
       <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="HelloService.CustomUserNamePasswordValidator, HelloService" />
     </serviceCredentials>
      <serviceAuthorization principalPermissionMode="Custom">
        <authorizationPolicies>
          <add policyType="HelloService.CustomAuthorizationPolicy, HelloService"/>
        </authorizationPolicies>
      </serviceAuthorization>
     <serviceDebug includeExceptionDetailInFaults="true" />
     <serviceMetadata httpGetEnabled="true" />
    </behavior>
   </serviceBehaviors>
  </behaviors>
  <bindings>
   <wsHttpBinding>
    <binding name="HelloServiceBinding">
     <security>
      <message clientCredentialType="UserName" />
     </security>
    </binding>
   </wsHttpBinding>
  </bindings>
  <services>
   <service behaviorConfiguration="HelloServiceBehavior" name="HelloService.HelloService">
    <endpoint address="" binding="wsHttpBinding" bindingConfiguration="HelloServiceBinding"
     contract="HelloService.IHelloService" />
   </service>
  </services>
 </system.serviceModel>

 serviceAuthorization要素のprincipalPermissionModeでは、Custom以外にNone(Thread.CurrentPrincipalにプリンリパルを設定しない),UseWindowsGroups(Windowsのグループ情報を使用),UseAspNetRoles(ロールプロバイダで提供されるロール情報を使用)があります。wsHttpBindingではUseWindowsGroupsが既定値です。UseAspNetRolesを使用したサンプルはWCF Sample 012その2 SqlMembershipProvider,SqlRoleProviderを使用してクライアントを認証する。(アクセス許可制御付き) で掲載しています。

カスタム認証の効果がわかるように、HelloService.svc.csのサービスクラスのHelloWorldサービスオペレーションも少し修正します。

namespace HelloService
{
    public class HelloService : IHelloService
    {
        public string HelloWorld(string name)
        {
            bool isInrole = System.Threading.Thread.CurrentPrincipal.IsInRole("role2");
            return string.Format("Hello {0}, UserName {1}, IsInRole(role2) {2}", 
                                        name,  SecurityContext.PrimaryIdentity.Name, isInrole);

        }
    }
    public class CustomUserNamePasswordValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
    {
       // 省略
    }
    public class CustomAuthorizationPolicy : System.IdentityModel.Policy.IAuthorizationPolicy
    {
       // 省略
    }
}

2. クライアントプロジェクトの修正

WCFSample.ConsoleClientプロジェクトを開き、Program.csを編集します。編集点は、サービスオペレーションを呼び出す前に、ユーザ名とパスワードを設定する部分のみです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace WCFSample.ConsoleClient
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Console.ReadLine();
            try
            {
                HelloServiceClient proxy = new HelloServiceClient();
                proxy.ClientCredentials.UserName.UserName = "Smith";
                proxy.ClientCredentials.UserName.Password = "Smith";
                System.Console.WriteLine(proxy.HelloWorld("Tarou"));
                ((ICommunicationObject)proxy).Close();
                proxy = null;
            }
            catch (Exception e)
            {
                System.Console.WriteLine(e.Message);
            }
            System.Console.ReadLine();
        }
    }
}

カスタム認証とカスタム承認ポリシークラスを使用すると、AspNetのメンバシッププロバイダとロールプロバイダを使用するよりも簡単にカスタムIdentityとIPrincipalを設定できるようになります。

説明は以上です。間違い、指摘点等がありましたら、ご連絡ください。