以角色為基礎的授權 (VB)
注意
自本文撰寫以來,ASP.NET 成員資格提供者已被 ASP.NET 身分識別取代。 強烈建議您更新應用程式以使用 ASP.NET 身分識別 平臺,而不是本文撰寫時精選的成員資格提供者。 ASP.NET 身分識別對於 ASP.NET 成員資格系統有一些優點,包括 :
- 更好的效能
- 改善擴充性和可測試性
- 支援 OAuth、OpenID Connect 和雙因素驗證
- 宣告型身分識別支援
- 與 ASP.Net Core 更好的互操作性
本教學課程一開始會探討角色架構如何與使用者的角色與其安全性內容產生關聯。 接著會檢查如何套用角色型 URL 授權規則。 接下來,我們將探討如何使用宣告式和程序設計方式來改變所顯示的數據,以及 ASP.NET 頁面所提供的功能。
簡介
在使用者型授權教學課程中,我們已瞭解如何使用URL授權來指定使用者可以流覽特定頁面集的內容。 只要在 中使用 Web.config
一些標記,我們可以指示 ASP.NET 只允許已驗證的使用者瀏覽頁面。 或者,我們可以指定只允許 Tito 和 Bob 的使用者,或指出除了 Sam 以外的所有已驗證使用者都允許。
除了 URL 授權之外,我們也查看了宣告式和程式設計技術,以根據使用者瀏覽來控制顯示的數據,以及頁面所提供的功能。 特別是,我們建立了一個頁面,其中列出目前目錄的內容。 任何人都可以流覽此頁面,但只有已驗證的使用者可以檢視檔案的內容,而只有 Tito 可以刪除檔案。
依使用者身分套用授權規則,可以成長到簿記的夜遊。 較可維護的方法是使用以角色為基礎的授權。 好消息是,我們可用來套用授權規則的工具,與用戶帳戶的角色一樣運作良好。 URL 授權規則可以指定角色,而不是使用者。 LoginView 控件可轉譯已驗證和匿名使用者的不同輸出,可根據登入的使用者角色來顯示不同的內容。 角色 API 包含用來判斷已登入使用者角色的方法。
本教學課程一開始會探討角色架構如何與使用者的角色與其安全性內容產生關聯。 接著會檢查如何套用角色型 URL 授權規則。 接下來,我們將探討如何使用宣告式和程序設計方式來改變所顯示的數據,以及 ASP.NET 頁面所提供的功能。 現在就開始吧!
瞭解角色如何與使用者的安全性內容相關聯
每當要求進入 ASP.NET 管線時,它就會與安全性內容相關聯,其中包含識別要求者的資訊。 使用窗體驗證時,驗證票證會當做身分識別令牌使用。 如我們在窗體驗證概觀教學課程中所討論,負責FormsAuthenticationModule
判斷要求者的身分識別,而要求者會在事件期間AuthenticateRequest
執行此身分識別。
如果找到有效的非過期驗證票證,則會 FormsAuthenticationModule
將其譯碼,以確定要求者的身分識別。 它會建立新的 GenericPrincipal
物件,並將這個 指派給 HttpContext.User
物件。 主體的用途,例如 GenericPrincipal
,是識別已驗證用戶的名稱,以及她所屬的角色。 此目的很明顯,因為所有主體物件都有 Identity
屬性和 IsInRole(roleName)
方法。 FormsAuthenticationModule
不過,對記錄角色資訊不感興趣,而且GenericPrincipal
它建立的物件不會指定任何角色。
如果已啟用角色架構,HTTP 模組會在 RoleManagerModule
之後FormsAuthenticationModule
逐步執行,並在事件期間識別已驗證使用者的角色,這會在事件之後PostAuthenticateRequest
AuthenticateRequest
引發。 如果要求來自已驗證的使用者,則會 RoleManagerModule
覆寫 GenericPrincipal
所 FormsAuthenticationModule
建立的物件,並將它取代為 RolePrincipal
物件。 類別 RolePrincipal
會使用角色 API 來判斷用戶所屬的角色。
圖 1 描述使用窗體驗證和角色架構時的 ASP.NET 管線工作流程。 會 FormsAuthenticationModule
先執行、透過其驗證票證識別使用者,並建立新的 GenericPrincipal
物件。 接下來,中的RoleManagerModule
步驟會以 RolePrincipal
物件覆寫 GenericPrincipal
物件。
如果匿名使用者造訪網站,則和 RoleManagerModule
都不會FormsAuthenticationModule
建立主體物件。
圖 1:使用窗體驗證和角色架構時,已驗證使用者的 ASP.NET 管線事件 (按兩下即可檢視完整大小的映像)
快取 Cookie 中的角色資訊
物件的 RolePrincipal
IsInRole(roleName)
方法會呼叫 Roles
。GetRolesForUser
表示取得使用者的角色,以判斷使用者是否為 roleName 的成員。 使用 SqlRoleProvider
時,這會導致查詢角色存放區資料庫。 使用以角色為基礎的 URL 授權規則時, RolePrincipal
系統會在對受角色型 URL 授權規則保護的頁面的每個要求上呼叫 方法 IsInRole
。 架構不需查閱資料庫中每個要求的角色資訊, Roles
而是包含快取 Cookie 中使用者角色的選項。
如果角色架構設定為快取 Cookie 中的使用者角色,則會 RoleManagerModule
在 ASP.NET 管線 EndRequest
事件期間建立 Cookie。 這個 Cookie 用於 中後續的要求中 PostAuthenticateRequest
,也就是物件建立時 RolePrincipal
。 如果 Cookie 有效且尚未過期,則會剖析 Cookie 中的數據,並用來填入使用者的角色,藉此節省 RolePrincipal
,使其不需要呼叫 Roles
類別來判斷使用者的角色。 圖 2 描述此工作流程。
圖 2:使用者的角色資訊可以儲存在 Cookie 中,以改善效能 (按兩下即可檢視完整大小的影像)
根據預設,角色快取 Cookie 機制已停用。 它可以透過 <roleManager>
中的 Web.config
;組態標記來啟用。 我們已在建立和管理角色教學課程中討論如何使用 <roleManager>
元素來指定角色提供者,因此您應該已在應用程式的 Web.config
檔案中擁有此元素。 角色快取 Cookie 設定會指定為 <roleManager>
的屬性;元素,並摘要於表 1 中。
注意
表格 1 中列出的組態設定會指定結果角色快取 Cookie 的屬性。 如需 Cookie、其運作方式及其各種屬性的詳細資訊,請參閱 此 Cookie 教學課程。
屬性 | 描述 |
---|---|
cacheRolesInCookie |
布爾值,指出是否使用 Cookie 快取。 預設值為 false 。 |
cookieName |
角色快取 Cookie 的名稱。 預設值為 ”ASPXROLES”。 |
cookiePath |
角色名稱Cookie的路徑。 path 屬性可讓開發人員將 Cookie 的範圍限制在特定目錄階層中。 默認值為 “/”,通知瀏覽器將驗證票證 Cookie 傳送至對網域提出的任何要求。 |
cookieProtection |
指出用來保護角色快取 Cookie 的技術。 允許的值為: All (預設) ; Encryption ; None 和 Validation .md) |
| cookieRequireSSL
|布爾值,指出是否需要 SSL 連線才能傳輸驗證 Cookie。 默認值為 false cookieSlidingExpiration false createPersistentCookie true cookieTimeout | Specifies the time, in minutes, after which the authentication ticket cookie expires. The default value is
30. This value is only pertinent when
createPersistentCookie true. | |
createPersistentCookie false(the default), a session cookie is used, which is deleted when the browser is closed. If
true, a persistent cookie is used; it expires
cookieTimeoutnumber of minutes after it has been created or after the previous visit, depending on the value of
cookieSlidingExpiration domainattribute, setting it to "yourdomain.com". | |
| Specifies the cookie's domain value. The default value is an empty string, which causes the browser to use the domain from which it was issued (such as www.yourdomain.com). In this case, the cookie will <strong>not</strong> be sent when making requests to subdomains, such as admin.yourdomain.com. If you want the cookie to be passed to all subdomains you need to customize the
maxCachedResults | Specifies the maximum number of role names that are cached in the cookie. The default is 25. The
RoleManagerModuledoes not create a cookie for users that belong to more than
maxCachedResultsroles. Consequently, the
RolePrincipalobject's
IsInRolemethod will use the
Rolesclass to determine the user's roles. The reason
maxCachedResults | A Boolean value that indicates whether the cookie's timeout is reset each time the user visits the site during a single session. The default value is
. This value is only pertinent when
. | |
exists is because many user agents do not permit cookies larger than 4,096 bytes. So this cap is meant to reduce the likelihood of exceeding this size limitation. If you have extremely long role names, you may want to consider specifying a smaller
| A Boolean value that specifies whether the role cache cookie is a session cookie or persistent cookie. If
. | |
is set to
. | |
is set to
maxCachedResults' 值;相反地,如果您有極短的角色名稱,可能會增加此值。 |
表 1:角色快取 Cookie 組態選項
讓我們將應用程式設定為使用非持續性角色快取 Cookie。 若要完成這項作業,請將 <roleManager>
中的 Web.config
專案更新為包含下列 Cookie 相關屬性:
<roleManager enabled="true"
defaultProvider="SecurityTutorialsSqlRoleProvider"
cacheRolesInCookie="true"
createPersistentCookie="false"
cookieProtection="All">
<providers>
...
</providers>
</roleManager>
我藉由新增三個屬性來更新 <roleManager>
; 元素: cacheRolesInCookie
、 createPersistentCookie
和 cookieProtection
。 藉由將 設定 cacheRolesInCookie
為 true
, RoleManagerModule
現在會自動快取 Cookie 中使用者的角色,而不必查閱每個要求的使用者角色資訊。 我明確將 和 cookieProtection
屬性分別設定createPersistentCookie
為 false
和 All
。 在技術上,我不需要為這些屬性指定值,因為我剛將其指派給預設值,但我在這裡放置它們,以明確說明我不是使用持續性 Cookie,而且 Cookie 同時經過加密和驗證。
就是這麼簡單! 因此,角色架構會在 Cookie 中快取使用者的角色。 如果使用者的瀏覽器不支援 Cookie,或是其 Cookie 遭到刪除或遺失,則不大 RolePrincipal
不然,物件只會在沒有 Cookie (或無效或過期的 Cookie 的情況下使用 Roles
類別,) 。
注意
Microsoft 的模式 & 實務群組不建議使用持續性角色快取 Cookie。 由於擁有角色快取 Cookie 就足以證明角色成員資格,如果駭客可以某種方式取得有效使用者 Cookie 的存取權,他就可以模擬該使用者。 如果 Cookie 保存在使用者的瀏覽器上,就會增加發生這種情況的可能性。 如需此安全性建議和其他安全性考慮的詳細資訊,請參閱 ASP.NET 2.0 的安全性問題清單。
步驟 1:定義 Role-Based URL 授權規則
如使用者型授權教學課程所述,URL 授權提供一種方法,可限制使用者或依角色存取一組頁面。 URL 授權規則會使用 具有 <allow>
和 <deny>
子元素的 <authorization>
元素來拼字Web.config
。 除了先前教學課程中討論的使用者相關授權規則之外,每個 <allow>
和 <deny>
子元素也可以包括:
- 特定角色
- 以逗號分隔的角色清單
例如,URL 授權規則會將存取權授與系統管理員和監督員角色中的這些使用者,但拒絕所有其他使用者的存取權:
<authorization>
<allow roles="Administrators, Supervisors" />
<deny users="*" />
</authorization>
<allow>
上述標記中的專案指出允許系統管理員和監督員角色;<deny>
; 元素會指示所有使用者遭到拒絕。
讓我們設定應用程式,讓 ManageRoles.aspx
、 UsersAndRoles.aspx
和 CreateUserWizardWithRoles.aspx
頁面只能供系統管理員角色中的那些使用者存取,而 RoleBasedAuthorization.aspx
頁面仍可供所有訪客存取。
若要達成此目的,請從將檔案新增 Web.config
至 Roles
資料夾開始。
圖 3:將檔案 Web.config
新增至 Roles
目錄, (按兩下即可檢視大小完整的映像)
接下來,將下列組態標記新增至 Web.config
:
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow roles="Administrators" />
<deny users="*"/>
</authorization>
</system.web>
<!-- Allow all users to visit RoleBasedAuthorization.aspx -->
<location path="RoleBasedAuthorization.aspx">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
</configuration>
區 <authorization>
段中的 <system.web>
元素表示只有 Administrators 角色中的使用者可以存取目錄中 ASP.NET 資源 Roles
。 元素 <location>
會定義頁面的 RoleBasedAuthorization.aspx
一組替代 URL 授權規則,讓所有使用者都能瀏覽頁面。
將變更儲存至 Web.config
之後,以不是系統管理員角色的使用者身分登入,然後嘗試流覽其中一個受保護的頁面。 UrlAuthorizationModule
會偵測到您沒有造訪要求資源的許可權;因此,FormsAuthenticationModule
會將您重新導向至登入頁面。 登入頁面會接著將您重新導向至 UnauthorizedAccess.aspx
頁面, (請參閱圖 4) 。 最後從登入頁面重新導向,UnauthorizedAccess.aspx
因為我們在使用者型授權教學課程的步驟 2 中新增至登入頁面的程式碼而發生。 特別是,如果 querystring 包含ReturnUrl
參數,登入頁面會自動將任何已驗證的使用者重新導向至 UnauthorizedAccess.aspx
,因為此參數表示用戶在嘗試檢視未獲授權檢視頁面之後到達登入頁面。
圖 4:只有系統管理員角色中的使用者可以檢視受保護的頁面, (按兩下即可檢視完整大小的映像)
註銷,然後以系統管理員角色中的使用者身分登入。 現在您應該能夠檢視三個受保護的頁面。
圖 5:Tito 可以瀏覽 UsersAndRoles.aspx
頁面,因為他位於系統管理員角色中, (按兩下即可檢視完整大小的映像)
注意
針對角色或使用者指定 URL 授權規則時,請務必牢記規則一次分析一次,從上而下。 一旦找到相符專案,使用者就會被授與或拒絕存取權,視在 或 <deny>
專案中找到<allow>
相符專案而定。 如果找不到相符專案,則會將存取權授與使用者。 因此,如果您想要限制對一或多個用戶帳戶的存取,您必須使用 元素作為URL授權組態中的最後一個專案 <deny>
。 If your URL authorization rules do not include a<deny>
element, all users will be granted access. For a more thorough discussion on how the URL authorization rules are analyzed, refer back to the "A Look at How the UrlAuthorizationModule
Uses the Authorization Rules to Grant or Deny Access" section of the User-Based Authorization tutorial.
步驟 2:根據目前登入的使用者角色限制功能
URL 授權可讓您輕鬆地指定粗略的授權規則,以指出允許的身分識別,以及哪些識別遭到拒絕,而無法檢視特定頁面 (或資料夾及其子資料夾中的所有頁面) 。 不過,在某些情況下,我們可能會允許所有使用者瀏覽頁面,但會根據瀏覽使用者的角色來限制頁面的功能。 這可能需要根據使用者的角色顯示或隱藏數據,或為屬於特定角色的使用者提供其他功能。
這類精細的角色型授權規則可以宣告式或以程式設計方式實作 (,或透過兩個) 的一些組合來實作。 在下一節中,我們將瞭解如何透過LoginView控件實作宣告式精細授權。 接下來,我們將探索程式設計技術。 不過,在我們可以查看套用細微授權規則之前,我們必須先建立一個頁面,其功能取決於流覽者的角色。
讓我們建立一個頁面,以列出 GridView 系統中的所有用戶帳戶。 GridView 將包含每個使用者的使用者名稱、電子郵件位址、上次登入日期,以及使用者的相關批注。 除了顯示每個用戶的資訊之外,GridView 還包含編輯和刪除功能。 我們一開始會建立此頁面,其中包含所有使用者可用的編輯和刪除功能。 在 [使用 LoginView 控件] 和 [以程序設計方式限制功能] 區段中,我們將瞭解如何根據造訪使用者的角色來啟用或停用這些功能。
注意
我們即將建置的 [ASP.NET] 頁面會使用 GridView 控件來顯示使用者帳戶。 由於本教學課程系列著重於窗體驗證、授權、用戶帳戶和角色,我不想花太多時間討論 GridView 控件的內部工作。 雖然本教學課程提供設定此頁面的特定逐步指示,但不會深入探討為何進行特定選擇的原因,或轉譯輸出上特定屬性有何影響。 如需 GridView 控件的完整檢查,請參閱在 ASP.NET 2.0 教學課程系列中使用數據。
首先, RoleBasedAuthorization.aspx
開啟資料夾中的頁面 Roles
。 將 GridView 從頁面拖曳到 Designer,並將其設定ID
為 UserGrid
。 在一段時間后,我們將撰寫呼叫 的程序 Membership
代碼。GetAllUsers
方法,並將產生的 MembershipUserCollection
對象系結至 GridView。 MembershipUserCollection
包含MembershipUser
系統中每個用戶帳戶的物件;MembershipUser
物件具有的屬性,例如 UserName
,Email
LastLoginDate
等等。
在撰寫將使用者帳戶系結至方格的程序代碼之前,讓我們先定義 GridView 的字段。 從 GridView 的智慧標記中,按兩下 [編輯資料行] 連結以啟動 [欄位] 對話框, (請參閱圖 6) 。 從這裡取消核取左下角的 [自動產生字段] 複選框。 由於我們希望此 GridView 包含編輯和刪除功能,因此請新增 CommandField 並將其 和 ShowDeleteButton
屬性設定ShowEditButton
為 True。 接下來,新增四個字段以顯示 UserName
、 Email
、 LastLoginDate
和 Comment
屬性。 針對兩個只讀屬性使用 BoundField, (UserName
和 LastLoginDate
) 和 TemplateFields 用於兩個可編輯字段 (Email
和 Comment
) 。
讓第一個 BoundField 顯示 UserName
屬性;將其 HeaderText
和 DataField
屬性設定為 “UserName”。 此欄位無法編輯,因此將其 ReadOnly
屬性設定為 True。 將 LastLoginDate
BoundField 設定為 “Last Login”,並將其DataField
設定HeaderText
為 “LastLoginDate”。 讓我們格式化這個 BoundField 的輸出,以便只顯示日期 (,而不是日期和時間) 。 若要完成這項作業,請將這個 BoundField 的 HtmlEncode
屬性設定為 False,並將其 DataFormatString
屬性設定為 “{0:d}”。 同時將 ReadOnly
屬性設定為 True。
將HeaderText
兩個 TemplateFields 的屬性設定為 「Email」 和 「Comment」。。
圖 6:GridView 的欄位可以透過 [字段] 對話框設定 (按兩下即可檢視大小完整的影像)
我們現在需要定義 ItemTemplate
EditItemTemplate
「Email」 和 「Comment」 TemplateFields 的 和 。 將標籤 Web 控制項新增至每個 ItemTemplates
控制項,並分別將其 Text
屬性系結至 Email
和 Comment
屬性。
針對 「Email」 TemplateField,新增名為 Email
的 TextBox,EditItemTemplate
並使用Text
雙向數據系結將其屬性系結至 Email
屬性。 將 RequiredFieldValidator 和 RegularExpressionValidator 新增至 EditItemTemplate
,以確保訪客編輯 Email 屬性已輸入有效的電子郵件位址。 針對 「Comment」 TemplateField,將名為的多 Comment
行 TextBox 新增至其 EditItemTemplate
。 分別將 TextBox 的 Columns
和 Rows
屬性設定為 40 和 4,然後使用雙向數據系結將其 Text
屬性系結至 Comment
屬性。
設定這些 TemplateFields 之後,其宣告式標記看起來應該如下所示:
<asp:TemplateField HeaderText="Email">
<ItemTemplate>
<asp:Label runat="server" ID="Label1" Text='<%# Eval("Email")%>'></asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox runat="server" ID="Email" Text='<%# Bind("Email")%>'></asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server"
ControlToValidate="Email" Display="Dynamic"
ErrorMessage="You must provide an email address."
SetFocusOnError="True">*</asp:RequiredFieldValidator>
<asp:RegularExpressionValidator ID="RegularExpressionValidator1" runat="server"
ControlToValidate="Email" Display="Dynamic"
ErrorMessage="The email address you have entered is not valid. Please fix
this and try again."
SetFocusOnError="True"
ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">*
</asp:RegularExpressionValidator>
</EditItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Comment">
<ItemTemplate>
<asp:Label runat="server" ID="Label2" Text='<%# Eval("Comment")%>'></asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox runat="server" ID="Comment" TextMode="MultiLine"
Columns="40" Rows="4" Text='<%# Bind("Comment")%>'>
</asp:TextBox>
</EditItemTemplate>
</asp:TemplateField>
編輯或刪除使用者帳戶時,我們必須知道用戶的 UserName
屬性值。 將 GridView 的 DataKeyNames
屬性設定為 “UserName”,以便透過 GridView 的 DataKeys
集合取得這項資訊。
最後,將 ValidationSummary 控件新增至頁面,並將其屬性設定為 True,並將其ShowSummary
屬性設定ShowMessageBox
為 False。 使用這些設定時,如果使用者嘗試編輯缺少或無效電子郵件地址的用戶帳戶,ValidationSummary 就會顯示用戶端警示。
<asp:ValidationSummary ID="ValidationSummary1"
runat="server"
ShowMessageBox="True"
ShowSummary="False" />
我們現在已完成此頁面的宣告式標記。 下一個工作是將用戶帳戶集系結至 GridView。 將名為 BindUserGrid
的方法新增至RoleBasedAuthorization.aspx
頁面的程式代碼後置類別,將 傳Membership.GetAllUsers
回的 系結MembershipUserCollection
至 UserGrid
GridView。 從 Page_Load
第一頁流覽的事件處理程式呼叫這個方法。
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
BindUserGrid()
End If
End Sub
Private Sub BindUserGrid()
Dim allUsers As MembershipUserCollection = Membership.GetAllUsers()
UserGrid.DataSource = allUsers
UserGrid.DataBind()
End Sub
有了此程式代碼,請透過瀏覽器瀏覽頁面。 如圖 7 所示,您應該會看到 GridView 列出系統中每個使用者帳戶的相關信息。
圖 7: UserGrid
GridView 列出系統中每個使用者的相關信息 (按兩下即可檢視大小完整的映像)
注意
UserGrid
GridView 會列出非分頁介面中的所有使用者。 這個簡單的方格介面不適用於有數十位或更多使用者的案例。 其中一個選項是設定 GridView 以啟用分頁。 方法 Membership.GetAllUsers
有兩個多載:一個不接受任何輸入參數,並傳回所有使用者,另一個會接受頁面索引和頁面大小的整數值,並只傳回使用者的指定子集。 第二個多載可用來更有效率地逐頁瀏覽使用者,因為它只會傳回用戶帳戶的精確子集,而不是 全部 。 如果您有數千個用戶帳戶,您可能會想要考慮篩選型介面,其中一個介面只會顯示 UserName 以選取字元開頭的使用者,例如。 此方法Membership.FindUsersByName
非常適合用來建置篩選式使用者介面。 我們將在未來的教學課程中探討如何建置這類介面。
當控件系結至正確設定的數據源控件時,GridView 控件提供內建編輯和刪除支援,例如 SqlDataSource 或 ObjectDataSource。 UserGrid
不過,GridView 會以程式設計方式系結其數據;因此,我們必須撰寫程式代碼來執行這兩項工作。 特別是,我們需要為 GridView 的 RowEditing
、 RowCancelingEdit
、 RowUpdating
和 RowDeleting
事件建立事件處理程式,當訪客按兩下 GridView 的 [編輯]、[取消]、[更新] 或 [刪除] 按鈕時引發。
首先,為 GridView 的 RowEditing
、 RowCancelingEdit
和 RowUpdating
事件建立事件處理程式,然後新增下列程式代碼:
Protected Sub UserGrid_RowEditing(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewEditEventArgs) Handles UserGrid.RowEditing
' Set the grid's EditIndex and rebind the data
UserGrid.EditIndex = e.NewEditIndex
BindUserGrid()
End Sub
Protected Sub UserGrid_RowCancelingEdit(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCancelEditEventArgs) Handles UserGrid.RowCancelingEdit
' Revert the grid's EditIndex to -1 and rebind the data
UserGrid.EditIndex = -1
BindUserGrid()
End Sub
Protected Sub UserGrid_RowUpdating(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs) Handles UserGrid.RowUpdating
' Exit if the page is not valid
If Not Page.IsValid Then
Exit Sub
End If
' Determine the username of the user we are editing
Dim UserName As String = UserGrid.DataKeys(e.RowIndex).Value.ToString()
' Read in the entered information and update the user
Dim EmailTextBox As TextBox = CType(UserGrid.Rows(e.RowIndex).FindControl("Email"),TextBox)
Dim CommentTextBox As TextBox= CType(UserGrid.Rows(e.RowIndex).FindControl("Comment"),TextBox)
' Return information about the user
Dim UserInfo As MembershipUser = Membership.GetUser(UserName)
' Update the User account information
UserInfo.Email = EmailTextBox.Text.Trim()
UserInfo.Comment = CommentTextBox.Text.Trim()
Membership.UpdateUser(UserInfo)
' Revert the grid's EditIndex to -1 and rebind the data
UserGrid.EditIndex = -1
BindUserGrid()
End Sub
RowEditing
和 RowCancelingEdit
事件處理程式只會設定 GridView 的 EditIndex
屬性,然後將使用者帳戶清單重新繫結至方格。 有趣的內容會在事件處理程式中 RowUpdating
發生。 此事件處理程式會從確保數據有效開始,然後從DataKeys
集合擷取UserName
已編輯用戶帳戶的值。 Email
然後,會以程式設計方式參考兩個 TemplateFields 中的 EditItemTemplate
和 Comment
TextBox。 其 Text
屬性包含已編輯的電子郵件位址和批注。
為了透過成員資格 API 更新使用者帳戶,我們需要先取得使用者的信息,我們會透過呼叫 Membership.GetUser(userName)
來執行。 然後,傳回 MembershipUser
物件的 Email
和 Comment
屬性會以從編輯介面輸入至兩個 TextBox 中的值來更新。 最後,這些修改會儲存,並呼叫 Membership.UpdateUser
。 事件處理程式會 RowUpdating
藉由將 GridView 還原為其預先編輯介面來完成。
接下來,建立 RowDeleting
RowDeleting 事件處理程式,然後新增下列程式代碼:
Protected Sub UserGrid_RowDeleting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewDeleteEventArgs) Handles UserGrid.RowDeleting
' Determine the username of the user we are editing
Dim UserName As String = UserGrid.DataKeys(e.RowIndex).Value.ToString()
' Delete the user
Membership.DeleteUser(UserName)
' Revert the grid's EditIndex to -1 and rebind the data
UserGrid.EditIndex = -1
BindUserGrid()
End Sub
上述事件處理程式會從 GridView 的DataKeys
集合中擷取UserName
值開始;然後這個UserName
值會傳遞至 Membership 類別的DeleteUser
方法。 方法 DeleteUser
會從系統刪除用戶帳戶,包括相關的成員資格數據 (,例如此用戶所屬的角色) 。 刪除用戶之後,方格的 EditIndex
會設定為 -1 (,以防使用者在另一個數據列處於編輯模式時按兩下 [刪除],) 呼叫 BindUserGrid
方法。
注意
刪除使用者帳戶之前,[刪除] 按鈕不需要用戶進行任何類型的確認。 建議您新增某種形式的使用者確認,以減少意外刪除帳戶的機會。 確認動作的最簡單方式之一是透過用戶端確認對話方塊。 如需這項技術的詳細資訊,請參閱 刪除時新增 Client-Side 確認。
確認此頁面如預期般運作。 您應該能夠編輯任何使用者的電子郵件位址和批註,以及刪除任何用戶帳戶。 RoleBasedAuthorization.aspx
由於頁面可供所有使用者存取,任何使用者甚至是匿名訪客都可以瀏覽此頁面,並編輯和刪除用戶帳戶! 讓我們更新此頁面,讓監督員和系統管理員角色中的使用者只能編輯使用者的電子郵件位址和批注,而且只有系統管理員可以刪除用戶帳戶。
[使用 LoginView 控件] 區段會查看使用 LoginView 控件來顯示使用者角色的特定指示。 如果系統管理員角色中的人員造訪此頁面,我們將示範如何編輯和刪除使用者的指示。 如果監督員角色中的使用者到達此頁面,我們會顯示編輯使用者的指示。 如果訪客是匿名的,或不是處於監督員或系統管理員角色,我們會顯示一則訊息,說明他們無法編輯或刪除使用者帳戶資訊。 在 [以程序設計方式限制功能] 區段中,我們將撰寫程式代碼,以程式設計方式顯示或隱藏使用者角色的 [編輯] 和 [刪除] 按鈕。
使用 LoginView 控件
如過去教學課程中所見,LoginView 控件對於顯示已驗證和匿名使用者的不同介面很有用,但 LoginView 控件也可以用來根據使用者的角色來顯示不同的標記。 讓我們使用 LoginView 控件,根據瀏覽使用者的角色來顯示不同的指示。
首先,在 GridView 上方 UserGrid
新增 LoginView。 如先前所述,LoginView 控制件有兩個內建範本: AnonymousTemplate
和 LoggedInTemplate
。 在這兩個範本中輸入簡短訊息,通知使用者他們無法編輯或刪除任何用戶資訊。
<asp:LoginView ID="LoginView1" runat="server">
<LoggedInTemplate>
You are not a member of the Supervisors or Administrators roles. Therefore you
cannot edit or delete any user information.
</LoggedInTemplate>
<AnonymousTemplate>
You are not logged into the system. Therefore you cannot edit or delete any user
information.
</AnonymousTemplate>
</asp:LoginView>
除了 AnonymousTemplate
和 LoggedInTemplate
之外,LoginView 控制項也可以包含 RoleGroups,也就是角色特定的範本。 每個 RoleGroup 都包含單一屬性, Roles
這個屬性會指定 RoleGroup 所套用的角色。 Roles
屬性可以設定為單一角色 (,例如 「系統管理員」) 或以逗號分隔的角色清單 (,例如「系統管理員、監督員」) 。
若要管理 RoleGroups,請按下控件智慧標記中的 [編輯 RoleGroups] 連結,以顯示 RoleGroup 集合編輯器。 新增兩個新的 RoleGroup。 將第一個 RoleGroup 的 Roles
屬性設定為 “Administrators”,並將第二個 屬性設定為 “Supervisors”。
圖 8:透過 RoleGroup 集合編輯器管理 LoginView 的 Role-Specific 範本, (按兩下即可檢視大小完整的映像)
按兩下 [確定] 以關閉 RoleGroup 集合編輯器;這會更新 LoginView 的宣告式標記,以包含 <RoleGroups>
RoleGroup 集合編輯器中定義之每個 RoleGroup 子元素的區段 <asp:RoleGroup>
。 此外,LoginView 智慧標記中的 [檢視] 下拉式清單一開始只 AnonymousTemplate
列出 和 LoggedInTemplate
,現在也包含新增的 RoleGroups。
編輯 RoleGroups,讓監督員角色中的用戶會顯示如何編輯使用者帳戶的指示,而系統管理員角色中的使用者則會顯示編輯和刪除的指示。 進行這些變更之後,您的 LoginView 宣告式標記看起來應該如下所示。
<asp:LoginView ID="LoginView1" runat="server">
<RoleGroups>
<asp:RoleGroup Roles="Administrators">
<ContentTemplate>
As an Administrator, you may edit and delete user accounts.
Remember: With great power comes great responsibility!
</ContentTemplate>
</asp:RoleGroup>
<asp:RoleGroup Roles="Supervisors">
<ContentTemplate>
As a Supervisor, you may edit users' Email and Comment information.
Simply click the Edit button, make your changes, and then click Update.
</ContentTemplate>
</asp:RoleGroup>
</RoleGroups>
<LoggedInTemplate>
You are not a member of the Supervisors or Administrators roles.
Therefore you cannot edit or delete any user information.
</LoggedInTemplate>
</AnonymousTemplate>
You are not logged into the system.
Therefore you cannot edit or delete any user information.
</AnonymousTemplate>
</asp:LoginView>
進行這些變更之後,請儲存頁面,然後瀏覽瀏覽器。 首先,以匿名使用者身分瀏覽頁面。 您應該會顯示訊息:「您未登入系統。 因此,您無法編輯或刪除任何使用者資訊。」然後以已驗證的使用者身分登入,但不是在監督員或系統管理員角色中登入的使用者。 這次您應該會看到訊息:「您不是監督員或系統管理員角色的成員。 因此,您無法編輯或刪除任何使用者資訊。」
接下來,以屬於監督員角色成員的使用者身分登入。 這次您應該會看到監督員角色特定訊息, (請參閱圖 9) 。 如果您以系統管理員角色的使用者身分登入,您應該會看到系統管理員角色特定訊息 (請參閱圖 10) 。
圖 9:暴力密碼顯示監督員 Role-Specific 訊息 (按兩下即可檢視大小完整的影像)
圖 10:Tito 顯示系統管理員 Role-Specific 訊息 (按鍵即可檢視完整大小的映像)
如圖 9 和 10 所示的螢幕快照,LoginView 只會轉譯一個範本,即使套用多個範本也一樣。 Bruce 和 Tito 都登入使用者,但 LoginView 只會轉譯相符的 RoleGroup,而不是 LoggedInTemplate
。 此外,Tito 同時屬於系統管理員和監督員角色,但 LoginView 控件會轉譯 Administrators 角色特定範本,而不是主管角色。
圖 11 說明 LoginView 控制項用來判斷要呈現之範本的工作流程。 請注意,如果指定多個 RoleGroup,LoginView 範本會轉譯符合 的第一個 RoleGroup。 換句話說,如果我們將監督員 RoleGroup 放在第一個 RoleGroup,而系統管理員則是第二個角色群組,則當 Tito 瀏覽此頁面時,他會看到監督員訊息。
圖 11:LoginView 控件的工作流程,用來判斷要呈現的範本 (按兩下即可檢視大小完整的影像)
以程序設計方式限制功能
雖然 LoginView 控件會根據瀏覽頁面的使用者角色顯示不同的指示,但所有使用者仍可看見 [編輯] 和 [取消] 按鈕。 我們需要以程式設計方式隱藏匿名訪客和不在主管或系統管理員角色中的 [編輯] 和 [刪除] 按鈕。 我們需要為不是系統管理員的每個人隱藏 [刪除] 按鈕。 為了達成此目的,我們將撰寫一些程式代碼,以程式設計方式參考 CommandField 的 Edit 和 Delete LinkButtons,並視需要將其 Visible
屬性設定為 False
。
在 CommandField 中以程式設計方式參考控件的最簡單方式,就是先將其轉換成範本。 若要完成此動作,請按兩下 GridView 智慧標記中的 [編輯資料行] 連結,從目前字段清單中選取 CommandField,然後按兩下 [將此欄位轉換成 TemplateField] 連結。 這會將 CommandField 轉換成具有 和 EditItemTemplate
的 TemplateFieldItemTemplate
。 ItemTemplate
包含 Edit 和 Delete LinkButtons,同時EditItemTemplate
裝載 Update 和 Cancel LinkButtons。
圖 12:將 CommandField 轉換成 TemplateField (按兩下即可檢視大小完整的影像)
更新 中的ItemTemplate
Edit和Delete LinkButtons,將其ID
屬性分別設定為和DeleteButton
的值EditButton
。
<asp:TemplateField ShowHeader="False">
<EditItemTemplate>
<asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="True"
CommandName="Update" Text="Update"></asp:LinkButton>
<asp:LinkButton ID="LinkButton2" runat="server" CausesValidation="False"
CommandName="Cancel" Text="Cancel"></asp:LinkButton>
</EditItemTemplate>
<ItemTemplate>
<asp:LinkButton ID="EditButton" runat="server" CausesValidation="False"
CommandName="Edit" Text="Edit"></asp:LinkButton>
<asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False"
CommandName="Delete" Text="Delete"></asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
每當數據系結至 GridView 時,GridView 就會列舉其 DataSource
屬性中的記錄,併產生對應的 GridViewRow
物件。 建立每個 GridViewRow
物件時,就會 RowCreated
引發 事件。 為了隱藏未經授權使用者的 [編輯] 和 [刪除] 按鈕,我們需要為此事件建立事件處理程式,並以程式設計方式參考 Edit 和 Delete LinkButtons,據以設定其 Visible
屬性。
建立 事件的事件處理程式 RowCreated
,然後新增下列程序代碼:
Protected Sub UserGrid_RowCreated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles UserGrid.RowCreated
If e.Row.RowType = DataControlRowType.DataRow AndAlso e.Row.RowIndex <> UserGrid.EditIndex Then
' Programmatically reference the Edit and Delete LinkButtons
Dim EditButton As LinkButton = CType(e.Row.FindControl("EditButton"), LinkButton)
Dim DeleteButton As LinkButton = CType(e.Row.FindControl("DeleteButton"), LinkButton)
EditButton.Visible = (User.IsInRole("Administrators") OrElse User.IsInRole("Supervisors"))
DeleteButton.Visible = User.IsInRole("Administrators")
End If
End Sub
請記住,所有 RowCreated
GridView 數據列的事件都會引發,包括頁首、頁尾、呼叫器介面等等。 如果我們處理的數據列不在編輯模式中,我們只想以程式設計方式參考Edit和Delete LinkButtons (,因為編輯模式中的數據列具有 [更新] 和 [取消] 按鈕,而不是 [編輯] 和 [刪除]) 。 此檢查是由 If
語句處理。
如果我們處理的數據列不是處於編輯模式,則會參考Edit和Delete LinkButtons,而且其Visible
屬性是根據物件IsInRole(roleName)
方法所User
傳回的布爾值來設定。 對象 User
會參考 所建立的 RoleManagerModule
主體,因此, IsInRole(roleName)
方法會使用 Roles API 來判斷目前訪客是否屬於 roleName。
注意
我們可以直接使用 Roles 類別,將呼叫取代為 方法的呼叫User.IsInRole(roleName)
Roles.IsUserInRole(roleName)
。 我決定在此範例中使用主體物件的 IsInRole(roleName)
方法,因為它比直接使用角色 API 更有效率。 稍早在本教學課程中,我們已將角色管理員設定為快取 Cookie 中的使用者角色。 只有在呼叫主體的 方法時,才會使用這個快取的 IsInRole(roleName)
Cookie數據;直接呼叫角色 API 一律牽涉到角色存放區的行程。 即使角色未在 Cookie 中快取,呼叫主體物件的 IsInRole(roleName)
方法通常更有效率,因為它會在要求期間第一次呼叫時快取結果。 另一方面,角色 API 不會執行任何快取。 由於針對 RowCreated
GridView 中的每個數據列引發事件一次,因此使用 User.IsInRole(roleName)
只會牽涉到角色存放區的一次行程,而 Roles.IsUserInRole(roleName)
需要 N 次車程,其中 N 是網格線中顯示的使用者帳戶數目。
如果瀏覽此頁面的使用者位於 Administrators 或 Supervisors 角色中,[編輯] 按鈕的 Visible
屬性會設定 True
為 ,否則會設定為 False
。 只有在使用者處於 Administrators 角色時,[刪除] 按鈕的 Visible
屬性才會設定 True
為 。
透過瀏覽器測試此頁面。 如果您以匿名訪客身分造訪頁面,或身為不是監督員或系統管理員的使用者,CommandField 是空的;它仍然存在,但做為精簡的斜條,沒有 [編輯] 或 [刪除] 按鈕。
注意
當非監督員和非系統管理員瀏覽頁面時,可能會完全隱藏 CommandField。 我將此保留為讀者的練習。
圖 13:非監督員和非系統管理員的編輯和刪除按鈕是隱藏的, (按兩下即可檢視大小完整的影像)
如果屬於監督員角色的使用者 (但不是系統管理員角色) 造訪,他只會看到 [編輯] 按鈕。
圖 14:當 [編輯] 按鈕可供監督員使用時,[刪除] 按鈕會隱藏 (按兩下即可檢視大小完整的影像)
如果系統管理員造訪,她可以同時存取 [編輯] 和 [刪除] 按鈕。
圖 15:[編輯] 和 [刪除] 按鈕僅適用於系統管理員, (按兩下即可檢視大小完整的映像)
步驟 3:將 Role-Based 授權規則套用至類別和方法
在步驟 2 中,我們會將編輯功能限制為主管和系統管理員角色中的使用者,並僅將功能刪除給系統管理員。 這是透過程式設計技術隱藏未經授權的使用者相關聯的使用者介面元素來完成的。 這類量值不保證未經授權的使用者將無法執行特殊許可權動作。 可能有稍後新增的使用者介面元素,或我們忘記為未經授權的用戶隱藏。 或者,駭客可能會發現一些其他方法,以取得 ASP.NET 頁面以執行所需的方法。
確保未經授權的用戶無法存取特定功能片段的簡單方式,就是使用 PrincipalPermission
屬性裝飾該類別或方法。 當 .NET 運行時間使用類別或執行其中一個方法時,它會檢查以確保目前的安全性內容具有許可權。 屬性 PrincipalPermission
提供一種機制,讓我們可以定義這些規則。
我們已在使用者型授權教學課程中查看使用 PrincipalPermission
屬性。 具體來說,我們已瞭解如何裝飾 GridView 的 SelectedIndexChanged
和 RowDeleting
事件處理程式,讓它們只能由已驗證的使用者和 Tito 分別執行。 屬性 PrincipalPermission
也適用於角色。
讓我們示範如何在 PrincipalPermission
GridView RowUpdating
和 RowDeleting
事件處理程式上使用 屬性,禁止對非授權的用戶執行。 我們只需要在每個函式定義上新增適當的屬性:
<PrincipalPermission(SecurityAction.Demand, Role:="Administrators")>_
<PrincipalPermission(SecurityAction.Demand, Role:="Supervisors")>_
Protected Sub UserGrid_RowUpdating(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs) Handles UserGrid.RowUpdating
...
End Sub
<PrincipalPermission(SecurityAction.Demand, Role:="Administrators")>_
Protected Sub UserGrid_RowDeleting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewDeleteEventArgs) Handles UserGrid.RowDeleting
...
End Sub
事件處理程式的 屬性 RowUpdating
會指示只有 Administrators 或 Supervisors 角色中的使用者可以執行事件處理程式,因為事件處理程式上的 RowDeleting
屬性會將執行限制為 Administrators 角色中的使用者。
注意
屬性 PrincipalPermission
會以 命名空間中的 System.Security.Permissions
類別表示。 請務必在程式代碼後置類別檔案頂端新增 Imports System.Security.Permissions
語句,以匯入此命名空間。
如果非系統管理員嘗試執行 RowDeleting
事件處理程式,或是非監督員或非系統管理員嘗試執行 RowUpdating
事件處理程式,則 .NET 執行時間會引發 SecurityException
。
圖 16:如果安全性內容未獲授權執行方法, SecurityException
則會擲回 (按兩下即可檢視大小完整的映射)
除了 ASP.NET 頁面之外,許多應用程式也有包含各種層級的架構,例如商業規則和數據存取層。 這些層通常會實作為類別庫,並提供類別和方法來執行商業規則和數據相關功能。 屬性 PrincipalPermission
也適用於將授權規則套用至這些層。
如需使用 PrincipalPermission
屬性定義類別和方法授權規則的詳細資訊,請參閱 Scott Guthrie 的部落格文章:使用 PrincipalPermissionAttributes
將授權規則新增至商務和數據層。
摘要
在本教學課程中,我們探討如何根據使用者的角色指定粗略和精細的授權規則。 Asp。NET 的 URL 授權功能可讓頁面開發人員指定允許或拒絕存取哪些頁面的身分識別。 如我們在使用者型授權教學課程中所見,URL 授權規則可以依使用者方式套用。 如本教學課程的步驟 1 中所見,它們也可以依角色依角色套用。
更精細的授權規則可以宣告方式或以程序設計方式套用。 在步驟 2 中,我們已探討如何使用 LoginView 控件的 RoleGroups 功能,根據瀏覽使用者的角色來呈現不同的輸出。 我們也探討如何以程式設計方式判斷使用者是否屬於特定角色,以及如何據以調整頁面的功能。
快樂的程序設計!
深入閱讀
如需本教學課程中討論之主題的詳細資訊,請參閱下列資源:
- 使用 將授權規則新增至商務和數據層
PrincipalPermissionAttributes
- 檢查 ASP.NET 2.0 的成員資格、角色和配置檔:使用角色
- ASP.NET 2.0 的安全性問題清單
- 元素的技術
<roleManager>
檔
關於作者
Scott Mitchell 是多個 ASP/ASP.NET 書籍的作者,且 4GuysFromRolla.com 的作者,自 1998 年以來,已與 Microsoft Web 技術合作。 Scott 是獨立顧問、訓練員和作者。 他的最新書籍是 Sams 在 24 小時內自行 ASP.NET 2.0。 您可以在 或 透過在 的http://ScottOnWriting.NET部落格連線mitchell@4guysfromrolla.com到 Scott。
特別感謝...
本教學課程系列是由許多實用的檢閱者檢閱。 本教學課程的首席檢閱者包括 Suchi Banerjee 和 Teresa Murphy。 有興趣檢閱即將推出的 MSDN 文章嗎? 如果是,請將一行放在我 mitchell@4GuysFromRolla.com