以角色為基礎的授權 (C#)

作者:Scott Mitchell

注意

本文撰寫之後,ASP.NET 成員資格提供者已被 ASP.NET 身分識別取代。 強烈建議您將應用程式更新為使用 ASP.NET 身分識別 平臺,而不是本文撰寫時精選的成員資格提供者。 ASP.NET 身分識別的一些優點優於 ASP.NET 成員資格系統,包括 :

  • 更好的效能
  • 改善擴充性和可測試性
  • 支援 OAuth、OpenID Connect 和雙因素驗證
  • 宣告型身分識別支援
  • 與 ASP.Net Core 的較佳互操作性

下載程式代碼下載 PDF

本教學課程一開始會探討角色架構如何與使用者的角色與其安全性內容產生關聯。 然後,它會檢查如何套用角色型 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它建立的物件不會指定任何角色。

如果已啟用角色架構,IN RoleManagerModule 模組會在之後FormsAuthenticationModule執行,並在事件期間PostAuthenticateRequest識別已驗證使用者的角色,這會在AuthenticateRequest事件之後引發。 如果要求來自已驗證的使用者,則 會 RoleManagerModule 覆寫 GenericPrincipalFormsAuthenticationModule 建立的物件,並將它取代為 RolePrincipal 物件。 類別 RolePrincipal 會使用角色 API 來判斷用戶所屬的角色。

圖 1 描述使用窗體驗證和角色架構時的 ASP.NET 管線工作流程。 會 FormsAuthenticationModule 先執行、透過其驗證票證識別使用者,並建立新的 GenericPrincipal 物件。 接下來,中的RoleManagerModule步驟會以 RolePrincipal 物件覆寫 GenericPrincipal 物件。

如果匿名使用者造訪網站,則和 RoleManagerModule 都不會FormsAuthenticationModule建立主體物件。

使用窗體驗證和角色架構時,已驗證使用者的 ASP.NET 管線事件

圖 1:使用窗體驗證和角色架構時,已驗證使用者的 ASP.NET 管線事件 (按兩下即可檢視完整大小的映像)

物件的 RolePrincipalIsInRole(roleName) 方法會呼叫 Roles.GetRolesForUser 以取得使用者的角色,以判斷使用者是否為 roleName的成員。 使用 SqlRoleProvider時,這會導致查詢角色存放區資料庫。 使用角色型 URL 授權規則時,RolePrincipalIsInRole系統會在每個要求上呼叫 方法,以存取受角色型 URL 授權規則保護的頁面。 角色架構不需要在每個要求上查閱資料庫中的角色資訊,而是包含快取 Cookie 中使用者角色的選項。

如果角色架構設定為快取 Cookie 中的使用者角色,則會 RoleManagerModule 在 ASP.NET 管線 EndRequest 事件期間建立 Cookie。 這個 Cookie 用於 中的 PostAuthenticateRequest後續要求,也就是建立 物件時 RolePrincipal 。 如果 Cookie 有效且尚未過期,則會剖析 Cookie 中的數據,並用來填入使用者的角色,藉此節省 RolePrincipal 對 類別的呼叫 Roles ,以判斷使用者的角色。 圖 2 描述此工作流程。

使用者的角色資訊可以儲存在 Cookie 中以改善效能

圖 2:使用者的角色資訊可以儲存在 Cookie 中,以改善效能 (按兩下即可檢視完整大小的映像)

根據預設,會停用角色快取 Cookie 機制。 它可以透過中的Web.config<roleManager>態標記來啟用。 我們已在<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; NoneValidation
cookieRequireSSL 布爾值,指出是否需要 SSL 連線才能傳輸驗證 Cookie。 預設值是 false
cookieSlidingExpiration 布爾值,指出每次使用者在單一會話期間瀏覽網站時,是否重設 Cookie 的逾時。 預設值是 false。 只有在 設定為 truecreatePersistentCookie,這個值才相關。
cookieTimeout 指定驗證票證 Cookie 到期的時間,以分鐘為單位。 預設值是 30。 只有在 設定為 truecreatePersistentCookie,這個值才相關。
createPersistentCookie 布爾值,指定角色快取 Cookie 是否為會話 Cookie 或持續性 Cookie。 如果 false (預設) ,則會使用會話 Cookie,這會在關閉瀏覽器時刪除。 如果 true為 ,則會使用永續性 Cookie;它會 cookieTimeout 在建立後或前一次造訪之後到期的分鐘數,視 的值 cookieSlidingExpiration而定。
domain 指定 Cookie 的網域值。 默認值是空字串,這會導致瀏覽器使用其發出網域的網域 (,例如 www.yourdomain.com) 。 在此情況下 ,在對 子域提出要求時,不會傳送Cookie,例如 admin.yourdomain.com。 如果您想要將 Cookie 傳遞至所有子域,您需要自定義 domain 屬性,請將它設定為 「yourdomain.com」。
maxCachedResults 指定 Cookie 中快取的角色名稱數目上限。 預設值為 25。 RoleManagerModule不會為屬於多個maxCachedResults角色的使用者建立 Cookie。 因此, RolePrincipal 物件的 IsInRole 方法會使用 Roles 類別來判斷使用者的角色。 原因是 maxCachedResults 許多使用者代理程序不允許大於 4,096 個字節的 Cookie。 因此,此上限是為了降低超過此大小限制的可能性。 如果您有非常長的角色名稱,您可以考慮指定較小的 maxCachedResults 值;相反地,如果您有極短的角色名稱,則可能會增加此值。

表 1: 角色快取 Cookie 組態選項

讓我們將應用程式設定為使用非持續性角色快取 Cookie。 若要完成這項作業,請 <roleManager> 更新 中的 Web.config 專案,以包含下列 Cookie 相關屬性:

<roleManager enabled="true"    
          defaultProvider="SecurityTutorialsSqlRoleProvider"    
          cacheRolesInCookie="true"    
          createPersistentCookie="false"    
          cookieProtection="All">    

     <providers>    
     ...    
     </providers>    
</roleManager>

我藉由新增三個屬性來更新 <roleManager> 元素: cacheRolesInCookiecreatePersistentCookiecookieProtection。 藉由將 設定 cacheRolesInCookietrue,現在 RoleManagerModule 會自動快取 Cookie 中使用者的角色,而不需要查閱每個要求的使用者角色資訊。 我分別將 和 cookieProtection 屬性設定createPersistentCookiefalseAll。 在技術上,我不需要為這些屬性指定值,因為我剛將它們指派給預設值,但我在這裡放置這些屬性,以明確指出我未使用持續性 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.aspxUsersAndRoles.aspxCreateUserWizardWithRoles.aspx 頁面只能供系統管理員角色中的那些使用者存取,而 RoleBasedAuthorization.aspx 頁面仍可供所有訪客存取。

若要達成此目的,請從將檔案新增 Web.configRoles 資料夾開始。

將 Web.config 檔案新增至角色目錄

圖 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:只有系統管理員角色中的使用者可以檢視受保護的頁面, (按兩下即可檢視完整大小的映像)

註銷,然後以系統管理員角色中的使用者身分登入。 現在您應該能夠檢視三個受保護的頁面。

Tito 可以造訪 UsersAndRoles.aspx 頁面,因為他位於系統管理員角色中

圖 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,並將其設定IDUserGrid。 此時,我們將撰寫程式代碼來呼叫 Membership.GetAllUsers 方法,並將產生的 MembershipUserCollection 對象系結至 GridView。 MembershipUserCollection包含MembershipUser系統中每個用戶帳戶的物件;MembershipUser物件具有、EmailLastLoginDate等屬性UserName

在撰寫將使用者帳戶系結至方格的程序代碼之前,讓我們先定義 GridView 的字段。 從 GridView 的智慧標記中,按兩下 [編輯資料行] 連結以啟動 [欄位] 對話框, (請參閱圖 6) 。 從這裡取消核取左下角的 [自動產生字段] 複選框。 由於我們希望此 GridView 包含編輯和刪除功能,因此請新增 CommandField 並將其 和 ShowDeleteButton 屬性設定ShowEditButton為 True。 接下來,新增四個字段以顯示 UserNameEmailLastLoginDateComment 屬性。 針對兩個只讀屬性使用 BoundField, (UserNameLastLoginDate) 和 TemplateFields 用於兩個可編輯字段 (EmailComment) 。

讓第一個 BoundField 顯示 UserName 屬性;將其 HeaderTextDataField 屬性設定為 “UserName”。 此欄位無法編輯,因此將其 ReadOnly 屬性設定為 True。 將 LastLoginDate BoundField 設定為 “Last Login”,並將其DataField設定HeaderText為 “LastLoginDate”。 讓我們格式化這個 BoundField 的輸出,以便只顯示日期 (,而不是日期和時間) 。 若要完成這項作業,請將這個 BoundField 的 HtmlEncode 屬性設定為 False,並將其 DataFormatString 屬性設定為 “{0:d}”。 同時將 ReadOnly 屬性設定為 True。

HeaderText兩個 TemplateFields 的屬性設定為 「Email」 和 「Comment」。。

GridView 的欄位可以透過欄位對話框進行設定

圖 6:GridView 的欄位可以透過 [字段] 對話框設定 (按兩下即可檢視大小完整的影像)

我們現在需要定義 ItemTemplateEditItemTemplate 「Email」 和 「Comment」 TemplateFields 的 和 。 將標籤 Web 控制項新增至每個 ItemTemplate ,並分別將其 Text 屬性系結至 EmailComment 屬性。

針對 「Email」 TemplateField,新增名為 Email 的 TextBox,EditItemTemplate並使用Text雙向數據系結將其屬性系結至 Email 屬性。 將 RequiredFieldValidator 和 RegularExpressionValidator 新增至 EditItemTemplate ,以確保訪客編輯 Email 屬性已輸入有效的電子郵件位址。 針對 「Comment」 TemplateField,將名為的多 Comment 行 TextBox 新增至其 EditItemTemplate。 分別將 TextBox 的 ColumnsRows 屬性設定為 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傳回的 系結MembershipUserCollectionUserGrid GridView。 從 Page_Load 第一頁流覽的事件處理程式呼叫這個方法。

protected void Page_Load(object sender, EventArgs e)    
{    
     if (!Page.IsPostBack)    
          BindUserGrid();    
}

private void BindUserGrid()    
{    
     MembershipUserCollection allUsers = Membership.GetAllUsers();    
     UserGrid.DataSource = allUsers;    
     UserGrid.DataBind();    
}

在此程式代碼就緒后,請瀏覽網頁瀏覽瀏覽器。 如圖 7 所示,您應該會看到 GridView 列出系統中每個使用者帳戶的相關信息。

UserGrid GridView 列出系統中每個使用者的相關信息

圖 7UserGrid GridView 列出系統中每個使用者的相關信息 (按兩下即可檢視全大小影像)

注意

UserGrid GridView 會列出非分頁介面中的所有使用者。 這個簡單的方格介面不適用於數十個以上的使用者。 其中一個選項是設定 GridView 以啟用分頁。 方法 Membership.GetAllUsers 有兩個多載:一個不接受任何輸入參數,並傳回所有使用者,另一個接受頁面索引和頁面大小的整數值,並只傳回指定的使用者子集。 第二個多載可用來更有效率地逐頁瀏覽使用者,因為它只會傳回用戶帳戶的精確子集,而不是 全部 。 如果您有數千個用戶帳戶,您可能會想要考慮以篩選為基礎的介面,其中一個只會顯示UserName以選取字元開頭的使用者,例如。 Membership.FindUsersByName method非常適合用來建置以篩選為基礎的使用者介面。 我們將在未來的教學課程中探討如何建置這類介面。

當控件系結至正確設定的數據源控件時,GridView 控件提供內建編輯和刪除支援,例如 SqlDataSource 或 ObjectDataSource。 UserGrid不過,GridView 會以程式設計方式系結其數據;因此,我們必須撰寫程式代碼來執行這兩項工作。 特別是,我們需要為 GridView 的 RowEditingRowCancelingEditRowUpdatingRowDeleting 事件建立事件處理程式,此事件會在訪客按兩下 GridView 的 [編輯]、[取消]、[更新] 或 [刪除] 按鈕時引發。

首先,為 GridView 的 RowEditingRowCancelingEditRowUpdating 事件建立事件處理程式,然後新增下列程式代碼:

protected void UserGrid_RowEditing(object sender, GridViewEditEventArgs e)
{
     // Set the grid's EditIndex and rebind the data

     UserGrid.EditIndex = e.NewEditIndex;
     BindUserGrid();
}

protected void UserGrid_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
{
     // Revert the grid's EditIndex to -1 and rebind the data
     UserGrid.EditIndex = -1;
     BindUserGrid();
}

protected void UserGrid_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
     // Exit if the page is not valid
     if (!Page.IsValid)
          return;

     // Determine the username of the user we are editing
     string UserName = UserGrid.DataKeys[e.RowIndex].Value.ToString();

     // Read in the entered information and update the user
     TextBox EmailTextBox = UserGrid.Rows[e.RowIndex].FindControl("Email") as TextBox;
     TextBox CommentTextBox = UserGrid.Rows[e.RowIndex].FindControl("Comment") as TextBox;

     // Return information about the user
     MembershipUser UserInfo = 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();
}

