首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> 關於ASP.NET

ASP.NET 基於角色的表單驗證授權機制完整實作:從資料庫設計到 Principal/Identity 應用

     編輯:關於ASP.NET

為什麼需要角色授權?

單純驗證只能確認「你是誰」。授權決定「你能做什麼」。Windows 整合驗證雖支援角色但綁定 NTLM,不支援跨平台 (Linux/Mac + Firefox/Chrome)。表單驗證 + 自訂角色提供平台無關的彈性授權。

資料庫設計

CREATE DATABASE WebSolution;
GO
USE WebSolution;
GO

CREATE TABLE Users (
    UserName NVARCHAR(100) PRIMARY KEY,
    PasswordHash NVARCHAR(200) NOT NULL, -- 儲存雜湊值,非明文
    PasswordSalt NVARCHAR(50) NOT NULL,
    UserRoles NVARCHAR(200) NOT NULL,    -- 逗號分隔角色: 'Admin,Editor'
    Email NVARCHAR(200),
    CreatedDate DATETIME DEFAULT GETDATE()
);

CREATE INDEX IX_Users_Email ON Users(Email);

進階設計:獨立 `Roles`、`UserRoles` 關聯表支援多對多、權限細粒度。

核心概念:Principal 與 Identity

  • IIdentity:代表使用者身分 (`Name`、`AuthenticationType`、`IsAuthenticated`)。
  • IPrincipal:代表安全上下文,包含 `Identity` + `IsInRole(role)` 方法。
  • 關係:`Principal = Identity + Roles + Custom Data`。
  • ASP.NET 內建:`FormsIdentity` + `RolePrincipal` (或自訂 `GenericPrincipal`)。

實作步驟

1. 登入驗證與建立驗證票據

protected void btnLogin_Click(object sender, EventArgs e)
{
    string user = txtUser.Text.Trim();
    string pwd = txtPwd.Text;

    // 1. 驗證帳密 (參數化查詢 + 雜湊比對)
    string roles = UserService.ValidateAndGetRoles(user, pwd); // 失敗回傳 null
    if (roles == null) { lblError.Text = "帳號或密碼錯誤"; return; }

    // 2. 建立 FormsAuthenticationTicket (含角色資訊)
    var ticket = new FormsAuthenticationTicket(
        version: 1,
        name: user,
        issueDate: DateTime.Now,
        expiration: DateTime.Now.AddMinutes(30),
        isPersistent: chkPersist.Checked,
        userData: roles, // 角色字串存入 UserData
        cookiePath: FormsAuthentication.FormsCookiePath);

    // 3. 加密並寫入 Cookie
    string encTicket = FormsAuthentication.Encrypt(ticket);
    var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket)
    {
        HttpOnly = true,
        Secure = FormsAuthentication.RequireSSL,
        Expires = ticket.IsPersistent ? ticket.Expiration : DateTime.MinValue
    };
    Response.Cookies.Add(cookie);

    // 4. 導向原請求頁面或預設頁
    string redirectUrl = FormsAuthentication.GetRedirectUrl(user, chkPersist.Checked);
    Response.Redirect(redirectUrl);
}

2. Global.asax 還原 Principal (關鍵步驟)

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
    HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
    if (authCookie == null) return;

    try
    {
        FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
        if (ticket == null || ticket.Expired) return;

        // 解析角色
        string[] roles = ticket.UserData.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

        // 建立 Identity & Principal
        FormsIdentity identity = new FormsIdentity(ticket);
        GenericPrincipal principal = new GenericPrincipal(identity, roles);

        // 附加至 HttpContext
        Context.User = principal;
    }
    catch { /* 記錄日誌,忽略無效票據 */ }
}

3. 網頁授權控制

聲明式 (web.config):

<location path="Admin">
  <system.web>
    <authorization>
      <allow roles="Admin" />
      <deny users="*" />
    </authorization>
  </system.web>
</location>

程式碼檢查:

// 頁面/控件/服務中
if (User.IsInRole("Admin")) { /* 顯示管理功能 */ }
if (User.Identity.IsAuthenticated && User.IsInRole("Editor")) { /* 允許編輯 */ }

頁面級聲明式:

<%@ Page Language="C#" %>
<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!User.IsInRole("Admin")) Response.Redirect("~/AccessDenied.aspx");
    }
</script>

進階:自訂 Principal 豐富上下文

public class CustomPrincipal : IPrincipal
{
    public IIdentity Identity { get; }
    public int UserId { get; }
    public string Department { get; }
    public string[] Permissions { get; } // 細粒度權限

    public CustomPrincipal(FormsIdentity identity, int userId, string dept, string[] perms)
    {
        Identity = identity;
        UserId = userId;
        Department = dept;
        Permissions = perms;
    }

    public bool IsInRole(string role) => Array.Exists(Permissions, p => p.Equals(role, StringComparison.OrdinalIgnoreCase));
}

在 `Application_AuthenticateRequest` 中建立 `CustomPrincipal` 附加至 `Context.User`,後續程式碼可存取 `UserId`、`Department` 等豐富屬性。

安全性加強建議

  • 密碼儲存:PBKDF2/BCrypt/Argon2 雜湊 + 隨機 Salt,**嚴禁明文/MD5/SHA1**。
  • Cookie 設定:`HttpOnly=true`、`Secure=true` (HTTPS)、`SameSite=Strict/Lax`。
  • 防暴力破解:登入失敗計數、帳號鎖定、驗證碼。
  • 票據加密:`FormsAuthentication.Encrypt` 使用 `machineKey` (驗證 + 加密),確保 `machineKey` 手動產生並同步至所有 Web 伺服器 (Web Farm)。

總結

表單驗證 + 角色授權核心流程:登入驗證 → 建立含角色的 Ticket → 加密 Cookie → `Application_AuthenticateRequest` 解密還原 `GenericPrincipal` → `User.IsInRole` / `web.config authorization` 控制存取。掌握 `Principal = Identity + Roles` 模型,並善用 `Global.asax` 還原上下文,即可構建安全、跨平台、細粒度的授權體系。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved