2 人のうち 2 人が役に立つと評価しました    - このトピックを評価する

Web アプリケーションのためのシングル サインオン エンタープライズ セキュリティ

Paul D. Sheriff
PDSA, Inc.

February 2004
日本語版最終更新日 2004 年 8 月 6 日

適用対象:
Microsoft® ASP.NET

要約: 複数の Web アプリケーションに対してシングル サインオンを有効にする技術を紹介します。この記事にはサンプル コードも含まれており、このコードを基にして、シングル サインオンを使用した堅牢なエンタープライズ セキュリティ システムを作成できるようになっています。サンプル プログラム ファイル内では実際のコメント行は英語で書かれていますが、この記事内では説明目的で日本語で書かれています。

この記事のコード サンプルをダウンロードする (ダウンロードする前に、「サンプルをインストールする」を参照してください)

目次

はじめに
シングル サインオン ソリューションの概要
クラスとページ
テーブル
AppLauncher Web アプリケーション
Apps クラス
Web アプリケーション内のトークンを取得する
シングル サインオン システムを強化する
サンプルをインストールする
まとめ
関連記事

はじめに

ほとんどの組織には、業務をサポートする数多くの Web アプリケーションが存在します。これらの Web アプリケーションのほとんどは、安全であることが要求されます。ユーザーが任意の Web アプリケーションに自由にアクセスできるようなことがあってはなりません。たとえば、エクゼクティブ サマリー意志決定支援システムには特定のユーザーのみがアクセスできるように制限し、他のユーザーは顧客情報システムのみにアクセスできるようにする必要があります。これらのシステムのユーザーの中には、Microsoft Windows® ドメインに属していない外部のユーザーが含まれている場合もあります。このようなアクセスを管理するには、システムへのログオンを各ユーザーに強制するセキュリティ メカニズムを使用します。ここで問題となるのは、ユーザーが絶えず各種のシステムにログオンしなければならなくなることです。Web アプリケーションが 5 つしかない組織でも、各アプリケーションへのアクセスが許可されているユーザーが、絶えずログオンとログオフを繰り返さなければならないのでは非常に不便です。これよりも望ましい方法があるはずです。

この記事では、エンタープライズ全体でシングル サインオン ソリューションを採用するための 1 つの方法を学習します。組織の内部ユーザーは、Windows 認証を使用してアプリケーションにアクセスできるようになりますが、外部ユーザーにはログオンが強制されます (図 1 参照)。

この記事で扱うトピック

Web アプリケーションのためのシングル サインオン

  • シングル サインオン トークンを生成する
  • ユーザーをアプリケーションにマッピングする
  • フォームベースの認証
  • 統合 Windows 認証

シングル サインオン ソリューションの概要

図 1 を見ると、企業のイントラネット上にホストされている Web サイトにログオンするために、内部ユーザーと外部ユーザーの両方が従う必要のある一連の手順がわかります。15,000 メートルの範囲に及ぶこのシステムには、Windows の認証を受けた Web サイト、Active Directory、データベース サーバー、1 つ以上のフォームベースの認証 Web サイトという 4 つのコンポーネントが存在します。


ms972971.singlesignon_01(ja-jp,MSDN.10).gif

図 1. 内部ユーザーおよび外部ユーザー向けシングル サインオン ソリューションの例


イントラネット ソリューション

まず図 1 の左上では、内部ユーザーがブラウザを起動し、特定の Web サイトにアクセスしようとします (手順 1)。この Web サイトでは、Active Directory を使用して、このユーザーを Windows の資格情報と照合します (手順 2)。ユーザーが有効な Windows ユーザーである場合は、このサイトへのアクセスが許可されます。認証が行われると、ユーザーの識別情報が取得され、データベース テーブルが呼び出されます (手順 3)。このデータベース テーブルには、指定されているユーザーが実行できるアプリケーションの一覧が含まれています。これらのアプリケーションは DataGrid に表示され、ユーザーによる選択ができるようになります。

実行するアプリケーションをユーザーがクリックすると、1 回限りの一意のトークンが生成されます (手順 4)。このトークンとユーザーの識別情報は、別のデータベース テーブルに格納されます。このトークンは次にクエリ行を経由して、ユーザーが実行する Web アプリケーション内にある特殊なページに渡されます。この特殊なページでは、このトークンをクエリ行から読み取り、トークンがデータベース テーブルに存在するかどうかを確認します (手順 5)。トークンが存在する場合は、データベースからログイン ID が取得され、このトークンを格納していたレコードが削除されます。これにより、別のユーザーがこのトークンを再利用し、ログイン ID を Web アプリケーションに戻すような行為を防ぐことができます。

以上でこの Web アプリケーション内にユーザーの識別情報がもたらされたため、次に ASP.NET フォーム認証チケットを生成する必要があります。イントラネット内のすべての Web アプリケーションでは、フォームベースの認証が行われるためです。このチケットは、このサイトの安全なページすべてにアクセスするユーザーが使用するものです。

エクストラネット ソリューション

Web アプリケーションへのアクセスを望む外部ユーザー (ドメイン外のユーザー) には、内部ユーザー用とは別の開始ページが表示されます (図 2 参照)。内部ユーザーと外部ユーザーの両方がアクセスする Web アプリケーションは、フォームベースの認証によって保護されています。外部ユーザーがこの Web アプリケーション内のページにアクセスしようとすると、そのユーザーは自動的にログイン ページにリダイレクトされます。このログイン ページは、内部ユーザーが認証を受けるページとは別のものです。ユーザーは自分の資格情報を入力する必要があります。それによって、同一のデータベースが呼び出され、このユーザーがこのアプリケーションの有効なユーザーであるかどうかが判断されます。有効なユーザーである場合は、このユーザーのセッションに対して通常のフォームベース認証 Cookie が生成されます。