RowEditingRowCancelingEdit 事件處理程式只會設定 GridView 的 EditIndex 屬性,然後將使用者帳戶清單重新繫結至方格。 有趣的內容會在事件處理程式中 RowUpdating 發生。 此事件處理程式會從確保數據有效開始,然後從DataKeys集合擷取UserName已編輯用戶帳戶的值。 Email然後,會以程式設計方式參考兩個 TemplateFields 中的 EditItemTemplateComment TextBox。 其 Text 屬性包含已編輯的電子郵件位址和批注。

為了透過成員資格 API 更新使用者帳戶,我們必須先取得使用者的信息,我們會透過呼叫 來執行此動作 Membership.GetUser(userName)。 然後,傳回 MembershipUser 物件的 EmailComment 屬性會以從編輯介面輸入至兩個 TextBox 中的值進行更新。 最後,這些修改會與的 Membership.UpdateUser呼叫一起儲存。 RowUpdating事件處理程式會藉由將 GridView 還原為其預先編輯介面來完成。

接下來,建立 RowDeleting 事件處理程式,然後新增下列程序代碼:

protected void UserGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
     // Determine the username of the user we are editing
     string UserName = 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();
}

上述事件處理程式會從 GridView 的DataKeys集合擷UserName取值開始;然後,這個UserName值會傳遞至 Membership 類別的 DeleteUser 方法。 方法 DeleteUser 會從系統刪除用戶帳戶,包括相關的成員資格數據 (,例如此用戶所屬的角色) 。 刪除用戶之後,方格的 EditIndex 會設定為 -1 (,以防使用者在另一個數據列處於編輯模式時按兩下 [刪除],) 並 BindUserGrid 呼叫 方法。

