연습: 사용자 지정 인증 및 권한 부여 구현

업데이트: 2007년 11월

이 연습에서는 IIdentityIPrincipal에서 파생되는 클래스를 사용하여 사용자 지정 인증 및 권한 부여를 구현하는 방법을 보여 줍니다. 또한 My.User.CurrentPrincipal을 IPrincipal에서 파생되는 클래스의 인스턴스로 설정하여 응용 프로그램 스레드의 기본 ID인 Windows ID를 재정의하는 방법을 보여 줍니다. 새 사용자 정보는 스레드의 현재 사용자 ID에 대한 정보를 반환하는 My.User 개체를 통해 즉시 사용할 수 있습니다.

종종 업무용 응용 프로그램을 사용하여 사용자가 제공하는 자격 증명에 따라 데이터 또는 리소스에 액세스할 수 있습니다. 일반적으로 이러한 응용 프로그램에서는 사용자의 역할을 확인한 후 해당 역할에 따라 리소스에 대한 액세스 권한을 제공합니다. 공용 언어 런타임에는 Windows 계정 또는 사용자 지정 ID에 따라 역할 기반 권한을 지원합니다. 자세한 내용은 역할 기반 보안을 참조하십시오.

시작

먼저 기본 폼과 로그인 폼으로 프로젝트를 설정하고 사용자 지정 인증을 사용하도록 구성합니다.

샘플 응용 프로그램을 만들려면

  1. 새 Visual Basic Windows 응용 프로그램 프로젝트를 만듭니다. 자세한 내용은 방법: Windows 응용 프로그램 프로젝트 만들기를 참조하십시오.

    기본 폼의 기본 이름은 Form1입니다.

  2. 프로젝트 메뉴에서 새 항목 추가를 클릭합니다.

  3. 로그인 폼 템플릿을 선택하고 추가를 클릭합니다.

    로그인 폼의 기본 이름은 LoginForm1입니다.

  4. 프로젝트 메뉴에서 새 항목 추가를 클릭합니다.

  5. 클래스 템플릿을 선택하고 이름을 SampleIIdentity로 바꾼 다음 추가를 클릭합니다.

  6. 프로젝트 메뉴에서 새 항목 추가를 클릭합니다.

  7. 클래스 템플릿을 선택하고 이름을 SampleIPrincipal로 바꾼 다음 추가를 클릭합니다.

  8. 프로젝트 메뉴에서 <ApplicationName> 속성을 클릭합니다.

  9. 프로젝트 디자이너에서 응용 프로그램 탭을 클릭합니다.

  10. 인증 모드 드롭다운을 응용 프로그램 정의로 변경합니다.

기본 폼을 구성하려면

  1. 폼 디자이너에서 Form1로 전환합니다.

  2. 도구 상자에서 Form1에 단추를 추가합니다.

    단추의 기본 이름은 Button1입니다.

  3. 단추 텍스트를 Authenticate로 변경합니다.

  4. 도구 상자에서 Form1에 레이블을 추가합니다.

    레이블의 기본 이름은 Label1입니다.

  5. 레이블 텍스트를 빈 문자열로 변경합니다.

  6. 도구 상자에서 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에서는 IIdentityIPrincipal 인터페이스를 인증 및 권한 부여의 기초로 사용합니다. 다음 절차에서 설명하듯이 응용 프로그램에서는 이들 인터페이스를 구현하여 사용자 지정 사용자 인증을 사용할 수 있습니다.

