チュートリアル : カスタムの認証および承認の実装

更新 : 2007 年 11 月

このチュートリアルでは、IIdentity および IPrincipal から派生するクラスを使用してカスタムの認証および承認を実装する方法を説明します。また、このチュートリアルでは、IPrincipal から派生するクラスのインスタンスを My.User.CurrentPrincipal に設定することによって、アプリケーション スレッドの既定の ID である Windows の ID をオーバーライドする方法も説明します。新しいユーザー情報は My.User オブジェクトを通じてすぐに利用できます。このオブジェクトは、スレッドの現在のユーザー ID についての情報を返します。

ビジネス アプリケーションでは、ユーザーによって提示される資格情報に基づいて、データやリソースへのアクセスを与えることがよくあります。このようなアプリケーションは、通常、ユーザーのロールを調べ、そのロールに基づいたリソースへのアクセスを許可します。共通言語ランタイムには、Windows アカウントまたはカスタム ID に基づくロール ベース承認のサポートが用意されています。詳細については、「ロール ベース セキュリティ」を参照してください。

概要

まずは、メイン フォームとログイン フォームを持つプロジェクトを設定し、カスタムの認証を使用するように構成します。

サンプル アプリケーションを作成するには

  1. 新しい Visual Basic Windows アプリケーション プロジェクトを作成します。詳細については、「方法 : Windows アプリケーション プロジェクトを作成する」を参照してください。

    メイン フォームの既定の名前は Form1 です。

  2. [プロジェクト] メニューの [新しい項目の追加] をクリックします。

  3. [ログイン フォーム] テンプレートを選択し、[追加] をクリックします。

    ログイン フォームの既定の名前は LoginForm1 です。

  4. [プロジェクト] メニューの [新しい項目の追加] をクリックします。

  5. [クラス] テンプレートを選択し、名前を SampleIIdentity に変更して、[追加] をクリックします。

  6. [プロジェクト] メニューの [新しい項目の追加] をクリックします。

  7. [クラス] テンプレートを選択し、名前を SampleIPrincipal に変更して、[追加] をクリックします。

  8. [プロジェクト] メニューの [<ApplicationName> のプロパティ] をクリックします。

  9. プロジェクト デザイナで、[アプリケーション] タブをクリックします。

  10. [認証モード] ボックスを [アプリケーション定義] に変更します。