注意

刪除使用者帳戶之前,[刪除] 按鈕不需要對用戶進行任何確認。 我們鼓勵您新增某種形式的用戶確認,以減少意外刪除帳戶的機會。 確認動作最簡單的方式之一是透過用戶端確認對話方塊。 如需這項技術的詳細資訊,請參閱 刪除時新增 Client-Side 確認

確認此頁面如預期般運作。 您應該能夠編輯任何使用者的電子郵件位址和批註,以及刪除任何用戶帳戶。 RoleBasedAuthorization.aspx由於此頁面可供所有使用者存取,任何使用者甚至匿名訪客都可以造訪此頁面,並編輯和刪除用戶帳戶! 讓我們更新此頁面,讓只有監督員和系統管理員角色中的使用者可以編輯使用者的電子郵件位址和批注,而且只有系統管理員可以刪除用戶帳戶。

[使用 LoginView 控件] 區段會查看使用 LoginView 控件來顯示使用者角色特有的指示。 如果系統管理員角色中的人員造訪此頁面,我們將示範如何編輯和刪除使用者的指示。 如果監督員角色中的使用者到達此頁面,我們會顯示編輯使用者的指示。 如果訪客是匿名的,或不在監督員或系統管理員角色中,我們會顯示一則訊息,說明他們無法編輯或刪除使用者帳戶資訊。 在 [以程序設計方式限制功能] 區段中,我們將撰寫程式代碼,根據使用者的角色,以程序設計方式顯示或隱藏 [編輯] 和 [刪除] 按鈕。

