サービス ステーション
WCF ベースのサービスでの承認
Dominick Baier および Christian Weyer

目次
分散アプリケーションにサービス指向の原則を採用すると、セキュリティに関して、それ以前とは多少異なる課題に直面します。サービスを呼び出すごとにセキュリティの境界を越えるということを、すぐに実感するでしょう。
すべてに当てはまるわけではありませんが、一般的には、サービスの呼び出しの送信者と受信者の間にネットワークが存在します。通常は通信フレームワークによって自動的に認証が処理されますが、それに加えて、承認に関する固有の戦略とインフラストラクチャを用意する必要があります。
Windows Communication Foundation (WCF) は、サービスで承認を実装するための強力な機能を提供します。簡単に使用できるロールベース システムと、さらに強力かつ複雑なクレームベースの API のいずれかを選択できます。この記事では、両方のシステムを比較し、それぞれのシステムで堅牢なサービス承認を実装する方法について説明します。
ロールベースの承認
ロールベースの承認の基になっている考え方は、ロールの一覧をユーザーに関連付けるということです。実行時に、サービス コードは、ロールの一覧のクエリを実行してセキュリティ関連の決定を行います。これらのロールは、Windows セキュリティ システム (ロールはグループと呼ばれる) やデータベースなどのカスタム ストアから提供されます。
Microsoft .NET Framework のロール API は、IIdentity (ID 情報) と IPrincipal (ロール情報) という 2 つのインターフェイスに基づいています。IIdentity 派生クラスには、ユーザーの名前、ユーザーが認証済みであるかどうか、どのようにして認証されたか、などの情報が保持されます。
IPrincipal 派生クラスは、IsInRole という単一のメソッドを実装する必要があります。このメソッドで、ロール名を渡してブール値を取得します。また、プリンシパルでラップする ID クラスへの参照がプリンシパルに保持されます。
.NET Framework には、これらのインターフェイスの実装がいくつか付属します。WindowsIdentity および WindowsPrincipal は、Windows ユーザーの詳細をラップします。GenericIdentity および GenericPrincipal は、認証されたカスタム ユーザーを表すために使用できます。
さらに、プリンシパルを格納して、アプリケーション内で実行中のスレッド (WCF の操作要求など) にそのプリンシパルを関連付けることもできます。これは、System.Threading.Thread クラスの CurrentPrincipal スレッドの静的プロパティです。一般的な手順としては、アプリケーションの 1 つのパートで Thread.CurrentPrincipal を設定してから、別のパートでこのプロパティにアクセスし、格納された IPrincipal 実装を取得します。続いて、このクラスで IsInRole method を呼び出し、現在のユーザーのロールの一覧に対してクエリを実行して、呼び出し元がその操作に対して承認されていることを確認します。
WCF で行われる作業は、このようになります。構成された認証および資格情報の種類に基づき、WCF は、対応する IIdentity 実装を作成します (Windows 認証では WindowsIdentity、他のほとんどの場合は GenericIdentity)。次に、WCF サービスの動作 (ServiceAuthorization) の構成に基づき、この ID をラップしてロール情報を提供する IPrincipal 実装が作成されます。続いて、サービス操作からアクセスできるように、Thread.CurrentPrincipal に IPrincipal が設定されます。
プログラムで IsInRole を呼び出してロールベースのセキュリティ チェックを実行する方法とは別に、ロール要求によってサービス操作に注釈を付けるための PrincipalPermissionAttribute という属性もあります。PrincipalPermissionAttribute では、SecurityException をスローすることにより、サービス操作の実行よりも前にクライアントに承認の失敗を通知します。WCF は、この例外をキャッチして、アクセス拒否のエラー メッセージを生成します。WCF クライアント プラミングにより、この特定のエラーを SecurityAccessDeniedException という種類の .NET 例外に変換します。図 1 は、ロールのチェックとエラー処理を行うさまざまな方法を示しています。

