逐步解說:實作自訂驗證和授權
更新:2007 年 11 月
本逐步解說會示範如何利用衍生自 IIdentity 和 IPrincipal 的類別,實作自訂驗證和授權。本逐步解說也會示範如何將 My.User.CurrentPrincipal 設定為衍生自 IPrincipal 之類別的執行個體,以覆寫應用程式執行緒的預設識別 (Identity),即 Windows 識別。您可以透過 My.User 物件立即獲得新使用者資訊,該物件會傳回執行緒之目前使用者身分的相關資訊。
商務應用程式通常根據使用者所提供的認證,提供對資料或資源的存取權。基本上,這類應用程式會檢查使用者的角色並根據該角色提供存取的資源。Common Language Runtime 會根據 Windows 帳戶或自訂識別,提供角色架構授權的支援。如需詳細資訊,請參閱以角色為基礎的安全性。
使用者入門
首先,使用主要表單和登入表單設定專案,然後設定專案以使用自訂驗證。
若要建立範例應用程式
建立新的 Visual Basic Windows 應用程式專案。如需詳細資訊,請參閱 HOW TO:建立 Windows 應用程式專案。
主要表單的預設名稱為 Form1。
在 [專案] 功能表上,按一下 [加入新項目]。
選取 [登入表單] 範本,然後按一下 [加入]。
登入表單的預設名稱為 LoginForm1。
在 [專案] 功能表上,按一下 [加入新項目]。
選取 [類別] 範本,將名稱變更為 SampleIIdentity,然後按一下 [加入]。
在 [專案] 功能表上,按一下 [加入新項目]。
選取 [類別] 範本,將名稱變更為 SampleIPrincipal,然後按一下 [加入]。
在 [專案] 功能表上,按一下 [<ApplicationName> 屬性]。
在 [專案設計工具] 中,按一下 [應用程式] 索引標籤。
將 [驗證模式] 下拉式清單變更為 [由應用程式定義]。
若要設定主要表單
在表單設計工具中切換至 Form1。
在 [工具箱] 中,將 [按鈕] 加入至 Form1 中。
按鈕的預設名稱為 Button1。
將按鈕文字變更為驗證。
在 [工具箱] 中,將 [標籤] 加入至 Form1 中。
標籤的預設名稱為 Label1。
將標籤文字變更為空字串。
在 [工具箱] 中,將 [標籤] 加入至 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
您可以執行應用程式,但是因為沒有驗證程式碼,所以不會驗證任何使用者。在下列章節中會討論如何加入驗證程式碼。
建立識別
.NET Framework 會使用 IIdentity 和 IPrincipal 介面做為驗證和授權的基準。實作這些介面後,您的應用程式就可以使用自訂使用者驗證,如這些程序所示範。
若要建立實作 IIdentity 的類別
在 [方案總管] 中選取 [SampleIIdentity.vb] 檔案。
此類別會封裝使用者的識別。
在 Public Class SampleIIdentity 的下一行中,加入下列程式碼,以繼承 IIdentity。
Implements System.Security.Principal.IIdentity
加入該程式碼並按 ENTER 鍵後,程式碼編輯器會建立您必須實作的 Stub 屬性。
加入私用欄位以存放使用者名稱,以及指示是否已驗證使用者的值。
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 屬性必須傳回與此識別關聯的使用者名稱。
在 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 硬式編碼至用戶端應用程式中,其原因有兩個。第一,惡意使用者可加以存取,並找到雜湊衝突。第二,您無法變更或撤銷使用者的密碼。應用程式應該針對指定的使用者,從系統管理員所維護的安全來源中取得雜湊密碼和 Salt。
雖然為了容易了解之故,此範例會具有硬式編碼的雜湊密碼和 Salt,但您應該在實際執行程式碼中使用更安全的處理方法。例如,您可以將使用者資訊存放在 SQL Server 資料庫中,然後使用預存程序 (Stored Procedure) 加以存取。如需詳細資訊,請參閱 HOW TO:連接至資料庫中的資料。
注意事項:
「測試應用程式」一節中會提供與此硬式編碼之雜湊密碼對應的密碼。
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] 檔案。
此類別會封裝使用者的識別。您可以使用 My.User 物件,將此主體附加到目前的執行緒,並存取使用者的識別。
在 Public Class SampleIPrincipal 的下一行中,加入下列程式碼,以繼承 IPrincipal。
Implements System.Security.Principal.IPrincipal
加入該程式碼並按 ENTER 鍵後,程式碼編輯器會建立您必須實作的 Stub 屬性和方法。
加入私用欄位,以存放與此主體關聯的識別。
Private identityValue As SampleIIdentity
在 Identity 屬性中輸入下列程式碼。
Return identityValue
Identity 屬性必須傳回目前主體的使用者身分。
在 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 類別的使用者識別。
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 物件設定該執行個體的目前執行緒識別。
若要設定登入表單
在設計工具中選擇 LoginForm1。
按兩下 [確定] 按鈕,以針對 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
測試應用程式
現在,應用程式具有驗證程式碼,您可以執行應用程式並嘗試驗證使用者。
若要測試應用程式
啟動應用程式。
按一下 [驗證]。
登入表單隨即開啟。
在 [使用者名稱] 方塊中輸入 [TestUser],並在 [密碼] 方塊中輸入 [BadPassword],然後按一下 [確定]。
訊息方塊隨即開啟,表示使用者名稱和密碼配對不正確。
按一下 [確定] 來解除訊息方塊。
按一下 [取消],以關閉登入表單。
主要表單中的標籤現在為 [使用者未經驗證] 和 [使用者不是系統管理員]。
按一下 [驗證]。
登入表單隨即開啟。
在 [使用者名稱] 文字方塊中輸入 [TestUser],並在 [密碼] 文字方塊中輸入 [Password],然後按一下 [確定]。請確定是以正確的大小寫輸入密碼。
主要表單中的標籤現在為 [TestUser 已驗證] 和 [使用者是系統管理員]。