メイン フォームを構成するには

  1. フォーム デザイナで Form1 に切り替えます。

  2. ツールボックスから [Button] を Form1 に追加します。

    ボタンの既定の名前は Button1 です。

  3. ボタンのテキストを Authenticate に変更します。

  4. ツールボックスから [Label] を Form1 に追加します。

    ラベルの既定の名前は Label1 です。

  5. ラベルのテキストを空の文字列に変更します。

  6. ツールボックスから [Label] を Form1 に追加します。

    ラベルの既定の名前は Label2 です。

  7. ラベルのテキストを空の文字列に変更します。

  8. Button1 をダブルクリックし、Click イベントのイベント ハンドラを作成して、コード エディタを開きます。

  9. 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 を実装するクラスを作成するには

  1. ソリューション エクスプローラで SampleIIdentity.vb ファイルを選択します。

    このクラスはユーザーの ID をカプセル化します。

  2. Public Class SampleIIdentity の次の行に、次のコードを追加して、IIdentity を継承します。

    Implements System.Security.Principal.IIdentity
    

    このコードを追加して Enter キーを押すと、実装が必要なスタブ プロパティがコード エディタによって作成されます。

  3. ユーザー名、およびユーザーが認証されているかどうかを示す値を格納するためのプライベート フィールドを追加します。

    Private nameValue As String
    Private authenticatedValue As Boolean
    Private roleValue As ApplicationServices.BuiltInRole
    
  4. AuthenticationType プロパティに次のコードを入力します。

    AuthenticationType プロパティでは、現在の認証機構を示す文字列を返す必要があります。

    この例では、明示的に指定された認証を使用するため、この文字列は "Custom Authentication" です。ユーザー認証データが SQL Server データベースに格納される場合なら、この値は "SqlDatabase" となります。

    Return "Custom Authentication"
    
  5. IsAuthenticated プロパティに次のコードを入力します。

    Return authenticatedValue
    

    IsAuthenticated プロパティでは、ユーザーが認証されているかどうかを示す値を返す必要があります。

  6. Name プロパティでは、この ID と関連付けられたユーザーの名前を返す必要があります。

    Name プロパティに次のコードを入力します。

    Return nameValue
    
  7. ユーザーのロールを返すプロパティを作成します。

    Public ReadOnly Property Role() As ApplicationServices.BuiltInRole
        Get
            Return roleValue
        End Get
    End Property
    
  8. 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
    
  9. ユーザー名とパスワードの組み合わせが有効かどうかを判断する、IsValidNameAndPassword という名前のメソッドを作成します。

    ms172766.alert_security(ja-jp,VS.90).gifセキュリティに関するメモ :

    認証アルゴリズムでは、パスワードを安全に扱う必要があります。たとえば、パスワードをクラス フィールドとして格納しないようにします。

    ユーザー パスワードは、システム内に格納しないようにします。その情報がリークしたら、セキュリティは無効になるからです。格納するとしたら、各ユーザーのパスワードのハッシュを格納します (ハッシュ関数を使用すると、データがかく乱され、出力データから入力データを導き出すことができないようになります)。パスワードのハッシュからパスワード自体を直接的に確認することはできません。

    しかし、悪意のあるユーザーは、可能性のあるすべてのパスワードのハッシュの辞書を、時間をかけて生成したうえで、目的のハッシュに対応するパスワードを探すという手を使う可能性があります。この種類の攻撃に対する保護のためには、ハッシュを求める前のパスワードに 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
    
  10. GetHashedPassword および GetSalt という名前の関数を作成します。指定されたユーザーに対応する、ハッシュ化パスワードと salt を返す関数です。

    ms172766.alert_security(ja-jp,VS.90).gifセキュリティに関するメモ :

    ハッシュ化パスワードと salt をクライアント アプリケーションにハードコーディングすることは、次の 2 つの理由により避ける必要があります。1 つは、悪意のあるユーザーからコードにアクセスされて、ハッシュ衝突を見つけ出される可能性があるからです。もう 1 つは、ユーザーのパスワードの変更や取り消しができないからです。アプリケーションでは、所定のユーザーに対応する、ハッシュ化パスワードと salt を、管理者によって管理されている、セキュリティで保護された場所から取得する必要があります。

    この例では、単純化のために、ハッシュ化パスワードと salt をハードコーディングしていますが、実際に使用するコードでは、もっと安全な方法を使用する必要があります。たとえば、SQL Server データベースにユーザー情報を格納して、ストアド プロシージャでアクセスするという方法があります。詳細については、「方法 : データベース内のデータに接続する」を参照してください。

    ms172766.alert_note(ja-jp,VS.90).gifメモ :

    ハードコーディングしている、このハッシュ化パスワードに対応するパスワードについては、「アプリケーションのテスト」のセクションを参照してください。

    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 を実装するクラスを作成するには

  1. ソリューション エクスプローラで SampleIPrincipal.vb ファイルを選択します。

    このクラスはユーザーの ID をカプセル化します。My.User オブジェクトを使用すると、このプリンシパルを現在のスレッドに割り当てて、ユーザーの ID にアクセスできます。

  2. Public Class SampleIPrincipal の次の行に、次のコードを追加して、IPrincipal を継承します。

    Implements System.Security.Principal.IPrincipal
    

    このコードを追加して Enter キーを押すと、実装が必要なスタブ プロパティおよびメソッドがコード エディタによって作成されます。

  3. このプリンシパルに関連付けられた ID を格納するためのプライベート フィールドを追加します。

    Private identityValue As SampleIIdentity
    
  4. Identity プロパティに次のコードを入力します。

    Return identityValue
    

    Identity プロパティでは、現在のプリンシパルのユーザー ID を返す必要があります。

  5. IsInRole メソッドに次のコードを入力します。

    IsInRole メソッドでは、指定したロールに現在のプリンシパルが属するかどうかを判断します。

    Return role = identityValue.Role.ToString
    
  6. 所定のユーザー名およびパスワードに対応する 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 をそのインスタンスに対して設定できます。

ログイン フォームを構成するには

  1. デザイナで LoginForm1 を選択します。

  2. [OK] ボタンをダブルクリックして、Click イベントをコード エディタで開きます。

  3. 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
    

アプリケーションのテスト

アプリケーションに認証コードを追加したので、アプリケーションを実行して、ユーザーの認証を試してみることができます。

アプリケーションをテストするには

  1. アプリケーションを起動します。

  2. [Authenticate] をクリックします。

    ログイン フォームが開きます。

  3. [ユーザー名] ボックスに「TestUser」、[パスワード] ボックスに「BadPassword」と入力し、[OK] をクリックします。

    ユーザー名とパスワードのペアが正しくないことを通知するメッセージ ボックスが表示されます。

  4. [OK] をクリックしてメッセージ ボックスを閉じます。

  5. [キャンセル] をクリックしてログイン フォームを閉じます。

    メイン フォームのラベルには、[User not authenticated] および [User is not an Administrator] と表示されます。

  6. [Authenticate] をクリックします。

    ログイン フォームが開きます。

  7. [ユーザー名] ボックスに「TestUser」、[パスワード] ボックスに「Password」と入力し、[OK] をクリックします。パスワードの大文字と小文字は正しく入力してください。

    メイン フォームのラベルには、[Authenticated TestUser] および [User is an Administrator] と表示されます。

参照

処理手順

方法 : データベース内のデータに接続する

概念

ユーザー データへのアクセス

参照

My.User オブジェクト

IIdentity

IPrincipal

その他の技術情報

Visual Basic での .NET Framework の認証と承認