図 1 ロールのチェックとエラー処理
サービス
class Service : IService {
// only 'users' role member can call this method
[PrincipalPermission(SecurityAction.Demand, Role = 'users')]
public string[] GetRoles(string username) {
// only administrators can retrieve the role information for other users
if (ServiceSecurityContext.Current.PrimaryIdentity.Name != username) {
if (Thread.CurrentPrincipal.IsInRole('administrators')) {
...
}
else {
// access denied
throw new SecurityException();
}
}
}
}
クライアント
var factory = new ChannelFactory<IService>('*');
factory.Credentials.UserName.UserName = 'bob';
factory.Credentials.UserName.Password = 'bob';
var proxy = factory.CreateChannel();
try {
Console.WriteLine('\nBob: roles for Bob --');
proxy.GetRoles('bob').ToList().ForEach(i => Console.WriteLine(i));
Console.WriteLine('\nBob: roles for Alice --');
proxy.GetRoles('alice').ToList().ForEach(i => Console.WriteLine(i));
}
catch (SecurityAccessDeniedException) {
Console.WriteLine('Access Denied\n');
}
ServiceAuthorization サービスの動作により、IPrincipal インスタンスの作成が要求スレッドに関連付けられるように制御されます。既定では、WCF は Windows 認証を前提としており、Thread.CurrentPrincipal に WindowsPrincipal を設定しようとします。
クライアントが Windows ユーザーでない場合は、ASP.NET ロール プロバイダからロールを取得する方法や、カスタム データベースからロールを取得するためのカスタム承認ポリシーを実装する方法を選択できます。または、カスタム IPrincipal 型を実装して、IsInRole 実装を完全に制御することもできます。
ASP.NET ロール プロバイダ
WCF では、ASP.NET ロール プロバイダを使用してユーザーのロールを取得できます。組み込みプロバイダのいずれかを使用するか (SQL Server 用のプロバイダや Microsoft 承認マネージャ)、または System.Web.Security.RoleProvider から派生させた固有のプロバイダを記述します。
ロール プロバイダに接続する場合、WCF では、IsInRole のすべての呼び出しをロール プロバイダの IsUserInRole メソッドに転送する実行中のスレッドに RoleProviderPrincipal をアタッチします。このメソッドは、対象となるユーザー名とロールを受け取り、ユーザーがそのロールのメンバである場合には true を返し、そうでない場合は false を返します。
図 2 では、カスタム ロール プロバイダの例が必要な構成エントリと共に示されています。WCF は、サービスでロールのチェックが発生するたびにロール プロバイダを呼び出します。ロールのチェックが数多く発生する場合は、ロール ストアへの過度のラウンドトリップを回避するために何らかのキャッシング手法を導入することをお勧めします。

図 2 ロール プロバイダと構成エントリの例
ロール プロバイダ
class CustomRoleProvider : RoleProvider {
public override string[] GetRolesForUser(string username) {
if (username == 'administrator') {
return new string[] { 'administrators', 'users' };
}
else {
return new string[] { 'sales', 'marketing', 'users' };
}
}
public override bool IsUserInRole(string username, string roleName) {
return GetRolesForUser(username).Contains(roleName);
}
...
}
構成
<behaviors>
<serviceBehaviors>
<behavior name='security'>
<serviceAuthorization
principalPermissionMode='UseAspNetRoles'
roleProviderName='CustomProvider'
</serviceAuthorization>
</behavior>
</serviceBehaviors>
</behaviors>
<system.web>
<roleManager
enabled='true'
defaultProvider='CustomProvider'>
<providers>
<add name='CustomProvider'
type='Service.CustomRoleProvider, Service' />
</providers>
</roleManager>
</system.web>
キャッシングなどのカスタマイズは、ロール プロバイダ自体に実装するか、カスタム IPrincipal 実装を経由して行うことができます。その方法について見ていきましょう。
カスタム プリンシパル
オプションの 1 つとして、WCF へのカスタム IPrincipal の実装があります。この実装では、各要求の認証段階で暗黙的にコードを実行できるようになります。その手順としては、まず、カスタム プリンシパルを作成して WCF プラミングに返す必要があります。これにより、サービス コードで Thread.CurrentPrincipal からカスタム プリンシパルが利用可能になります。カスタム プリンシパルを使用すると、ロールベースのセキュリティ全体をカスタマイズし、サービス開発者専用のセキュリティ ロジックを公開することができます。
カスタム プリンシパルの記述は簡単です。IPrincipal インターフェイスを実装して、カスタム機能を追加するだけです。プリンシパルを WCF に統合するには、ServiceAuthorization 要素の PrincipalPermissionMode 属性を "custom" に設定して、プリンシパルの作成および WCF への適用のための承認ポリシーを提供する必要があります。
承認ポリシーとは、System.IdentityModel.Policy.IAuthorizationPolicy インターフェイスを実装するクラスです。このインターフェイスの実装では、要求ごとに呼び出される Evaluate というメソッドを使用する必要があります。これで、WCF サービス セキュリティ コンテキストに到達してカスタム プリンシパルを設定することができます。図 3 は、カスタム プリンシパルとその承認ポリシーおよび対応する構成エントリを示しています。

図 3 カスタム プリンシパルと承認ポリシー
プリンシパル
class CustomPrincipal : IPrincipal {
IIdentity _identity;
string[] _roles;
Cache _cache = HttpRuntime.Cache;
public CustomPrincipal(IIdentity identity) {
_identity = identity;
}
// helper method for easy access (without casting)
public static CustomPrincipal Current {
get {
return Thread.CurrentPrincipal as CustomPrincipal;
}
}
public IIdentity Identity {
get { return _identity; }
}
// return all roles (custom property)
public string[] Roles {
get {
EnsureRoles();
return _roles;
}
}
// IPrincipal role check
public bool IsInRole(string role) {
EnsureRoles();
return _roles.Contains(role);
}
// cache roles for subsequent requests
protected virtual void EnsureRoles() {
// caching logic omitted – see the sample download
}
}
承認ポリシー
class AuthorizationPolicy : IAuthorizationPolicy {
// called after the authentication stage
public bool Evaluate(EvaluationContext evaluationContext,
ref object state) {
// get the authenticated client identity from the evaluation context
IIdentity client = GetClientIdentity(evaluationContext);
// set the custom principal
evaluationContext.Properties['Principal'] =
new CustomPrincipal(client);
return true;
}
// rest omitted
}
構成
<behaviors>
<serviceBehaviors>
<behavior name='security'>
<serviceAuthorization principalPermissionMode='Custom'>
<authorizationPolicies>
<add policyType='Service.AuthorizationPolicy, Service' />
</authorizationPolicies>
</serviceAuthorization>
</behavior>
</serviceBehaviors>
</behaviors>
承認ロジックの一元化
ここまでは、サービス操作の中で、WCF セキュリティ システムから提供されるロール情報にアクセスする方法について説明してきました。ただし、場合によっては、受信した各要求の検査と承認の決定を行うことのできる、一元化されたロジックを利用する方法が便利です。すべてのサービス操作に対してロジックを適用する必要がなくなります。ここで、サービス承認マネージャについて説明します。
サービス承認マネージャは、System.ServiceModel.ServiceAuthorizationManager から派生したクラスです。CheckAccessCore メソッドをオーバーライドして各要求のカスタム承認コードを実行することができます。CheckAccess で、現在のセキュリティ コンテキストおよび受信メッセージ (ヘッダーを含む) にアクセスできます。CheckAccess で false が返された場合は、WCF によってアクセス拒否のエラー メッセージが生成され、そのメッセージがクライアントに送信されます。戻り値が true の場合は、サービス操作へのアクセスが許可されます。
WS-Addressing アクション ヘッダーでクライアントが呼び出そうとする操作には、一意の識別子があります。この値を取得するには、操作コンテキストで CheckAccess に渡される IncomingMessageHeader.Action プロパティを使用します。アクション値の形式は次のとおりです。
ServiceNamespace/ContractName/OperationName
たとえば、次のようになります。
urn:msdnmag/ServiceContract/GetRoles
WCF では、完全な要求メッセージを ref パラメータとして渡します。これにより、メッセージがサービス操作に到達する前に (または、承認に失敗したためにメッセージが戻ってくる前に)、メッセージの検査だけでなく変更も行うことができます。
WCF ではメッセージが常に読み取り専用であることに注目してください。つまり、メッセージの本文を検査するには、最初にメッセージのコピーを作成する必要があります。この操作を行うには、次のコードを使用してメッセージ バッファを作成します (サイズの大きいメッセージやストリーミング メッセージについては慎重に対応する必要があります)。
MessageBuffer buffer =
operationContext.RequestContext.RequestMessage.CreateBufferedCopy(
int.MaxValue);
メッセージのコピーを作成したら、標準の API を使用してメッセージ コンテキストにアクセスできます。図 4 には、サービス操作から中央の場所へと承認ロジックを移動するサービス承認マネージャの実装例が示されています。

図 4 サービス承認マネージャ
コード
class AuthorizationManager : ServiceAuthorizationManager {
public override bool CheckAccess(
OperationContext operationContext, ref Message message) {
base.CheckAccess(operationContext, ref message);
string action = operationContext.IncomingMessageHeaders.Action;
if (action == 'urn:msdnmag/IService/GetRoles') {
// messags in WCF are always read-once
// we create one copy to work with, and one copy for WCF
MessageBuffer buffer = operationContext.RequestContext.RequestMessage.
CreateBufferedCopy(int.MaxValue);
message = buffer.CreateMessage();
// get the username value using XPath
XPathNavigator nav = buffer.CreateNavigator();
StandardNamespaceManager nsm = new
StandardNamespaceManager(nav.NameTable);
nsm.AddNamespace('msdn', 'urn:msdnmag');
XPathNavigator node = nav.SelectSingleNode
('s:Envelope/s:Body/msdn:GetRoles/msdn:username', nsm);
string parameter = node.InnerXml;
// check authorization
if (operationContext.ServiceSecurityContext.PrimaryIdentity.Name ==
parameter) {
return true;
}
else {
return (GetPrincipal(operationContext).IsInRole(
'administrators'));
}
}
return true;
}
// rest omitted
}
構成
<serviceAuthorization
serviceAuthorizationManagerType='Service.AuthorizationManager, Service'>
クレームベースの承認
サービスの量と複雑さがある限界に達すると、ロールベースのセキュリティの能力や柔軟性では対処できなくなります。エンタープライズ規模のサービス指向アプリケーションがきわめて複雑になるまで、さして時間はかかりません。さまざまなクライアントで多様なクライアント フレームワークおよび資格情報の種類が使用され、各クライアントが各種のバックエンド サービスとやり取りし、そのバックエンド サービスで別のサービスが呼び出されます。場合によっては、信頼されたサブシステムの手法を利用して直接的な呼び出し元のみが承認されますが、呼び出しチェーンで最初の呼び出し元 ID を転送する必要が生じることもあります。また、内部サービスへの外部クライアント (パートナーや顧客) からのアクセスが必要になると、複雑化が一気に進みます。
システムの複雑化が進んだ場合、たちまち問題となるのは、ID および承認データをモデル化する IIdentity と IPrincipal の機能が不十分であるという点です。ロールベースのセキュリティの使用は二分決定に限定されており、特定のサブジェクトへの任意のデータの関連付けには適用できません。分散システムに属しているエンティティの ID を表現できる、テクノロジに依存しない形式が必要です。
以上の要件に対応するために、.NET Framework Version 3.0 以降では、クレームベースの手法による新しい ID およびアクセス制御 API が導入されています。この新しい API は、System.IdentityModel アセンブリおよび名前空間に存在します。また、マイクロソフトでは、"Zermatt" というコード ネームを持つ新しい ID フレームワークのプレビューをリリースしたばかりです。この新しいフレームワークは、System.IdentityModel のクラスおよび概念に基づいて構築されており、サービスおよびアプリケーションに対するクレームベースのセキュリティをより簡単に構築するためのものです。注目すべき点は、System.IdentityModel および Zermatt は WCF にまったくバインドされておらず、任意の .NET アプリケーション用のクレームに使用できることです。これらの API が WCF に緊密に統合されていることは、意図的ではありません
理解する必要のある System.IdentityModel 名前空間の最も重要な構造クラスは、Claim、ClaimSet、AuthorizationContext、および AuthorizationPolicy です。ここからは、各クラスの詳細と、WCF セキュリティ システムにどのように統合されているかについて説明します。
クレームとは、システム内のエンティティに関連付けることのできる情報の集まりです。ほとんどの場合、エンティティはユーザーですが、サービスやリソースである場合もあります。クレームは、3 つの部分の情報から成っています。クレームの種類、クレームの内容、およびクレームがサブジェクトの ID または機能のどちらを表しているのかを示す情報です。このデータ構造は、System.IdentityModel.Claims.Claim というクラスによって表されます。また、Claim は DataContract であり、シリアル化対応です (サービス境界を越えてクレームをを転送する場合に重要です)。
[DataContract(Namespace =
'http://schemas.xmlsoap.org/ws/2005/05/identity')]
public class Claim {
[DataMember(Name = 'ClaimType')]
public string ClaimType;
[DataMember(Name = 'Resource')]
public object Resource;
[DataMember(Name = 'Right')]
public string Right;
}
ClaimType は、クレームの種類を識別する URI です。固有の種類の URI を使用できますが、ClaimTypes クラスからいくつかの標準的な種類を利用できます。名前を表すクレームでは、ClaimTypes.Name によって保持される URI 値を使用できます。
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
Resource プロパティは、実際のクレーム値を格納します。Resource が型オブジェクトであることに注意してください。つまり、任意の複雑な情報をクレームに関連付けることができます (単純な文字列から複雑なグラフまで)。ただし、これがシリアル化では問題となる場合があることを頭に入れておいてください。問題が生じた場合は、プリミティブ型にしてください。Right プロパティの目的は、クレーム セットについて理解すると明らかになります。
新しいクレームを作成するには、Claim コンストラクタを使用するか、または Claim クラスの静的メソッドの 1 つを利用します (標準的なクレームの種類の場合)。
Claim purchaseLimitClaim = new Claim(
'http://www.leastprivilege.com/claims/purchaselimit',
5000,
Rights.PossessProperty);
Claim nameClaim = Claim.CreateNameClaim('Alice');
一般的に、クレームは単体では使用されません。通常はクレーム セットにグループ化されます。ClaimSet クラスには、クレームの一覧および各クレームの発行者への参照が保持されます。
[DataContract(Namespace='http://schemas.xmlsoap.org/ws/2005/05/identity')]
public abstract class ClaimSet : IEnumerable<Claim>, IEnumerable {
public abstract ClaimSet Issuer { get; }
public abstract Claim this[int index] { get; }
public static ClaimSet System { get; }
public static ClaimSet Windows { get; }
}
発行者は、分散システムが複雑になった場合に重要な概念です。クレームは、さまざまなソースから提供されます (現在のサービス、呼び出し側サービス、セキュリティ トークン サービスなど)。発行者をクレーム セットに関連付けることにより、サービスではクレーム ソースを区別できます (信頼性の判断にも影響します)。
発行者は、クレーム セットとしても表現されます。System.IdentityModel には、System (システムから提供されるクレームの場合) と Windows (Windows セキュリティ サブシステムから提供されるクレームの場合) という 2 つの定義済み発行者クレーム セットが付属します。両方とも、ClaimSet クラスで静的プロパティとして利用可能です。
もう 1 つの重要な概念は、クレーム セットの ID です。クレーム セットは、通常、表現するサブジェクトを一意に識別する 1 つのクレームを必要とします。これには、Claim クラスの Right プロパティが使用されます。クレーム セットは、Identity の Right 値を持つ 1 つのクレーム (一意の識別子) と、PossessProperty の Right 値を持つ複数のクレーム (サブジェクトを説明する追加のクレーム) を保持する必要があります。
固有のクレーム セットを作成するには、DefaultClaimSet インスタンスを作成します。また、さらに制御を高めるには、抽象クラスの ClaimSet より派生させます。
DefaultClaimSet myClaimSet =
new DefaultClaimSet(ClaimSet.System, new List<Claim> {
new Claim(ClaimTypes.Name, 'Alice', Rights.Identity),
new Claim(purchaseLimitClaimType, 5000, Rights.PossessProperty),
Claim.CreateMailAddressClaim(new MailAddress('alice@leastprivilege.com'))
});
System.IdentityModel には、Windows トークンおよび X.509 証明書をクレームに変換するための 2 つのクレーム セットが付属します。それぞれ、WindowsClaimSet と X509CertificateClaimSet です。WCF では、これらの 2 つのクレーム セットが Windows および証明書のクライアント資格情報の種類に使用されます。
ClaimSet は、クレームのクエリのために 2 つの処理プリミティブを提供します。FindClaims と ContainsClaim です。FindClaims は、指定されたクレームの種類に対してクレームのコレクションを返します。ContainsClaim は、特定のクレームの存在に関する情報を返します。
クレーム セットは基本的に汎用であるため、通常は、クレーム セット データ構造に対して動作するドメイン固有の拡張を独自に記述します。C# 3.0 の拡張メソッドは、これらの種類を拡張してカスタム機能を提供する場合に便利な方法です (私が作成した Claim、ClaimSet、および関連する型の拡張メソッドのライブラリを
leastprivilege.com/IdentityModel からダウンロードできます)。
承認コンテキスト
クレームを使用して実際の作業を始める前に、IdentityModel の最後のキーワードである承認コンテキストについて理解する必要があります。承認コンテキストは、クレーム セットのコンテナとして、さらには変換ポリシー (後で説明します) として機能します。WCF では、スレッドの静的な ServiceSecurityContext によって承認コンテキストが利用可能になります。図 5 に示されているコード スニペットを使用して、現在のサービス操作要求に関連付けられているクレーム セットおよびクレームをダンプすることができます。

図 5 クレームとクレーム セットの検索
public void ShowAuthorizationContext() {
AuthorizationContext context =
ServiceSecurityContext.Current.AuthorizationContext;
foreach (ClaimSet set in context.ClaimSets) {
Console.WriteLine('\nIssuer:\n');
Console.WriteLine(set.Issuer.GetType().Name);
foreach (Claim claim in set.Issuer) {
ShowClaim(claim);
}
Console.WriteLine('\nIssued:\n');
Console.WriteLine(set.GetType().Name);
foreach (Claim claim in set) {
ShowClaim(claim);
}
}
}
private void ShowClaim(Claim claim) {
Console.WriteLine('{0}\n{1}\n{2}\n',
claim.ClaimType,
claim.Resource,
claim.Right);
}
このコードを WCF サービスに配置すると、構成されたクライアント資格情報の種類に応じて異なる出力が得られます。Windows 承認が有効になっている場合、WCF で生成されるクレーム セットには、ユーザーの SID (ID クレーム)、グループの SID、およびユーザー名が含まれます。クライアント証明書を使用して認証された要求の場合、クレーム セットには、サブジェクト名、パブリック キー、拇印 (ID クレーム)、有効期限の日付などを示す各クレームが含まれます。ユーザー名とパスワードの単純なペアで認証されるユーザーの場合は、1 つのユーザー名 ID クレームのみが出力されます。
おわかりのように、WCF に統合されたクレーム層により、Windows トークンや証明書などテクノロジ固有の ID 情報が、標準的な API でクエリを実行できる汎用データ構造に変換されます。これにより、複数の資格情報の種類に対応したサービスをより簡単に記述できるようになり、テクノロジ固有の API にこだわる必要がなくなります。また、新しい資格情報の種類を WCF に追加すると、対応するプラミングにより、その独自の形式がクレームに変換されます。
クレームの変換
一般的にサービス操作では、ユーザーの SID や証明書の拇印の情報は不要ですが、ドメイン固有の ID 情報 (ユーザー ID、電子メール アドレス、購入限度など) は必要です。クレームの変換は、テクノロジ固有の ID の詳細をアプリケーション固有の情報に変換するプロセスです。
すべての ID 情報が共通の形式で保持されているため、クレーム情報を解析および分析してアプリケーション コンテキストに適した新しいクレームを作成することは簡単です。WCF で生成されたクレーム セットの ID クレームに基づき、要求をアプリケーションのユーザー ID にマップして、適切な ID および承認情報を新しいクレーム セットに追加することができます。図 6 に示すように、その後は、サービス操作で WCF 承認コンテキストにアクセスして対象となる情報を取得するだけです。
図 6 クレームの変換 (クリックすると拡大画像が表示されます)
クレームの変換は、承認ポリシーで達成されます。既に、カスタム プリンシパルの作成で承認ポリシーを使用しましたが、今回は別の目的があります。承認ポリシーは、クレーム生成プロセスの一部となり、WCF の内部クレーム生成が完了した後で実行されます。つまり、呼び出し元の資格情報に関連付けられたクレームは既にアクセス可能であるということです。
既存のクレームでクエリを実行して ID 情報を取得した後で、その情報に基づき、自分のドメイン固有のクレームをモデル化した新しいクレーム セットを作成できます。作成したものをクレーム セットの一覧に追加できます。この一覧は、要求がサービス操作に到達する前に、承認コンテキストを作成するために使用されます。
IAuthorizationPolicy インターフェイスで実装が必要な 3 つのメンバについて説明します。Id は、ポリシーの一意の識別子を返します (通常は GUID)。Issuer は、このポリシーを作成する発行者を表すクレーム セットを返します。ここで最も重要なメソッドは、Evaluate です。このメソッドは評価コンテキストを受け取ります。評価コンテキストでは、基本的に、まだ構築プロセスにある承認コンテキストが表されます。
EvaluationContext.ClaimSets を通して、現在生成されているすべてのクレームにアクセスできます。Evaluate メソッド内から ServiceSecurityContext にアクセスしないでください。アクセスすると、承認ポリシーが再度トリガされて無限ループが発生してしまいます。新しいクレーム セットを評価コンテキストに追加するには、EvaluationContext.AddClaimSet を呼び出します。このクレーム セットは、後で承認コンテキストの一部となります。
図 7 には、一般的なパターンを実装する承認ポリシーの例が示されています。この例では、まず、最初のクレーム セットの ID クレームを取得します。このクレームは、マッピング コンポーネントに送信されます。そこで、クレームが調査され、アプリケーション ユーザー ID が返されます。その後で、ポリシーは ID を使用してデータ ストアを検出し、クレーム セットの一部となる情報を取得します。この情報は、新しいクレーム セットとしてパッケージ化され、評価コンテキストに追加されます。

