﻿# ASP.NET 自訂角色的方式（不用實做 Role Provider）

我之前講過一篇文章叫做[簡介 ASP.NET 表單驗證 (FormsAuthentication) 的運作方式](/post/2008/02/20/Explain-Forms-Authentication-in-ASPNET-20)，但若要使用 **Forms Authentication** 來驗證使用者，又要自行指派**使用者角色**時那就麻煩了，通常要實做**角色提供者**(Role Provider)才行，是有點麻煩，我今天介紹一個更簡單、方便的自訂角色方法。

![ASP.NET FormsAuthentication 自訂 Roles 角色](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/0da38d7f967d8628c234-200627270-903e12b9-c8dc-430c-8ca8-8d16237a8173.png)

若使用 Forms Authentication 我常用的登入程式碼如下：

```csharp
/// <summary>
/// 設定要存在 FormsAuthenticationTicket 中的資料，這裡用來儲存角色資訊
/// </summary>
string userData = "";

protected void Login_Button_Click(object sender, EventArgs e)
{
    // 登入時清空所有 Session 資料
    Session.RemoveAll();

    // 登入的密碼 (以 SHA1 雜湊處理)
    string strPassword = FormsAuthentication.HashPasswordForStoringInConfigFile(txtPassword.Text, "SHA1");

    if (ValidateLogin(txtAccount.Text, strPassword))
    {
        // 將管理者登入的 Cookie 設定成 Session Cookie
        bool isPersistent = false;

        FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
          txtAccount.Text,
          DateTime.Now,
          DateTime.Now.AddMinutes(30),
          isPersistent,
          userData,
          FormsAuthentication.FormsCookiePath);

        // 將 FormsAuthenticationTicket 加密
        string encTicket = FormsAuthentication.Encrypt(ticket);

        // 將加密過的 FormsAuthenticationTicket 回應給瀏覽器儲存
        var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
        cookie.HttpOnly = true;
        Response.Cookies.Add(cookie);

        Response.Redirect("~/Members/", true);
    }
    else
    {
        Page.ClientScript.RegisterStartupScript(Page.GetType(), "LoginFail", "alert('登入失敗，請重新登入!');", true);
    }
}

/// <summary>
/// 驗證使用者是否登入成功
/// </summary>
/// <param name="strUsername">登入帳號</param>
/// <param name="strPassword">登入密碼</param>
/// <returns></returns>
private bool ValidateLogin(string strUsername, string strPassword)
{
    // 驗證

        // 請自行寫 Code 檢查 Username, Password 是否正確

    // 授權：設定角色到 userData
    userData = "gold_member,board_admin";

    return true;
}
```

而這段程式的重點在於我將 `userData` 儲存在 [FormsAuthenticationTicket](https://learn.microsoft.com/zh-tw/dotnet/api/system.web.security.formsauthenticationticket?view=netframework-4.8&WT.mc_id=DT-MVP-4015686) 中，讓**使用者登入資訊**包括「角色」的定義，但是如果你要在程式中使用 `User.IsInRole` 或要在 [SiteMap](https://learn.microsoft.com/zh-tw/dotnet/api/system.web.sitemap?view=netframework-4.8&WT.mc_id=DT-MVP-4015686) 檔中啟用 `securityTrimmingEnabled` 的話，還需要在 `Global.asax` 中新增 [Application\_AuthenticateRequest](https://learn.microsoft.com/zh-tw/dotnet/api/system.web.httpapplication.authenticaterequest?view=netframework-4.8&WT.mc_id=DT-MVP-4015686) 事件，才能讓 ASP.NET 知道該使用者擁有什麼角色，如下：

```csharp
void Application_AuthenticateRequest(object sender, EventArgs e)
{
    if (Request.IsAuthenticated) {
        // 先取得該使用者的 FormsIdentity
        FormsIdentity id = (FormsIdentity)User.Identity;
        
        // 再取出使用者的 FormsAuthenticationTicket
        FormsAuthenticationTicket ticket = id.Ticket;
        
        // 將儲存在 FormsAuthenticationTicket 中的角色定義取出，並轉成字串陣列
        string[] roles = ticket.UserData.Split(new char[] { ',' });
        
        // 指派角色到目前這個 HttpContext 的 User 物件去
        Context.User = new GenericPrincipal(Context.User.Identity, roles);
    }
}
```

這段程式碼我可是研究好久才整理出來的，但我覺得透過這種方式自訂「角色」感覺簡單多了，而且也不需要自己實做 Role Provider 或在資料庫中多建立一個表格。

> 若要啟用 SiteMap 安全限制的功能必須要在 `web.config` 中設定該 sitemap 定義的 `securityTrimmingEnabled` 屬性為 True 才行，如下：

```xml
<siteMap defaultProvider="SiteMapProvider" enabled="true">
    <providers>
        <add name="SiteMapProvider" description="設的 SiteMap 定義檔"
             type="System.Web.XmlSiteMapProvider " siteMapFile="~/Web.sitemap"
             securityTrimmingEnabled="true"/>
    </providers>
</siteMap>
```