クラスとページ

エンタープライズ セキュリティ システムをサポートするには、いくつかのクラスと Web ページを作成する必要があります。図 2 は、このシステム用に作成する必要のある各クラスと Web ページを示しています。この図が示す各クラスについては、この記事で後述します。この図の後で、各クラスの一覧と、システム内でのクラスまたは Web ページの主な機能について説明します。


ms972971.singlesignon_02(ja-jp,MSDN.10).gif

図 2. シングル サインオン ソリューションの開発に必要な少数のクラスとページ


Apps クラス

このクラスは、指定されているユーザーが利用できるアプリケーションの一覧を取得する役割を果たします。また、このクラスでは新しいトークンの生成、指定されているトークンで与えられたユーザーの識別情報の取得、およびトークンの削除も行います。

表 1. Apps クラスのメソッド


メソッド名説明
GetAppsByLoginIDユーザーのドメイン ログイン ID が与えられると、このユーザーに関連付けられているアプリケーションを検索し、アプリケーションの DataSet を返します。
CreateLoginToken新しいログイン トークンを作成し、このトークンを返します。
GenerateToken新しいトークンを生成します。このバージョンでは、単純な GUID がトークンとして使用されます。
VerifyLoginTokenトークンが与えられると、そのトークンがデータベースに存在するかどうかを確認します。また、AppToken クラスのインスタンスを作成し、適切な情報を挿入し、この新しいオブジェクトを返します。
DeleteTokenトークンをテーブルから削除します。

AppToken クラス

このクラスには、Apps クラスの VerifyLoginToken メソッドからのトークン情報を返すのに必要な 4 つのプロパティが含まれています。表 2 では、このクラスの 4 つのプロパティについて説明しています。

表 2. AppToken クラスのプロパティ


プロパティ説明
LoginIDユーザーのログイン ID を表す文字列値。
AppNameこの AppToken レコードが関連付けられているアプリケーション名を表す文字列値。
LoginKeyesUsers テーブル内のユーザーの主キーを示す整数値。
AppKeyesApps テーブル内のアプリケーションの主キーを示す整数値。

AppUserRoles クラス

このクラスは、アプリケーションにログインしようとしているユーザーに関する情報を取得する役割を果たします。あるメソッドは、ログイン ID とアプリケーション キーが与えられたときに、そのログインが有効であるかどうかを確認します。別のメソッドは、任意のユーザーの一連のロールを返します。さらに別のメソッドは、ログイン ID とアプリケーション キーが与えられたときに、esUser テーブルの主キーを返します。

AppLauncher アプリケーション内の Default.aspx

この Web ページ クラスは、IIS への認証を行った Windows ユーザーを取得し、そのユーザーによるアクセスが許可されているアプリケーションの一覧を返します。この一覧は、DataGrid (図 3) に表示されます。ユーザーは特定のアプリケーションをクリックできるようになっています。ユーザーがアプリケーションをクリックすると、このページによって新しいトークンが生成され、トークンとユーザー ID が esAppToken テーブルに格納され、そのアプリケーションが呼び出されて、トークンが Web アプリケーション内の AppLogin.aspx ページに渡されます。


ms972971.singlesignon_03(ja-jp,MSDN.10).gif

図 3. ログインしたユーザーによる実行が許可されているアプリケーションの一覧を表示する Application Launcher


各 Web サイト内の AppLogin.aspx

この Web ページ クラスは、Application Launcher からのみ呼び出されます。他のアプリケーションがこのページを呼び出そうとした場合、ユーザーは Web サイトの Default.aspx ページにリダイレクトされます。各 Web アプリケーションではフォームベースの認証を使用しているため、このリダイレクトによって ASP.NET はサイト内の Login.aspx ページにユーザーをリダイレクトするようになります。

このページが Application Launcher サイトからのトークンを使用して呼び出された場合、このページによって Apps クラスのメソッドが呼び出され、このトークンが有効であるかどうかが確認されます。トークンが有効である場合は、AppToken オブジェクトが Apps クラスから返されるため、このページではこのオブジェクト内の情報を使用してフォームベースによる認証がなされたユーザーを作成できるようになります。

各 Web サイト内の Login.aspx

これは、通常のログイン用 Web ページで、ユーザーの資格情報を要求し、これらの資格情報をデータベースと照合して、ユーザーが有効なユーザーであるかどうかを確認します。有効なユーザーの場合には、認証チケットを作成して、ユーザーがサイトにアクセスしたときに要求したページにユーザーをリダイレクトします。

各 Web サイト内の Default.aspx

これは、各 Web サイトに存在するメインのジャンプ先ページです。このページと、サイト内のその他のページには、ユーザーが AppLogin またはログイン用 Web ページを経由して認証されている場合にのみアクセスできます。

テーブル

このシングル サインオン エンタープライズ セキュリティ システムをサポートするには、データベース内にいくつかのテーブルを作成する必要があります。この記事で紹介するテーブルには多くのフィールドはありませんが、テーブルの機能は理解していただけるはずです。図 4 は、作成する必要のあるデータベース内の各テーブル間の関係を示しています。図 4 の後には、これらの各テーブルの一覧と、各テーブルがこのソリューションでどのように使用されるかについての説明が含まれています。


ms972971.singlesignon_04(ja-jp,MSDN.10).gif

図 4. ロールを使用したシングル サインオン システムを実装するために必要な少数の単純なテーブル


esApps