図 7 承認ポリシー
class CustomerAuthorizationPolicy : IAuthorizationPolicy {
Guid _id = Guid.NewGuid();
// custom issuer claim set
ApplicationIssuerClaimSet _issuer = new ApplicationIssuerClaimSet();
public bool Evaluate(EvaluationContext evaluationContext,
ref object state) {
Claim id = evaluationContext.ClaimSets.FindIdentityClaim();
string userId = Map(id);
evaluationContext.AddClaimSet(this, new CustomerClaimSet(userId));
return true;
}
public ClaimSet Issuer {
get { return _issuer; }
}
public string Id {
get { return 'CustomerAuthorizationPolicy: ' + _id.ToString(); }
}
}
最後の手順では、serviceAuthorization の動作構成に承認ポリシーを追加します。複数のポリシーを追加すると、追加した順序で起動されます。つまり、あるポリシーで追加された値に次のポリシーが依存するといった、複数の手順から成る承認ポリシーを記述できます。これは、より複雑なシナリオに適した強力な手法です。
<serviceAuthorization>
<authorizationPolicies>
<add policyType='LeastPrivilege.CustomerAuthorizationPolicy, Service' />
<add policyType='some_other_policy' />
</authorizationPolicies>
</serviceAuthorization>
前のコードを再実行して承認コンテキストを調べると、新しいクレーム セットおよびクレームが見つかります。サービス操作では、類似のコードを使用し、承認に関するクレームをチェックします (図 8 を参照)。