使用 LoginView 控件

如過去教學課程中所見,LoginView 控件對於顯示已驗證和匿名使用者的不同介面很有用,但 LoginView 控件也可以用來根據使用者的角色來顯示不同的標記。 讓我們使用 LoginView 控件,根據瀏覽使用者的角色來顯示不同的指示。

首先,在 GridView 上方 UserGrid 新增 LoginView。 如我們稍早所討論,LoginView 控制件有兩個內建範本: AnonymousTemplateLoggedInTemplate。 在這兩個範本中輸入簡短訊息,通知使用者他們無法編輯或刪除任何用戶資訊。

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

除了 AnonymousTemplateLoggedInTemplate之外,LoginView 控制項還可以包含 RoleGroups,這是角色特定的範本。 每個 RoleGroup 都包含單一屬性 , Roles指定 RoleGroup 所套用的角色。 屬性 Roles 可以設定為單一角色 (,例如 “Administrators”) 或以逗號分隔的角色清單 (,例如 “Administrators, Supervisors” ) 。

若要管理 RoleGroups,請按下控件智慧標記中的 [編輯 RoleGroups] 連結,以顯示 RoleGroup 集合編輯器。 新增兩個新的 RoleGroup。 將第一個 RoleGroup 的 Roles 屬性設定為 “Administrators”,第二個屬性設定為 “Supervisors”。

透過 RoleGroup 集合編輯器管理 LoginView 的 Role-Specific 範本

圖 8:透過 RoleGroup 集合編輯器管理 LoginView 的 Role-Specific 範本, (按兩下即可檢視完整大小的影像)

按兩下 [確定] 以關閉 RoleGroup 集合編輯器;這會更新 LoginView 的宣告式標記,以包含 <RoleGroups> 具有 RoleGroup 集合編輯器中定義之每個 RoleGroup 子元素的區段 <asp:RoleGroup> 。 此外,LoginView 智慧標記中的 [檢視] 下拉式清單一開始只列出 AnonymousTemplateLoggedInTemplate ,現在也包含新增的 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&#39; 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) 。

暴力密碼會顯示監督員 Role-Specific 訊息

圖 9:暴力密碼會顯示監督員 Role-Specific 訊息 (按兩下以檢視完整大小的影像)

Tito 會顯示系統管理員 Role-Specific 訊息

圖 10:Tito 顯示系統管理員 Role-Specific 訊息 (按兩下即可檢視大小完整的映像)

如圖 9 和 10 所示的螢幕快照,即使套用多個範本,LoginView 只會轉譯一個範本。 Bruce 和 Tito 都已登入使用者,但 LoginView 只會轉譯相符的 RoleGroup,而不是 LoggedInTemplate。 此外,Tito 同時屬於系統管理員和監督員角色,但 LoginView 控件會轉譯 Administrators 角色特定的範本,而不是監督員角色。

圖 11 說明 LoginView 控制項用來判斷要呈現之範本的工作流程。 請注意,如果指定多個 RoleGroup,LoginView 範本會轉譯符合 的第一個 RoleGroup。 換句話說,如果我們已將監督員 RoleGroup 設為第一個 RoleGroup,並將 Administrators 設為第二個,則當 Tito 造訪此頁面時,他會看到監督員訊息。

LoginView 控件的工作流程,用於判斷要轉譯的範本

圖 11:LoginView 控件的工作流程,用來判斷要呈現的範本 (按兩下即可檢視完整大小的影像)

以程序設計方式限制功能

雖然 LoginView 控件會根據瀏覽頁面的使用者角色顯示不同的指示,但所有使用者仍可看見 [編輯] 和 [取消] 按鈕。 我們需要以程式設計方式隱藏匿名訪客和不在監督員或系統管理員角色中的 [編輯] 和 [刪除] 按鈕。 我們需要為不是系統管理員的每個人隱藏 [刪除] 按鈕。 為了達成此目的,我們將撰寫一些程式代碼,以程式設計方式參考 CommandField 的 Edit 和 Delete LinkButtons,並視需要將其 Visible 屬性設定為 false