このテーブルには、エンタープライズ内のすべての Web アプリケーションの一覧が含まれています。アプリケーション名の他に、アプリケーションの長い説明と、このアプリケーションが存在する URL もこのテーブルに格納されています。URL は完全な URL で、最後に AppLogin.aspx ページを必ず含んでいます。Application Launcher の default.aspx ページによって、Web アプリケーションへのリダイレクト前に "Token=<GeneratedToken>" が URL に追加されます。


esApps テーブルのサンプル データ

図 5. esApps テーブルのサンプル データ

esUsers

このテーブルには、アプリケーションを使用できるすべてのユーザーが一覧表示されます。内部ユーザーの場合は、ユーザーのドメイン ログイン ID を複製する必要があります。外部ユーザーに対しては、パスワード フィールドを追加することをお勧めします。


ms972971.singlesignon_06(ja-jp,MSDN.10).gif

図 6. esUsers テーブルのサンプル データ


esAppsUsers

このテーブルは、esUsers テーブル内のユーザーを、これらのユーザーが実行できる esApps テーブル内のアプリケーションに関連付けます。このテーブル内の唯一のデータは、esUsers テーブルと esApps テーブルに対する外部キーです。

esAppRoles

このテーブルには、各アプリケーションの一連のロールが含まれています。たとえば、1 つのアプリケーションには "Admin" と "User" のロールが含まれており、別のアプリケーションには "User" と "Supervisor" のロールが含まれています。


esAppRoles テーブルのサンプル データ

図 7. esAppRoles テーブルのサンプル データ

esAppUsersRoles

これは、アプリケーション内の各ユーザーのそれぞれのロールを示す一覧です。たとえば、ユーザー "鈴木" 氏は "HR" アプリケーションでは "Supervisor" になりますが、"Payroll" アプリケーションでは "Admin" および "User" になる場合があります。

esAppToken

esAppToken には、ログイン ポータル アプリケーションで生成され、個別の Web アプリケーションに渡されるトークンが含まれています。通常これらのレコードは、約 2 秒以下の短い間だけ存在します。これは、呼び出された Web アプリケーションが、このテーブルから情報を収集するとすぐにトークンを削除するためです。このため、別のユーザーによってトークンが再利用されることがありません。


esAppToken テーブルのサンプル データ

図 8. esAppToken テーブルのサンプル データ

AppLauncher Web アプリケーション

Application Launcher ソリューション (図 9) は、Windows 認証されたサイトとクラス ライブラリ プロジェクトで構成されています。Windows 認証されたこのサイトでは、ユーザーのドメイン ID をスレッドから直接読み取り、その ID を使用してユーザーのテーブル内のユーザーを検索します。ここで、ユーザーが実行できるアプリケーションを表示する Web ページを見てみましょう。


ms972971.singlesignon_09(ja-jp,MSDN.10).gif

図 9. AppLauncherxx プロジェクト、および AppLauncherDataxx プロジェクトへの参照を含む AppLauncherxx ソリューション


AppLauncherxx 内の Default.aspx

Application Launcher Web サイトに必要な Web ページは、default.aspx の 1 つだけです。この Web ページでは、ユーザーの Windows ログイン ID をページ上の User オブジェクトから読み取り、このユーザー用にデータベース内で定義されているアプリケーションの一覧を読み込みます。以下は、default.aspx ページが読み込まれたときに呼び出される Page_Load イベント プロシージャのコードです。


// C#
private void Page_Load(object sender, System.EventArgs e)
{
// ドメイン プリフィックスなしの
// ユーザー名を表示する
lblLogin.Text = "Applications Available for: " + 
Apps.LoginIDNoDomain(User.Identity.Name);

AppLoad();
}



' VB.NET
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
' ドメイン プリフィックスなしの
' ユーザー名を表示する
lblLogin.Text = "Applications Available for: " & _
Apps.LoginIDNoDomain(User.Identity.Name)

AppLoad()
End Sub


Apps クラス内の LoginIDNoDomain 静的メソッドは、ドメイン プリフィックスを取り除くために呼び出されます。"Yamaguchi" というログイン ID のユーザーが "PDSA" というドメインで認証された場合、User.Identity.Name プロパティによって "PDSA\Yamaguchi" が返されます。このメソッドでは、文字列 "Yamaguchi" のみが返されます。

このユーザーが実行できるアプリケーションの読み込み

AppLoad メソッドでは Apps クラスのインスタンスを使用して、このユーザーによる実行が許可されているアプリケーションの DataSet を取得します。Apps クラス内の GetAppsByLoginID メソッドについては、この記事で後述します。


// C#
private void AppLoad()
{
Apps app = new Apps();

try
  {
// このユーザー用アプリケーションを読み込む
grdApps.DataSource =
app.GetAppsByLoginID(User.Identity.Name);
grdApps.DataBind();

  }
catch (Exception ex)
  {
lblMessage.Text = ex.Message;
  }
}



' VB.NET
Private Sub AppLoad()
Dim app As New Apps

Try
' このユーザー用アプリケーションを読み込む
grdApps.DataSource = app.GetAppsByLoginID(User.Identity.Name)
grdApps.DataBind()

Catch ex As Exception
lblMessage.Text = ex.Message
End Try

End Sub


LinkButton コントロール

指定したユーザーのアプリケーション一覧が default.aspx ページの DataGrid コントロールに表示されると (図 3 参照)、ユーザーはこれらのアプリケーションのいずれかを選択できるようになります。ユーザーがクリックするハイパーリンクを表示するために DataGrid で使用されるコントロールは LinkButton です。この LinkButton は Web ページ上で次のように定義されます。


<asp:LinkButton id=lnkApp runat="server" 
AppID='<%# DataBinder.Eval(Container.DataItem, "iAppID") %>' 
UserID='<%# DataBinder.Eval(Container.DataItem, "iUserID") %>' 
CommandArgument='<%# DataBinder.Eval(Container.DataItem, "sURL") %>' 
Text='<%# DataBinder.Eval(Container.DataItem, "sAppName") %>'>
</asp:LinkButton>


