單純驗證只能確認「你是誰」。授權決定「你能做什麼」。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` 關聯表支援多對多、權限細粒度。
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);
}
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 { /* 記錄日誌,忽略無效票據 */ }
}
聲明式 (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>
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` 等豐富屬性。
表單驗證 + 角色授權核心流程:登入驗證 → 建立含角色的 Ticket → 加密 Cookie → `Application_AuthenticateRequest` 解密還原 `GenericPrincipal` → `User.IsInRole` / `web.config authorization` 控制存取。掌握 `Principal = Identity + Roles` 模型,並善用 `Global.asax` 還原上下文,即可構建安全、跨平台、細粒度的授權體系。