質問
安全な WCF サービスを実装しています。認証は、ユーザー名/パスワード、または Windows 資格情報を使用して行われます。サービスは Windows サービス プロセスでホストされます。現在、最適な実装方法を模索中です 認可 サービス操作ごとに。
たとえば、次の方法を考えてみましょう。
public EntityInfo GetEntityInfo(string entityId);
ご存知のとおり、WCF には、呼び出し元/クライアントによって渡されたセキュリティ資格情報を取得できる OperationContext オブジェクトがあります。今、認証 メソッドの最初の行が呼び出されるまでにすでに終了しているはずです。しかし、決定が入力データ自体に依存する場合、承認をどのように実装すればよいでしょうか?たとえば、上記の場合、「管理者」ユーザー (権限などがデータベースに保存されている) はエンティティ情報の取得を許可され、他のユーザーは許可されるべきではありません。認可チェックをどこに置くか?
次のようにメソッドの最初の行に置くとします。
CheckAccessPermission(PermissionType.GetEntity, user, entityId) //user is pulled from the current OperationContext
ここで、いくつかの質問があります。
認可チェックの前に、または認可チェックの内側で、entityId を検証しますか (たとえば、null/空の値をチェックするなど)。言い換えれば、すべてのメソッドに認可チェックを含める必要がある場合、それは良いパターンでしょうか?引数の検証と承認のどちらを最初に行う必要がありますか?
このように承認チェックがあちこちにあり、単体テストに OperationContext がない場合、WCF サービスを単体テストするにはどうすればよいでしょうか。(WCF セットアップを行わずに、このサービス クラスの実装を直接テストしようとしていると仮定します)。
何かアイデアはありますか?
解決
質問 1 については、必ず最初に承認を行ってください。最も厳重なセキュリティを維持するために、承認の前にコードを (制御範囲内で) 実行してはなりません。上のポールの例は素晴らしいです。
質問 2 については、具体的なサービス実装をサブクラス化することでこれに対処できます。上で述べたように、真のビジネス ロジック実装を抽象「CheckPermissions」メソッドを持つ抽象クラスにします。次に、2 つのサブクラスを作成します。1 つは WCF 用で、もう 1 つは true (または単体テストで実行したいもの) を返す (デプロイされていない DLL 内で非常に分離された) ものです。
例 (ただし、これらを同じファイルや DLL 内に含めるべきではないことに注意してください):
public abstract class MyServiceImpl
{
public void MyMethod(string entityId)
{
CheckPermissions(entityId);
//move along...
}
protected abstract bool CheckPermissions(string entityId);
}
public class MyServiceUnitTest
{
private bool CheckPermissions(string entityId)
{
return true;
}
}
public class MyServiceMyAuth
{
private bool CheckPermissions(string entityId)
{
//do some custom authentication
return true;
}
}
次に、WCF デプロイメントでクラス "MyServiceMyAuth" を使用し、他のクラスに対して単体テストを実行します。
他のヒント
質問 1 については、最初に認証を実行するのが最善です。こうすることで、検証エラー メッセージが権限のないユーザーに漏洩することを防ぎます。
ところで、独自の認証方法 (CheckAccessPermission がそれに相当すると思います) を使用する代わりに、ASP.NET ロール プロバイダーに対する WCF のすぐに使用できるサポートを利用できる場合があります。これが完了したら、OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.IsInRole() を介して承認を実行します。PrimaryIdentity は IPrincipal です。
質問 #2 については、Dependency Injection を使用してこれを行い、サービス実装を次のように設定します。
class MyService : IMyService
{
public MyService() : this(new UserAuthorization()) { }
public MyService(IAuthorization auth) { _auth = auth; }
private IAuthorization _auth;
public EntityInfo GetEntityInfo(string entityId)
{
_auth.CheckAccessPermission(PermissionType.GetEntity,
user, entityId);
//Get the entity info
}
}
IAuthorization はユーザーが定義するインターフェイスであることに注意してください。
サービス タイプを直接 (つまり、WCF ホスティング フレームワーク内で実行せずに) テストするので、すべての呼び出しを許可するダミーの IAuthorization タイプを使用するようにサービスを設定するだけです。ただし、さらに優れたテストは、IAuthorization をモックし、それがいつ、予期されるパラメーターを使用して呼び出されるかをテストすることです。これにより、メソッド自体とともに、認証メソッドへの呼び出しが有効であることをテストできます。
承認をそれ自体のタイプに分離すると、それが正しいかどうかを単独でより簡単にテストすることもできます。私の (限定的ではありますが) 経験では、DI 「パターン」を使用すると、関心事の分離が大幅に向上し、型のテストが容易になるだけでなく、インターフェイスがよりクリーンになります (これには明らかに議論の余地があります)。
私の好みのモックフレームワークは次のとおりです ライノモック これは無料で、非常に優れた流暢なインターフェイスを備えていますが、他にもたくさんあります。DI についてさらに詳しく知りたい場合は、優れた入門書と .Net フレームワークをいくつか紹介します。
- マーティン・ファウラーがDIについて語る
- ジェレミー・ミラーがDIについて語る
- Scott Hanselman の DI コンテナのリスト
- 私の個人的なお気に入りの DI コンテナ: キャッスル プロジェクト ウィンザー コンテナ