チュートリアル : カスタムの認証および承認の実装
更新 : 2007 年 11 月
このチュートリアルでは、IIdentity および IPrincipal から派生するクラスを使用してカスタムの認証および承認を実装する方法を説明します。また、このチュートリアルでは、IPrincipal から派生するクラスのインスタンスを My.User.CurrentPrincipal に設定することによって、アプリケーション スレッドの既定の ID である Windows の ID をオーバーライドする方法も説明します。新しいユーザー情報は My.User オブジェクトを通じてすぐに利用できます。このオブジェクトは、スレッドの現在のユーザー ID についての情報を返します。
ビジネス アプリケーションでは、ユーザーによって提示される資格情報に基づいて、データやリソースへのアクセスを与えることがよくあります。このようなアプリケーションは、通常、ユーザーのロールを調べ、そのロールに基づいたリソースへのアクセスを許可します。共通言語ランタイムには、Windows アカウントまたはカスタム ID に基づくロール ベース承認のサポートが用意されています。詳細については、「ロール ベース セキュリティ」を参照してください。
概要
まずは、メイン フォームとログイン フォームを持つプロジェクトを設定し、カスタムの認証を使用するように構成します。
サンプル アプリケーションを作成するには
新しい Visual Basic Windows アプリケーション プロジェクトを作成します。詳細については、「方法 : Windows アプリケーション プロジェクトを作成する」を参照してください。
メイン フォームの既定の名前は Form1 です。
[プロジェクト] メニューの [新しい項目の追加] をクリックします。
[ログイン フォーム] テンプレートを選択し、[追加] をクリックします。
ログイン フォームの既定の名前は LoginForm1 です。
[プロジェクト] メニューの [新しい項目の追加] をクリックします。
[クラス] テンプレートを選択し、名前を SampleIIdentity に変更して、[追加] をクリックします。
[プロジェクト] メニューの [新しい項目の追加] をクリックします。
[クラス] テンプレートを選択し、名前を SampleIPrincipal に変更して、[追加] をクリックします。
[プロジェクト] メニューの [<ApplicationName> のプロパティ] をクリックします。
プロジェクト デザイナで、[アプリケーション] タブをクリックします。
[認証モード] ボックスを [アプリケーション定義] に変更します。
メイン フォームを構成するには
フォーム デザイナで Form1 に切り替えます。
ツールボックスから [Button] を Form1 に追加します。
ボタンの既定の名前は Button1 です。
ボタンのテキストを Authenticate に変更します。
ツールボックスから [Label] を Form1 に追加します。
ラベルの既定の名前は Label1 です。
ラベルのテキストを空の文字列に変更します。
ツールボックスから [Label] を Form1 に追加します。
ラベルの既定の名前は Label2 です。
ラベルのテキストを空の文字列に変更します。
Button1 をダブルクリックし、Click イベントのイベント ハンドラを作成して、コード エディタを開きます。
Button1_Click メソッドに次のコードを追加します。
My.Forms.LoginForm1.ShowDialog() ' Check if the user was authenticated. If My.User.IsAuthenticated Then Me.Label1.Text = "Authenticated " & My.User.Name Else Me.Label1.Text = "User not authenticated" End If If My.User.IsInRole(ApplicationServices.BuiltInRole.Administrator) Then Me.Label2.Text = "User is an Administrator" Else Me.Label2.Text = "User is not an Administrator" End If
このアプリケーションは実行できますが、認証コードがないため、ユーザーを認証しません。認証コードの追加については、次のセクションで説明します。
ID の作成
.NET Framework では、認証および承認の基盤として IIdentity インターフェイスおよび IPrincipal インターフェイスが使用されます。アプリケーションでは、これらのインターフェイスを実装することにより、カスタムのユーザー認証を使用できます。以下の手順はその例です。
IIdentity を実装するクラスを作成するには
ソリューション エクスプローラで SampleIIdentity.vb ファイルを選択します。
このクラスはユーザーの ID をカプセル化します。
Public Class SampleIIdentity の次の行に、次のコードを追加して、IIdentity を継承します。
Implements System.Security.Principal.IIdentity
このコードを追加して Enter キーを押すと、実装が必要なスタブ プロパティがコード エディタによって作成されます。
ユーザー名、およびユーザーが認証されているかどうかを示す値を格納するためのプライベート フィールドを追加します。
Private nameValue As String Private authenticatedValue As Boolean Private roleValue As ApplicationServices.BuiltInRole
AuthenticationType プロパティに次のコードを入力します。
AuthenticationType プロパティでは、現在の認証機構を示す文字列を返す必要があります。
この例では、明示的に指定された認証を使用するため、この文字列は "Custom Authentication" です。ユーザー認証データが SQL Server データベースに格納される場合なら、この値は "SqlDatabase" となります。
Return "Custom Authentication"
IsAuthenticated プロパティに次のコードを入力します。
Return authenticatedValue
IsAuthenticated プロパティでは、ユーザーが認証されているかどうかを示す値を返す必要があります。
Name プロパティでは、この ID と関連付けられたユーザーの名前を返す必要があります。
Name プロパティに次のコードを入力します。
Return nameValue
ユーザーのロールを返すプロパティを作成します。
Public ReadOnly Property Role() As ApplicationServices.BuiltInRole Get Return roleValue End Get End Property
Sub New メソッドを作成します。この中では、ユーザーを認証してから、名前とパスワードに基づいてユーザーの名前とロールを設定することにより、クラスを初期化します。
このメソッドでは、ユーザー名とパスワードの組み合わせが有効かどうかを判断するために、IsValidNameAndPassword という名前のメソッドを呼び出します。
Public Sub New(ByVal name As String, ByVal password As String) ' The name is not case sensitive, but the password is. If IsValidNameAndPassword(name, password) Then nameValue = name authenticatedValue = True roleValue = ApplicationServices.BuiltInRole.Administrator Else nameValue = "" authenticatedValue = False roleValue = ApplicationServices.BuiltInRole.Guest End If End Sub
ユーザー名とパスワードの組み合わせが有効かどうかを判断する、IsValidNameAndPassword という名前のメソッドを作成します。
セキュリティに関するメモ : 認証アルゴリズムでは、パスワードを安全に扱う必要があります。たとえば、パスワードをクラス フィールドとして格納しないようにします。
ユーザー パスワードは、システム内に格納しないようにします。その情報がリークしたら、セキュリティは無効になるからです。格納するとしたら、各ユーザーのパスワードのハッシュを格納します (ハッシュ関数を使用すると、データがかく乱され、出力データから入力データを導き出すことができないようになります)。パスワードのハッシュからパスワード自体を直接的に確認することはできません。
しかし、悪意のあるユーザーは、可能性のあるすべてのパスワードのハッシュの辞書を、時間をかけて生成したうえで、目的のハッシュに対応するパスワードを探すという手を使う可能性があります。この種類の攻撃に対する保護のためには、ハッシュを求める前のパスワードに salt を追加して、salt 処理されたハッシュを生成する必要があります。salt とは、各パスワードに対して一意の、追加的なデータです。これにより、ハッシュ辞書を事前計算するのは不可能となります。
悪意のあるユーザーからパスワードを保護するには、パスワードの salt 処理されたハッシュのみを格納する必要があり、できれば、セキュリティで保護されたコンピュータに格納します。悪意のあるユーザーが、salt 処理されたハッシュからパスワードを復元するのは、非常に困難です。この例では、GetHashedPassword メソッドおよび GetSalt メソッドを使用して、ユーザーのハッシュ化パスワードと salt を読み込みます。
Private Function IsValidNameAndPassword( _ ByVal username As String, _ ByVal password As String) _ As Boolean ' Look up the stored hashed password and salt for the username. Dim storedHashedPW As String = GetHashedPassword(username) Dim salt As String = GetSalt(username) 'Create the salted hash. Dim rawSalted As String = salt & Trim(password) Dim saltedPwBytes() As Byte = _ System.Text.Encoding.Unicode.GetBytes(rawSalted) Dim sha1 As New _ System.Security.Cryptography.SHA1CryptoServiceProvider Dim hashedPwBytes() As Byte = sha1.ComputeHash(saltedPwBytes) Dim hashedPw As String = Convert.ToBase64String(hashedPwBytes) ' Compare the hashed password with the stored password. Return hashedPw = storedHashedPW End Function
GetHashedPassword および GetSalt という名前の関数を作成します。指定されたユーザーに対応する、ハッシュ化パスワードと salt を返す関数です。
セキュリティに関するメモ : ハッシュ化パスワードと salt をクライアント アプリケーションにハードコーディングすることは、次の 2 つの理由により避ける必要があります。1 つは、悪意のあるユーザーからコードにアクセスされて、ハッシュ衝突を見つけ出される可能性があるからです。もう 1 つは、ユーザーのパスワードの変更や取り消しができないからです。アプリケーションでは、所定のユーザーに対応する、ハッシュ化パスワードと salt を、管理者によって管理されている、セキュリティで保護された場所から取得する必要があります。
この例では、単純化のために、ハッシュ化パスワードと salt をハードコーディングしていますが、実際に使用するコードでは、もっと安全な方法を使用する必要があります。たとえば、SQL Server データベースにユーザー情報を格納して、ストアド プロシージャでアクセスするという方法があります。詳細については、「方法 : データベース内のデータに接続する」を参照してください。
メモ : ハードコーディングしている、このハッシュ化パスワードに対応するパスワードについては、「アプリケーションのテスト」のセクションを参照してください。
Private Function GetHashedPassword(ByVal username As String) As String ' Code that gets the user's hashed password goes here. ' This example uses a hard-coded hashed passcode. ' In general, the hashed passcode should be stored ' outside of the application. If Trim(username).ToLower = "testuser" Then Return "ZFFzgfsGjgtmExzWBRmZI5S4w6o=" Else Return "" End If End Function Private Function GetSalt(ByVal username As String) As String ' Code that gets the user's salt goes here. ' This example uses a hard-coded salt. ' In general, the salt should be stored ' outside of the application. If Trim(username).ToLower = "testuser" Then Return "Should be a different random value for each user" Else Return "" End If End Function
この時点で、SampleIIdentity.vb ファイルの内容は次のコードのようになります。
Public Class SampleIIdentity
Implements System.Security.Principal.IIdentity
Private nameValue As String
Private authenticatedValue As Boolean
Private roleValue As ApplicationServices.BuiltInRole
Public ReadOnly Property AuthenticationType() As String Implements System.Security.Principal.IIdentity.AuthenticationType
Get
Return "Custom Authentication"
End Get
End Property
Public ReadOnly Property IsAuthenticated() As Boolean Implements System.Security.Principal.IIdentity.IsAuthenticated
Get
Return authenticatedValue
End Get
End Property
Public ReadOnly Property Name() As String Implements System.Security.Principal.IIdentity.Name
Get
Return nameValue
End Get
End Property
Public ReadOnly Property Role() As ApplicationServices.BuiltInRole
Get
Return roleValue
End Get
End Property
Public Sub New(ByVal name As String, ByVal password As String)
' The name is not case sensitive, but the password is.
If IsValidNameAndPassword(name, password) Then
nameValue = name
authenticatedValue = True
roleValue = ApplicationServices.BuiltInRole.Administrator
Else
nameValue = ""
authenticatedValue = False
roleValue = ApplicationServices.BuiltInRole.Guest
End If
End Sub
Private Function IsValidNameAndPassword( _
ByVal username As String, _
ByVal password As String) _
As Boolean
' Look up the stored hashed password and salt for the username.
Dim storedHashedPW As String = GetHashedPassword(username)
Dim salt As String = GetSalt(username)
'Create the salted hash.
Dim rawSalted As String = salt & Trim(password)
Dim saltedPwBytes() As Byte = _
System.Text.Encoding.Unicode.GetBytes(rawSalted)
Dim sha1 As New _
System.Security.Cryptography.SHA1CryptoServiceProvider
Dim hashedPwBytes() As Byte = sha1.ComputeHash(saltedPwBytes)
Dim hashedPw As String = Convert.ToBase64String(hashedPwBytes)
' Compare the hashed password with the stored password.
Return hashedPw = storedHashedPW
End Function
Private Function GetHashedPassword(ByVal username As String) As String
' Code that gets the user's hashed password goes here.
' This example uses a hard-coded hashed passcode.
' In general, the hashed passcode should be stored
' outside of the application.
If Trim(username).ToLower = "testuser" Then
Return "ZFFzgfsGjgtmExzWBRmZI5S4w6o="
Else
Return ""
End If
End Function
Private Function GetSalt(ByVal username As String) As String
' Code that gets the user's salt goes here.
' This example uses a hard-coded salt.
' In general, the salt should be stored
' outside of the application.
If Trim(username).ToLower = "testuser" Then
Return "Should be a different random value for each user"
Else
Return ""
End If
End Function
End Class
プリンシパルの作成
次に、IPrincipal を派生するクラスを実装し、SampleIIdentity クラスのインスタンスを返させる必要があります。
IPrincipal を実装するクラスを作成するには
ソリューション エクスプローラで SampleIPrincipal.vb ファイルを選択します。
このクラスはユーザーの ID をカプセル化します。My.User オブジェクトを使用すると、このプリンシパルを現在のスレッドに割り当てて、ユーザーの ID にアクセスできます。
Public Class SampleIPrincipal の次の行に、次のコードを追加して、IPrincipal を継承します。
Implements System.Security.Principal.IPrincipal
このコードを追加して Enter キーを押すと、実装が必要なスタブ プロパティおよびメソッドがコード エディタによって作成されます。
このプリンシパルに関連付けられた ID を格納するためのプライベート フィールドを追加します。
Private identityValue As SampleIIdentity
Identity プロパティに次のコードを入力します。
Return identityValue
Identity プロパティでは、現在のプリンシパルのユーザー ID を返す必要があります。
IsInRole メソッドに次のコードを入力します。
IsInRole メソッドでは、指定したロールに現在のプリンシパルが属するかどうかを判断します。
Return role = identityValue.Role.ToString
所定のユーザー名およびパスワードに対応する SampleIIdentity の新しいインスタンスでクラスを初期化する Sub New メソッドを作成します。
Public Sub New(ByVal name As String, ByVal password As String) identityValue = New SampleIIdentity(name, password) End Sub
このコードでは、SampleIPrincipal クラスに対するユーザー ID を設定します。
この時点で、SampleIPrincipal.vb ファイルの内容は次のコードのようになります。
Public Class SampleIPrincipal
Implements System.Security.Principal.IPrincipal
Private identityValue As SampleIIdentity
Public ReadOnly Property Identity() As System.Security.Principal.IIdentity Implements System.Security.Principal.IPrincipal.Identity
Get
Return identityValue
End Get
End Property
Public Function IsInRole(ByVal role As String) As Boolean Implements System.Security.Principal.IPrincipal.IsInRole
Return role = identityValue.Role.ToString
End Function
Public Sub New(ByVal name As String, ByVal password As String)
identityValue = New SampleIIdentity(name, password)
End Sub
End Class
ログイン フォームの接続
アプリケーションでは、ログイン フォームを使用してユーザー名とパスワードを取得できます。この情報を使用して、SampleIPrincipal クラスのインスタンスを初期化でき、また My.User オブジェクトを使用して、現在のスレッドの ID をそのインスタンスに対して設定できます。
ログイン フォームを構成するには
デザイナで LoginForm1 を選択します。
[OK] ボタンをダブルクリックして、Click イベントをコード エディタで開きます。
OK_Click メソッドのコードを次のコードで置き換えます。
Dim samplePrincipal As New SampleIPrincipal( _ Me.UsernameTextBox.Text, Me.PasswordTextBox.Text) Me.PasswordTextBox.Text = "" If (Not samplePrincipal.Identity.IsAuthenticated) Then ' The user is still not validated. MsgBox("The username and password pair is incorrect") Else ' Update the current principal. My.User.CurrentPrincipal = samplePrincipal Me.Close() End If
アプリケーションのテスト
アプリケーションに認証コードを追加したので、アプリケーションを実行して、ユーザーの認証を試してみることができます。
アプリケーションをテストするには
アプリケーションを起動します。
[Authenticate] をクリックします。
ログイン フォームが開きます。
[ユーザー名] ボックスに「TestUser」、[パスワード] ボックスに「BadPassword」と入力し、[OK] をクリックします。
ユーザー名とパスワードのペアが正しくないことを通知するメッセージ ボックスが表示されます。
[OK] をクリックしてメッセージ ボックスを閉じます。
[キャンセル] をクリックしてログイン フォームを閉じます。
メイン フォームのラベルには、[User not authenticated] および [User is not an Administrator] と表示されます。
[Authenticate] をクリックします。
ログイン フォームが開きます。
[ユーザー名] ボックスに「TestUser」、[パスワード] ボックスに「Password」と入力し、[OK] をクリックします。パスワードの大文字と小文字は正しく入力してください。
メイン フォームのラベルには、[Authenticated TestUser] および [User is an Administrator] と表示されます。