上のコードに示すように、esApps テーブルからの主キー (iAppID) が挿入された属性と、esUsers テーブルからの主キー (iUserID) が挿入された属性の 2 つが追加されています。これらの属性と CommandArgument 内の URL によって、データベースに格納するのに十分なユーザー プロファイル情報がもたらされます。これらの追加属性は、ItemCommand イベント プロシージャで取得されます。このイベント プロシージャについては次に説明します。

ヒント: サーバー コントロールには任意の属性を追加できます。ASP.NET ではこれらの属性は無視されますが、サーバー コントロールの Attributes プロパティを使用すると、これらの値を取得できます。

ItemCommand イベント

ユーザーが DataGrid 内の LinkButton コントロールをクリックすると、ItemCommand イベント プロシージャが呼び出されます。このメソッドで、Apps クラスの新しいインスタンスを作成し、LinkButton コントロールを取得する必要があります。それによって、属性を取得して、Apps クラス内の CreateLoginToken メソッドを呼び出し、このデータをデータベース内の esAppToken テーブルに格納できるようになります。最後に、このメソッドを使用してトークンを取得すると、LinkButton の CommandArgument プロパティ内の URL がこのトークンと連結されます。次に、Response.Redirect が起動し、トークンによって渡された Web アプリケーションが呼び出されます。


// C#
private void grdApps_ItemCommand(object source, 
System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
Apps app = new Apps();
bool redirect = false;
string token = String.Empty;
LinkButton lb;

try
  {
lb = (LinkButton) e.Item.Cells[0].Controls[1];
    
// このユーザー/app 用トークンを作成する
token = app.CreateLoginToken(
lb.Text, 
User.Identity.Name, 
Convert.ToInt32(lb.Attributes["UserID"]), 
Convert.ToInt32(lb.Attributes["AppID"]));

redirect = true;
  }
catch (Exception ex)
  {
redirect = false;
lblMessage.Text = ex.Message;
  }

if (redirect)
  {
// 生成されたトークンの中で渡された
// Web アプリケーションにリダイレクトする

Response.Redirect(e.CommandArgument.ToString() +
"?Token=" + token, false);
  }
}