以程式設計方式參考 CommandField 中的控件最簡單的方式,就是先將控件轉換成範本。 若要達成此目的,請按兩下 GridView 智慧標記中的 [編輯資料行] 連結,從目前字段清單中選取 CommandField,然後按兩下 [將此欄位轉換成 TemplateField] 連結。 這會將 CommandField 轉換成具有 和 EditItemTemplateItemTemplate TemplateField。 ItemTemplate包含 Edit 和 Delete LinkButtons,同時EditItemTemplate裝載 Update 和 Cancel LinkButtons。

將 CommandField 轉換成 TemplateField

圖 12:將 CommandField 轉換成 TemplateField (按兩下即可檢視完整大小的影像)

更新 中的ItemTemplateEdit和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 void UserGrid_RowCreated(object sender, GridViewRowEventArgs e)
{
     if (e.Row.RowType == DataControlRowType.DataRow && e.Row.RowIndex != UserGrid.EditIndex)
     {
          // Programmatically reference the Edit and Delete LinkButtons
          LinkButton EditButton = e.Row.FindControl("EditButton") as LinkButton;

          LinkButton DeleteButton = e.Row.FindControl("DeleteButton") as LinkButton;

          EditButton.Visible = (User.IsInRole("Administrators") || User.IsInRole("Supervisors"));
          DeleteButton.Visible = User.IsInRole("Administrators");
     }
}

請記住,所有 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 的 SelectedIndexChangedRowDeleting 事件處理程式,讓它們只能由已驗證的使用者和 Tito 分別執行。 屬性 PrincipalPermission 也適用於角色。

讓我們示範如何在 PrincipalPermission GridView RowUpdatingRowDeleting 事件處理程式上使用 屬性,禁止對非授權的用戶執行。 我們只需要在每個函式定義上新增適當的屬性:

[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
[PrincipalPermission(SecurityAction.Demand, Role = "Supervisors")]
protected void UserGrid_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
     ...
}

[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
protected void UserGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
     ...
}

事件處理程式的 屬性 RowUpdating 會指示只有 Administrators 或 Supervisors 角色中的使用者可以執行事件處理程式,因為事件處理程式上的 RowDeleting 屬性會將執行限制為 Administrators 角色中的使用者。

注意

屬性 PrincipalPermission 會以 命名空間中的 System.Security.Permissions 類別表示。 請務必在程式代碼後置類別檔案頂端新增 using System.Security.Permissions 語句,以匯入此命名空間。

如果非系統管理員嘗試執行 RowDeleting 事件處理程式,或是非監督員或非系統管理員嘗試執行 RowUpdating 事件處理程式,則 .NET 執行時間會引發 SecurityException

如果安全性內容未獲授權執行方法,則會擲回 SecurityException

圖 16:如果安全性內容未獲授權執行方法, SecurityException 則會擲回 (按兩下即可檢視大小完整的映射)

除了 ASP.NET 頁面之外,許多應用程式也有包含各種層級的架構,例如商業規則和數據存取層。 這些層通常會實作為類別庫,並提供類別和方法來執行商業規則和數據相關功能。 屬性 PrincipalPermission 也適用於將授權規則套用至這些層。

如需使用 PrincipalPermission 屬性定義類別和方法授權規則的詳細資訊,請參閱 Scott Guthrie 的部落格文章:使用 PrincipalPermissionAttributes將授權規則新增至商務和數據層

摘要

在本教學課程中,我們探討如何根據使用者的角色指定粗略和精細的授權規則。 Asp。NET 的 URL 授權功能可讓頁面開發人員指定允許或拒絕存取哪些頁面的身分識別。 如我們在使用者型授權教學課程中所見,URL 授權規則可以依使用者方式套用。 如本教學課程的步驟 1 中所見,它們也可以依角色依角色套用。

更精細的授權規則可以宣告方式或以程序設計方式套用。 在步驟 2 中,我們已探討如何使用 LoginView 控件的 RoleGroups 功能,根據瀏覽使用者的角色來呈現不同的輸出。 我們也探討如何以程式設計方式判斷使用者是否屬於特定角色,以及如何據以調整頁面的功能。

快樂的程序設計!

深入閱讀

如需本教學課程中討論之主題的詳細資訊,請參閱下列資源:

關於作者

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