図 8 承認に関するクレームをチェック
public void PlaceOrder(Order order) {
int purchaseLimit = GetPurchaseLimit();
if (Order.Total > purchaseLimit) {
// do appropriate action
}
}
private int GetPurchaseLimit() {
AuthorizationContext context =
ServiceSecurityContext.Current.AuthorizationContext;
foreach (ClaimSet set in context.ClaimSets) {
foreach (Claim claim in set.FindClaims(
Constants.PurchaseLimitClaimType,
Rights.PossessProperty)) {
return int.Parse(claim.Resource.ToString());
}
}
throw new Exception('Claim not found');
}
サービス承認マネージャは、クレームベースの承認でも動作します。WCF により、操作コンテキストが CheckAccessCore メソッドに渡されます。そこから、サービス セキュリティ コンテキストにアクセスすると、さらに承認コンテキストにアクセスできます。このように、承認の決定を特定の場所で一元的に行うことができます。
セキュリティ トークン サービス
セキュリティ トークン サービス (STS) とは、セキュリティ ロジックをさらに統合できるようにするツールです。STS の主要なタスクは、ユーザーを認証してセキュリティ トークンを作成することです。このセキュリティ トークンにはクレームを含めることができます。クライアントでは、最初に STS で認証してから、取得したトークンを通信先となるサービスに転送する必要があります。
対象のサービスが STS で認識されるため (この情報はトークン要求の一部)、一元的な承認が可能であり、サービスが依存するクレームを事前に生成できます。したがって、サービス エンドポイントでクレームを変換する必要はありません。STS によって一元的に行われます。この手法を導入すると、システムの複雑化が特定のレベルに達した場合に、セキュリティ インフラストラクチャを大幅に合理化できます。
セキュリティ トークン サービスは、複数の信頼されたドメインを統合するうえで重要なインフラストラクチャ コンポーネントでもあります。複数のトークン サービス間で信頼性を確立することにより、サービスで使用できる信頼境界をまたいでセキュリティ トークンを交換できます。
WCF には、STS を記述するために必要なすべての基本クラスおよび前のシナリオに対する自動的なクライアント側/サービス側のサポートがあります。ただし、関連する WS-* 仕様をすべて適切に実装することは複雑なタスクです。代わりに、市販の STS を購入するか、カスタム STS を構築するためのより高いレベルのツールキット (Zermatt など) を購入してください。次のバージョンの Microsoft Active Directory Federation Services は、完全な機能を備えた WCF 用 STSとなるように設計されています。
Dominick Baier は、ドイツの thinktecture 社 (
www.thinktecture.com) のセキュリティ コンサルタントです。また、DevelopMentor で、セキュリティおよび WCF のカリキュラムの指導も行っており、Developer Security MVP でもあり、『Developing More Secure Microsoft ASP.NET 2.0 Applications』の著者でもあります。彼のブログは
leastprivilege.com で公開されています。