' VB.NET
Private Sub grdApps_ItemCommand(ByVal source As Object, _
ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
Handles grdApps.ItemCommand
Dim app As New Apps
Dim boolRedirect As Boolean
Dim token As String
Dim lb As LinkButton

Try
lb = DirectCast(e.Item.Cells(0).Controls(1), LinkButton)

' このユーザー/app 用トークンを作成する
token = app.CreateLoginToken(lb.Text, _
User.Identity.Name, _
Convert.ToInt32(lb.Attributes("UserID")), _
Convert.ToInt32(lb.Attributes("AppID")))

boolRedirect = True

Catch ex As Exception
boolRedirect = False
lblMessage.Text = ex.Message

End Try

If boolRedirect Then
' 生成されたトークンの中で渡された
' Web アプリケーションにリダイレクトする
Response.Redirect(e.CommandArgument.ToString() & _
"?Token=" & token, False)
End If
End Sub


上のコードでは、e.Item 引数から LinkButton コントロールを取得することが示されています。e.Item は、クリックした DataGrid 内の行の参照です。クリックした LinkButton コントロールの特定のインスタンスを取得するには、LinkButton を含む列を参照します。この場合には、これは Cells(0) になります。このセル内で Controls(1) を参照すると、コントロールを取得することができます。このコントロールが (1) の位置にある理由は、LinkButton をセルに配置するために DataGrid で使用される ItemTemplate が要素ゼロ (0) と見なされるためです。

LinkButton を取得したら、次に Attributes プロパティを使用して、LinkButton の設定時に格納した UserID と AppID を取得できるようになります。Attributes プロパティは、追加属性の集まりです。元のコントロールの定義に含まれていないこれらの属性を、サーバー コントロールに追加します。

Web.Config の変更

Web アプリケーション内のユーザーを認証するには、Web.Config ファイル内の <authentication> 要素を "Windows" に設定する必要があります。さらに、Web.config 内の <authorization> 要素で匿名ユーザーを拒否する必要があります。


<authorization>
<deny users="?" />
</authorization> 


これらの 2 つの要素を設定すると、サーバーはブラウザからユーザーの Windows 資格情報を取得するよう強制されます。当然のことですが、これはユーザーがログインしているドメイン内で Internet Explorer を使用している場合にのみ有効です。

ここで行う必要のある最後のエントリは、データベース内のテーブルにアクセスするための接続文字列を格納することです。この記事のサンプルでは、Web.config の <appSettings> セクションを使用して接続文字列を格納しています。


<appSettings>
<add key="eSecurityConnectString" 
value="server=(local);Database=eSecurity;uid=myUserID;pwd=myPassword" />
</appSettings>


この記事では、SQL Server を使用して、eSecurity と呼ばれるデータベースと、以前に示したテーブルすべてを作成しています。この記事のサンプル コードには、SQL Server データベース内にテーブルを作成するために実行できる SQL スクリプトが含まれています。別のデータベース システムを使用している場合は、そのデータベースに合わせてこれらのスクリプトを変更する必要があります。

Apps クラス

ここで、このエンタープライズ セキュリティ システムのほとんどの作業を実行する Apps クラスを見てみましょう。AppLauncherData アセンブリ内の 3 つのクラスはそれぞれユーザーのアプリケーションの読み込み、ユーザーのロールの読み込み、およびセキュリティ トークンの処理を実行します。データベースとの対話機能とトークンの操作機能は、ユーザー インターフェイス層とは別に配置することをお勧めします。そうすると、ユーザー インターフェイスを変更せずに、トークンの作成方法やデータベースとの対話方法を変更できるようになります。

Apps クラスは、トークンの操作とユーザーのアプリケーションの読み込みを行います。このクラスの定義を見てみましょう。


// C#
public class Apps
{
string mConnectString;

public Apps()
  {
mConnectString = ConfigurationSettings.
AppSettings["eSecurityConnectString"];
  }

...
}



' VB.NET
Public Class Apps
Private mConnectString As String

Public Sub New()
mConnectString = ConfigurationSettings. _
AppSettings("eSecurityConnectString")
End Sub

...
End Class


コードが示すように、このクラスが最初に行うことは、接続文字列をメンバ変数に読み込むことです。この接続文字列は、Web.config ファイルから取得します。

GetAppsByLoginID メソッド

特定のユーザーのアプリケーションを読み込むには、そのユーザーのログイン ID を GetAppsByLoginID メソッドに渡します。このメソッドは、Join を実行し、該当する情報をすべて取得します。使用する SQL Join では、esApps テーブルと esAppsUsers テーブルから情報を取得する必要があります。さらに、esUsers テーブルにも結合する必要があります。これは、特定のユーザーのアプリケーションを取得する必要があるにもかかわらず、手元にはユーザーのログイン ID 情報しかないためです。このため、esUsers テーブル内のユーザーの主キーを検索して、他のテーブルに結合する必要があります。

注: この記事では概念を示すことが目的であるため、Dynamic SQL を使用しています。実際のエンタープライズ セキュリティ システムでは、すべての SQL 呼び出しにストアド プロシージャを使用することをお勧めします。

// C#
public DataSet GetAppsByLoginID(string loginID)
{
DataSet ds = new DataSet();
SqlCommand cmd;
SqlDataAdapter da;
string sql;

sql = "SELECT esApps.iAppID, esAppsUsers.iUserID, ";
sql += " esApps.sAppName, esApps.sDesc, esApps.sURL ";
sql += " FROM esApps";
sql += " INNER JOIN esAppsUsers ";
sql += " ON esApps.iAppID = esAppsUsers.iAppID ";
sql += " INNER JOIN esUsers ";
sql += " ON esAppsUsers.iUserID = esUsers.iUserID ";
sql += " WHERE sLoginID = @sLoginID ";
sql = String.Format(sql, Apps.LoginIDNoDomain(loginID));

try
  {
cmd = new SqlCommand(sql);
cmd.Parameters.Add(new 
SqlParameter("@sLoginID", SqlDbType.Char));
cmd.Parameters["@sLoginID"].Value = 
Apps.LoginIDNoDomain(loginID);
cmd.Connection = new SqlConnection(mConnectString);

da = new SqlDataAdapter(cmd);

da.Fill(ds);

return ds;
  }
catch (Exception ex)
  {
throw ex;
  }
}



' VB.NET
Public Function GetAppsByLoginID(ByVal LoginID As String) _
As DataSet
Dim ds As New DataSet
Dim cmd As SqlCommand
Dim da As SqlDataAdapter
Dim sql As String

sql = "SELECT esApps.iAppID, esAppsUsers.iUserID, "
sql &= " esApps.sAppName, esApps.sDesc, esApps.sURL "
sql &= " FROM esApps"
sql &= " INNER JOIN esAppsUsers "
sql &= " ON esApps.iAppID = esAppsUsers.iAppID "
sql &= " INNER JOIN esUsers "
sql &= " ON esAppsUsers.iUserID = esUsers.iUserID "
sql &= " WHERE sLoginID = @sLoginID "
sql = String.Format(sql, Apps.LoginIDNoDomain(LoginID))

Try
cmd = New SqlCommand(sql)
cmd.Parameters.Add(New _
SqlParameter("@sLoginID", SqlDbType.Char))
cmd.Parameters("@sLoginID").Value = _
Apps.LoginIDNoDomain(LoginID)
cmd.Connection = New SqlConnection(mConnectString)

da = New SqlDataAdapter(cmd)
da.Fill(ds)

Return ds

Catch ex As Exception
Throw ex
End Try
End Function


CreateLoginToken メソッド

ユーザーが DataGrid 内のアプリケーションをクリックした場合は、新しいトークンを作成する必要があります。CreateLoginToken メソッドではこのタスクを実行します。


// C#
public string CreateLoginToken(string appName, 
string loginID, int userID, int appID)
{
SqlCommand cmd = new SqlCommand();
SqlParameter param;
string token;
string sql;

// 新しいトークンを作成する
token = GenerateToken();

sql = "INSERT INTO esAppToken(sToken, sAppName, ";
sql += " sLoginID, iUserID, iAppID, dtCreated) ";
sql += " VALUES(@sToken, @sAppName, @sLoginID, ";
sql += "        @iUserID, @iAppID, @dtCreated) ";

param = new SqlParameter("@sToken", SqlDbType.Char);
param.Value = token;
cmd.Parameters.Add(param);

param = new SqlParameter("@sAppName", SqlDbType.Char);
param.Value = appName;
cmd.Parameters.Add(param);

param = new SqlParameter("@sLoginID", SqlDbType.Char);
param.Value = Apps.LoginIDNoDomain(loginID);
cmd.Parameters.Add(param);

param = new SqlParameter("@iUserID", SqlDbType.Int);
param.Value = userID;
cmd.Parameters.Add(param);

param = new SqlParameter("@iAppID", SqlDbType.Int);
param.Value = appID;
cmd.Parameters.Add(param);

param = new SqlParameter("@dtCreated", SqlDbType.DateTime);
param.Value = DateTime.Now;
cmd.Parameters.Add(param);

try
  {
cmd.CommandType = CommandType.Text;
cmd.CommandText = sql;

cmd.Connection = new SqlConnection(mConnectString);
cmd.Connection.Open();

cmd.ExecuteNonQuery();
  }
catch (Exception ex)
  {
throw ex;
  }
finally
  {
if (cmd.Connection.State != ConnectionState.Closed)
    {
cmd.Connection.Close();
cmd.Connection.Dispose();
    }
  }

return token;
}



' VB.NET
Public Function CreateLoginToken(ByVal AppName As String, _
ByVal LoginID As String, ByVal UserID As Integer, _
ByVal AppID As Integer) As String
Dim cmd As New SqlCommand
Dim param As SqlParameter
Dim token As String
Dim sql As String

' 新しいトークンを作成する
token = GenerateToken()

sql = "INSERT INTO esAppToken(sToken, sAppName, "
sql &= " sLoginID, iUserID, iAppID, dtCreated) "
sql &= " VALUES(@sToken, @sAppName, @sLoginID, "
sql &= " @iUserID, @iAppID, @dtCreated)"
sql = String.Format(sql, token, AppName, _
Apps.LoginIDNoDomain(LoginID), UserID, AppID, _
DateTime.Now.ToString())

param = New SqlParameter("@sToken", SqlDbType.Char)
param.Value = token
cmd.Parameters.Add(param)

param = New SqlParameter("@sAppName", SqlDbType.Char)
param.Value = AppName
cmd.Parameters.Add(param)

param = New SqlParameter("@sLoginID", SqlDbType.Char)
param.Value = Apps.LoginIDNoDomain(LoginID)
cmd.Parameters.Add(param)

param = New SqlParameter("@iUserID", SqlDbType.Int)
param.Value = UserID
cmd.Parameters.Add(param)

param = New SqlParameter("@iAppID", SqlDbType.Int)
param.Value = AppID
cmd.Parameters.Add(param)

param = New SqlParameter("@dtCreated", SqlDbType.DateTime)
param.Value = DateTime.Now
cmd.Parameters.Add(param)

Try
cmd.CommandType = CommandType.Text
cmd.CommandText = sql

cmd.Connection = New SqlConnection(mConnectString)
cmd.Connection.Open()

cmd.ExecuteNonQuery()

Catch ex As Exception
Throw ex

Finally
If cmd.Connection.State <> ConnectionState.Closed Then
cmd.Connection.Close()
cmd.Connection.Dispose()
End If
End Try

Return token
End Function


トークンを作成するには、GenerateToken メソッドを呼び出します。これが CreateLoginToken メソッドとは別になっている理由は、今後生成するトークンの種類を変更できるようにするためです。この記事の最後には、いくつかのアイデアが紹介されています。このメソッドでは Guid クラスを使用して、新しい GUID をトークンとして生成します。


// C#
public string GenerateToken()
{
return System.Guid.NewGuid().ToString();
}



' VB.NET
Public Function GenerateToken() As String
Return System.Guid.NewGuid().ToString()
End Function


Web アプリケーション内のトークンを取得する

Application Launcher からのアプリケーションの起動をテストするには、Launcher から呼び出すことのできるテスト用の Web アプリケーションを作成します (図 10 参照)。テスト用の Web アプリケーションでは、前述の AppLauncherDataxx プロジェクトを使用します。


ms972971.singlesignon_10(ja-jp,MSDN.10).gif

図 10. AppLogin、Login、および Default ページと共に AppLauncherDataxx プロジェクトを使用する各 Web アプリケーション


Web.Config を変更する

シングル サインオン システムと統合する Web アプリケーションを作成するには、まず Web.config ファイルを変更し、<appSettings> セクションを作成して、eSecurity データベースとのインターフェイスを取るための接続文字列を格納する必要があります。外部ユーザーがアクセスしたときにはアプリケーション ID とアプリケーション名も格納する必要があります。これらの場合に備えたトークンは作成していないため、対象のアプリケーションがどれであるかを把握して、ユーザーのロールの作成時に使用する AppToken オブジェクトを読み込むことができるようにします。この方法については、この記事で後述します。


<appSettings>
<add key="eSecurityConnectString"
value="server=(local);Database=
eSecurity;uid=mUserID;pwd=myPassword"></add>
<add key="eSecurityAppID" value="1"></add>
<add key="eSecurityAppName" value="Payroll"></add>
</appSettings>


さらに、Web アプリケーションを設定し、フォームベースの認証を使用できるようにする必要があります。これを行うには、以下に示すように <authentication> 要素を変更します。


<authentication mode="Forms">
<forms name="AppTest" loginUrl="Login.aspx" />
</authentication>


最後に、<authorization> 要素を変更し、匿名ユーザーを拒否する必要があります。


<authorization>
<deny users="?" />
</authorization> 


AppLogin.aspx ページ

Application Launcher から呼び出す各アプリケーションは、AppLogin ページを呼び出して、このページに生成済みのトークンを渡す必要があります。AppLogin ページでは、このトークンが正しいかどうかを確認し、該当する情報を esAppToken テーブルから取得し、次に esAppToken 内のレコードを削除して、それが再利用されないようにします。


// C#
private void Page_Load(object sender, System.EventArgs e)
{
VerifyToken();
}

private void VerifyToken()
{
Apps app = new Apps();
AppToken al;

try
  {
al = app.VerifyLoginToken(
Request.QueryString["Token"].ToString());

if(al.LoginID.Trim() == "")
    {
// 有効なログインではない場合
// 既定のページにリダイレクトする
// これによりログイン ページが表示される
Response.Redirect("default.aspx");
    }
else
    {
// Forms Authentication Cookie を作成する
// Forms Authentication 変数を設定する
FormsAuthentication.Initialize();
FormsAuthentication.SetAuthCookie(
al.LoginID.ToString(), false);

// アプリケーション トークン オブジェクトを設定する
Application["AppToken"] = al;

// Default ページにリダイレクトする
Response.Redirect("default.aspx");
    }
  }
catch
  {
// Default ページを経由してログイン ページにリダイレクトする
Response.Redirect("default.aspx");
  }
}



' VB.NET
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
VerifyToken()
End Sub

Private Sub VerifyToken()
Dim app As New Apps
Dim al As AppToken

Try
al = app.VerifyLoginToken( _
Request.QueryString("Token").ToString())

If al.LoginID.Trim() = "" Then
' 有効なログインではない場合
' 既定のページにリダイレクトする
' これによりログイン ページが表示される
Response.Redirect("default.aspx")
Else
' Forms Authentication Cookie を作成する
' Forms Authentication 変数を設定する
FormsAuthentication.Initialize()
FormsAuthentication.SetAuthCookie( _
al.LoginID.ToString(), False)

' アプリケーション トークン オブジェクトを設定する
Application("AppToken") = al

' Default ページにリダイレクトする
Response.Redirect("default.aspx")
End If

Catch
' Default ページを経由してログイン ページにリダイレクトする
Response.Redirect("default.aspx")
End Try
End Sub


VerifyLogin メソッドでは、まずトークンが有効であるかどうかを確認します。これは、Apps クラス内の VerifyLoginToken メソッドを呼び出すことで行います。このメソッドによって、AppToken クラスのインスタンスが返されます。このクラスの LoginID プロパティに値が入力されている場合は、それが有効なユーザーであることがわかります。入力されていない場合、トークンは有効ではありません。トークンが有効ではなかった場合、このメソッドによってユーザーは Web サイトの default.aspx ページにリダイレクトされます。当然のことながら、フォームベースの認証がオンになっている場合、これによってユーザーは Login.aspx ページにリダイレクトされ、ログオンするよう求められます。

フォームの認証 Cookie は、FormsAuthentication.Initialize メソッドと FormsAuthentication.SetAuthCookie メソッドを呼び出すことで送信されます。これにより、メモリ内の Cookie がブラウザに送信されます。この Cookie は、ユーザーがサイトを再訪問するたびに、ASP.NET ランタイムによってチェックされます。

VerifyLoginToken メソッド

このメソッドは、クエリ行を経由して渡された生成済みのトークンを受け取り、このトークンが有効であるかどうかを確認します。トークンの検索には esAppToken テーブルが使用されます。トークンがテーブルに存在する場合は、テーブルの値すべてが AppToken オブジェクトの個別のプロパティに配置されます。この AppToken オブジェクトがこのメソッドから返されます。


// C#
public AppToken VerifyLoginToken(string Token)
{
AppToken al = new AppToken();
DataSet ds = new DataSet();
SqlCommand cmd;
DataRow dr;
SqlDataAdapter da;
string sql;

sql = "SELECT iAppTokenID, sAppName, sLoginID, ";
sql += " iAppID, iUserID ";
sql += " FROM esAppToken";
sql += " WHERE sToken = @sToken ";

try
  {
cmd = new SqlCommand(sql);
cmd.Parameters.Add(new 
SqlParameter("@sToken", SqlDbType.Char));
cmd.Parameters["@sToken"].Value = Token;
cmd.Connection = new SqlConnection(mConnectString);

da = new SqlDataAdapter(cmd);
da.Fill(ds);

if (ds.Tables[0].Rows.Count > 0)
    {
dr = ds.Tables[0].Rows[0];

al.LoginID = dr["sLoginID"].ToString();
al.AppName = dr["sAppName"].ToString();
al.AppKey = Convert.ToInt32(dr["iAppID"]);
al.LoginKey = Convert.ToInt32(dr["iUserID"]);

DeleteToken(Convert.ToInt32(dr["iAppTokenID"]));
    }
  }
catch (Exception ex)
  {
throw ex;
  }

return al;
}



' VB.NET
Public Function VerifyLoginToken(ByVal Token As String) As AppToken
Dim al As New AppToken
Dim ds As New DataSet
Dim cmd As SqlCommand
Dim dr As DataRow
Dim da As SqlDataAdapter
Dim sql As String

sql = "SELECT iAppTokenID, sAppName, sLoginID, "
sql &= " iAppID, iUserID "
sql &= " FROM esAppToken"
sql &= " WHERE sToken = @sToken "

Try
cmd = New SqlCommand(sql)
cmd.Parameters.Add(New _
SqlParameter("@sToken", SqlDbType.Char))
cmd.Parameters("@sToken").Value = Token
cmd.Connection = New SqlConnection(mConnectString)

da = New SqlDataAdapter(cmd)

da.Fill(ds)

If ds.Tables(0).Rows.Count > 0 Then
dr = ds.Tables(0).Rows(0)

al.LoginID = dr("sLoginID").ToString()
al.AppName = dr("sAppName").ToString()
al.AppKey = Convert.ToInt32(dr("iAppID"))
al.LoginKey = Convert.ToInt32(dr("iUserID"))

DeleteToken(Convert.ToInt32(dr("iAppTokenID")))
End If

Catch ex As Exception
Throw ex
End Try

Return al
End Function


ロール ベースのセキュリティ

Web アプリケーションに対して検証されたユーザーは、default.aspx ページにリダイレクトされます。ブラウザには認証 Cookie が送られているため、この Cookie は毎回送り返されます。ユーザーが要求先のページにルーティングされる前に、global.asax ファイル内の Application_AuthenticateRequest メソッドが呼び出されます。このメソッドでは、このユーザーに関連付ける任意のロールを構築できます。このメソッドについて説明した記事は他にもあるため、ここでは説明は省略します。これがどのように機能するかは、サンプル コードを見てください。ロール ベースのセキュリティ用のコードは非常に単純です。コードはキャッシュを使用すればさらに強化できますが、ここでは基本を理解していただけるはずです。

シングル サインオン システムを強化する

この記事では、エンタープライズ セキュリティ システムの作成方法について詳しく説明してきました。ただし、このシステムの作成に関する "すべての" 局面について説明することは、この記事の趣旨ではありません。たとえば、管理者がユーザーを追加し、ユーザーをアプリケーションにマッピングできるようにするには、一連の保守画面を追加する必要があります。この一連の画面の安全を保護し、特定のロールを持つユーザーのみがアクセスできるようにする必要があります。

このシステムに追加できるもう 1 つの強化機能として、システムにまだ属していないドメイン ユーザーを自動的に追加できるようにする機能が挙げられます。これによって、すべてのユーザーを手入力することなく、ユーザーをシステムに追加できるようになります。または、ユーザーを追加するためのアプリケーションを作成することもできます。これらのユーザーには既定のロールを割り当てて、特定のアプリケーションのみにアクセスできるようにすることをお勧めします。また、管理者は新しいユーザーがこのような方法で追加されたときに、電子メール メッセージで通知を受けることができます。

このセキュリティ システムは、作成される特定のトークンに依存しており、このトークンの作成直後に Web アプリケーションが呼び出されるため、Web アプリケーションが起動要求に応答しない場合に問題が生じることがあります。このような場合、トークンはデータベース内に残されるため、それが原因でセキュリティが侵害されるおそれがあります。この問題を回避するために、指定した分数よりも古いトークンを削除するための定期的なジョブを作成することをお勧めします。または、トークンと、トークンの作成時刻で構成される独自の固有のトークンを作成することもできます。そうすれば、VerifyLoginToken メソッドを変更するだけで、作成時刻から指定の分数を経過していないかどうかを確認できるようになります。

この記事で使用したトークンは GUID です。このトークンにより、データベースへのコールバックを使用して、ユーザーのプロファイル情報を取得することが要求されます。このシステムに望ましい強化機能として、WS-Security 拡張標準に基づくトークンを使用して、プロファイル情報を暗号化し、これを Application Launcher からのトークンとして各 Web アプリケーションに渡すようにする機能があります。こうすると、毎回のデータベースとのやり取りを避けることができます。

当然のことながら、コード内のすべての動的 SQL 呼び出しを変更し、ストアド プロシージャやパラメータ付きのコマンド オブジェクトを使用するように指定することで、SQL の挿入攻撃を回避し、これらのテーブルを完全に保護する必要があります。ストアド プロシージャおよびコマンド オブジェクトのパラメータはできるだけ使用してください。

サンプルをインストールする

この記事のために、2 つのサンプル アプリケーションが作成されています。これらはどちらも Web アプリケーションです。それぞれの Web アプリケーション用のソリューションにはクラス ライブラリも含まれています。サンプルは Microsoft Visual Basic® .NET と C# の両方で作成されているため、どちらのバージョンを使用するかを選択できます。ソリューション ファイルには .SQL ファイルが含まれているため、データベースに適切なテーブルを簡単に作成できるようになっています。これらの .SQL ファイルは SQL Server 用に作成されたものですが、比較的簡単に他のデータベース システムに合わせて変更できます。サンプル アプリケーションをインストールするための手順は次のとおりです。

  1. eSecurity という名前のデータベースを DBMS 内に作成します。
  2. .SQL ファイルを実行して、eSecurity データベース内にテーブルを作成します。
  3. 提供された .ZIP ファイル内のファイルを解凍し、フォルダに配置します。
  4. 使用する各フォルダを参照する仮想ディレクトリを作成します。たとえば、VB.NET サンプルを使用する場合は、これらの 2 つのフォルダを参照する、AppLauncherVB および AppTestVB という 2 つの仮想ディレクトリを作成します。
  5. .SLN ファイルを変更し、仮想ディレクトリ フォルダを参照するようにします。

まとめ

Web アプリケーションのためのシングル サインオン システムを構築すると、ユーザーの作業時間を節約し、エンタープライズ全体で複数のログイン ID とパスワードを記憶しなければならない必要性がなくなります。また、外部ユーザーをドメインに追加しなくても、これらのユーザーが内部の Web アプリケーションを使用することを許可できるようになります。このようなシステムの実装に必要なものは、少数の単純なテーブルとクラスだけです。シングル サインオンを使用した堅牢なエンタープライズ セキュリティ システムの作成には多くの作業が必要になりますが、この記事で紹介されているサンプルを開始点として利用できます。

関連記事


著者紹介

Paul D. Sheriff は、PDSA, Inc. (http://www.pdsa.com/products) の社長を務めています。PDSA 社は、SDLC 文書やアーキテクチャ フレームワークをはじめとする .NET コンサルティング、製品、およびサービスを提供するマイクロソフトのコンサルティング会社兼パートナーです。Sheriff 氏は Microsoft Regional Director for Southern California も兼任しており、.NET 関連の著書には『ASP.NET Developer's Jumpstart』(Addison-Wesley 出版) があります。また、PSDA の Web サイトには同氏による eBook も何冊か掲載されています。Sheriff 氏に直接連絡を取るには、PSheriff@pdsa.com まで電子メールをお送りください。


この情報は役に立ちましたか。
(残り 1500 文字)