IIdentity를 구현하는 클래스를 만들려면

  1. 솔루션 탐색기에서 SampleIIdentity.vb 파일을 선택합니다.

    이 클래스는 사용자의 ID를 캡슐화합니다.

  2. Public Class SampleIIdentity 바로 다음 줄에 아래 코드를 추가하여 IIdentity에서 상속합니다.

    Implements System.Security.Principal.IIdentity
    

    이 코드를 추가하고 Enter 키를 누르면 사용자가 구현해야 할 스텁 속성이 코드 편집기에서 만들어집니다.

  3. 사용자가 인증되었는지 여부를 나타내는 값과 사용자 이름을 저장할 private 필드를 추가합니다.

    Private nameValue As String
    Private authenticatedValue As Boolean
    Private roleValue As ApplicationServices.BuiltInRole
    
  4. AuthenticationType 속성에 다음 코드를 입력합니다.

    AuthenticationType 속성은 현재의 인증 메커니즘을 나타내는 문자열을 반환해야 합니다.

    이 예제에서는 명시적으로 지정된 인증을 사용하므로 "사용자 지정 인증" 문자열이 반환됩니다. 사용자 인증 데이터가 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라는 메서드를 만듭니다.

    보안 정보:

    인증 알고리즘에서는 암호를 안전하게 처리해야 합니다. 예를 들어, 암호를 클래스 필드에 저장하지 않아야 합니다.

    사용자 암호가 누출되면 보안이 침해되므로 사용자 암호를 시스템에 저장하지 않도록 해야 합니다. 각 사용자 암호의 해시는 저장할 수 있습니다. 해시 함수는 데이터를 스크램블하므로 출력을 통해 입력을 추측할 수 없습니다. 암호의 해시를 사용하여 직접 암호를 알아낼 수 없습니다.

    하지만 악의적 사용자는 가능한 모든 암호의 해시가 포함된 사전을 생성하여 해당 해시의 암호를 찾아낼 수도 있습니다. 이러한 유형의 공격으로부터 보호하려면 암호를 해시하기 전에 솔트를 추가하여 솔트된 해시를 생성해야 합니다. 솔트는 각 암호에 고유한 추가 데이터로, 해시 사전을 미리 추정하지 못하도록 합니다.

    악의적 사용자로부터 암호를 보호하려면 암호의 솔트된 해시만 저장하되 되도록이면 안전한 컴퓨터에 저장해야 합니다. 악의적 사용자가 솔트된 해시에서 암호를 알아내는 것은 매우 어렵습니다. 이 예제에서는 GetHashedPassword 및 GetSalt 메서드를 사용하여 사용자의 해시된 암호와 솔트를 로드합니다.

    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 함수를 만듭니다.

    보안 정보:

    해시된 암호와 솔트를 클라이언트 응용 프로그램에 하드 코딩하는 것은 다음 두 가지 이유 때문에 피해야 합니다. 첫 번째로 악의적 사용자가 해시된 암호와 솔트에 액세스하여 해시 충돌을 찾아낼 수 있습니다. 두 번째로 사용자의 암호를 변경하거나 취소할 수 없게 됩니다. 응용 프로그램은 관리자가 유지 관리하는 안전한 소스에서 특정 사용자의 해시된 암호와 솔트를 가져와야 합니다.

    이 예제에서는 간단히 처리하기 위해 해시된 암호와 솔트를 하드 코딩하지만 실제 코드에서는 더 안전한 방법을 사용해야 합니다. 예를 들어, 사용자 정보를 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을 구현하는 클래스를 만들려면

  1. 솔루션 탐색기에서 SampleIPrincipal.vb 파일을 선택합니다.

    이 클래스는 사용자의 ID를 캡슐화합니다. My.User 개체를 사용하여 이 보안 주체를 현재 스레드에 연결하고 사용자의 ID에 액세스합니다.

  2. Public Class SampleIPrincipal 바로 다음 줄에 아래 코드를 추가하여 IPrincipal에서 상속합니다.

    Implements System.Security.Principal.IPrincipal
    

    이 코드를 추가하고 Enter 키를 누르면 사용자가 구현해야 할 스텁 속성과 메서드가 코드 편집기에서 만들어집니다.

  3. 이 보안 주체와 연관된 ID를 저장할 private 필드를 추가합니다.

    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. 확인 단추를 클릭하여 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. User name 상자에 TestUser를 입력하고 Password 상자에 BadPassword를 입력한 다음 OK를 클릭합니다.

    사용자 이름과 암호가 올바르지 않음을 나타내는 메시지 상자가 열립니다.

  4. 확인을 클릭하여 메시지 상자를 닫습니다.

  5. 취소를 클릭하여 로그인 폼을 닫습니다.

    기본 폼의 레이블에 User not authenticated 및 User is not an Administrator가 표시됩니다.

  6. Authenticate를 클릭합니다.

    로그인 폼이 열립니다.

  7. 사용자 이름 텍스트 상자에 TestUser를 입력하고 암호 텍스트 상자에 Password를 입력한 다음 확인을 클릭합니다. 입력한 암호의 대/소문자가 맞는지 확인합니다.

    기본 폼의 레이블에 Authenticated TestUser 및 User is an Administrator가 표시됩니다.

참고 항목

작업

방법: 데이터베이스의 데이터에 연결

개념

사용자 데이터 액세스

참조

My.User 개체

IIdentity

IPrincipal

기타 리소스

Visual Basic을 사용한 .NET Framework의 인증 및 권한 부여