Dynamics CRM 2011 : カスタム ASPX ページとのシングルサインオン

みなさん、こんにちは。

今日は開発者向けの内容として、カスタム ASPX ページからの
シングルサインオンのサンプル解説をお届けします。

概要

Microsoft Dynamics CRM 2011 では ISV フォルダ配下のカスタム
ASPX ページをサポートしないため、カスタム ASPX ページは別途
カスタム Web サイトに配置する必要があります。

Windows 統合認証を利用する場合問題ありませんが、クレーム
認証を利用する場合、Microsoft Dynamics CRM 2011 とカスタム
Web サイト間のシングルサインオンを行うためには工夫が必要です。

チュートリアルのコード解説

SDK には上記シナリオをカバーするチュートリアルが提供されて
いますが、今回はその詳細を解説します。チュートリアルにある
手順はすべて完了しており、コードも動作していることとします。

Default.aspx.cs ファイル

チュートリアルで提供されるサンプルのトップページにアクセスすると
Default.aspx.cs ファイルの Page_Load が呼ばれます。このコード
では一番初めに以下のコードでクレームの取得を試みます。

IClaimsIdentity claimsIdentity = ((IClaimsPrincipal)(Thread.CurrentPrincipal)).Identities[0];

この時点でアプリケーションは設定した AD FS 2.0 にクライアントを
リダイレクトします。クライアントは AD FS 2.0 に対して認証を終えた後
アプリケーションにクレームを提供します。

次に LINQ クエリを利用してクレームより UPN を取得します。

string upn = (from c in claimsIdentity.Claims
                                where c.ClaimType == System.IdentityModel.Claims.ClaimTypes.Upn
                                select c.Value).FirstOrDefault();

その次のコードで Microsoft Dynamics CRM  2011 の組織サービス
を作成するために、CrmConnect クラスをインスタンス化しています。
ここで重要なポイントは画面にアクセスしているユーザーの権限を
利用してサービスを作成しているのではなく、コードに埋め込まれた
ユーザー情報を利用してサービスを作成している点です。必ず
利用したい組織の IFD アドレスを利用してください。

CrmConnect crmConnect = new CrmConnect(
                new Uri("<IFD 組織サービス URL>"),
                new Uri("<IFD 探索サービス URL>"),             
                "<CRM ユーザー名>",
                "<パスワード>");

最後に DoCrmStuff メソッドを呼び出し、引数に UPN を渡します。

CrmInfo loggedInUser = crmConnect.DoCrmStuff(upn);

CRMConnect.cs ファイル

コンストラクタ部分で組織サービスを作成しています。ここで利用
される認証情報は、コードに埋め込まれたユーザー情報です。

DoCrmStuff メソッド

一番初めに WhoAmIRequest を実行しています。これは画面に
アクセスしているユーザーを確認しているわけではなく、組織サービス
作成用に埋め込まれたユーザーの情報を取得しています。
また取得している内容はユーザーの ID ではなく、組織 ID です。

Guid orgId = ((WhoAmIResponse)_orgServiceProxy.Execute(new WhoAmIRequest())).OrganizationId;

次のコードで、画面にアクセスしているユーザーの情報を、UPN を
利用して取得しています。最後に取得した userId が、現在画面に
アクセスしているユーザーの systemuserId です。

RetrieveUserIdByExternalIdRequest request = new RetrieveUserIdByExternalIdRequest();
request.ExternalId = "C:" + upn;
request.OrganizationId = orgId;

RetrieveUserIdByExternalIdResponse response = (RetrieveUserIdByExternalIdResponse)_discServiceProxy.Execute(request);
Guid userId = response.UserId;

次のコードでは、取得した ID を利用して、画面にアクセスしている
ユーザーの部署 ID を取得します。これはサンプルのため全列を
取得していますが、実際には必要最小限の列だけ取得してください。
この部署 ID は画面に結果を出すためだけに利用されます。

Entity su = _orgServiceProxy.Retrieve("systemuser", userId, new ColumnSet(true));
EntityReference bizRef = (EntityReference)su["businessunitid"];
Guid bizId = bizRef.Id;

次のコードでは組織サービスの CallerId を設定することで偽装を
行っています。これによりハードコードされた資格情報ではなく、
画面にアクセスしているユーザーの資格情報で組織サービスを
実行しています。

// 取引先企業レコードクラスの生成
Entity account = new Entity();
account.LogicalName = "account";
account["name"] = "On Behalf " + DateTime.Now.ToLongTimeString();

// 現在の CallerId の保存。ハードコードされた資格情報です。
Guid originalCallerId = _orgServiceProxy.CallerId;
// 画面にアクセスしているユーザーの ID を CallerId に設定。
_orgServiceProxy.CallerId = userId;

// 取引先企業レコードの作成
Guid accountId;
try
{
    accountId = _orgServiceProxy.Create(account);
}
finally
{
    // CallerId を元に戻す
    _orgServiceProxy.CallerId = originalCallerId;
}

まとめ

シングルサインオンを考えた場合、現在アクセスしているユーザーの
認証情報をどのように渡すかを考えがちですが、このサンプルでは
ユーザーの認証情報を利用せずに組織サービスを生成し、呼び出す
箇所で偽装を利用することにより、目的を達成しています。

ただし、先に Microsoft Dynamics CRM 2011 で認証されていても
カスタム ASPX ページにアクセスした際 AD FS 2.0 にリダイレクト
される点が重要です。ここで同じ AD FS 2.0 を利用しておくことで
ユーザーが認証情報を再度入力する必要がなくなります。

‐ Dynamics CRM サポート 中村 憲一郎