// 建立 Cookie 物件
var cookie = new HttpCookie("UserName", "john_doe")
{
Expires = DateTime.Now.AddDays(7), // 7 天有效
HttpOnly = true, // 防 XSS 竊取
Secure = Request.IsSecureConnection, // 僅 HTTPS 傳輸
SameSite = SameSiteMode.Lax, // CSRF 保護 (需 .NET 4.7.2+)
Domain = ".example.com" // 選擇性:跨子域共用
};
Response.Cookies.Add(cookie);
if (Request.Cookies["UserName"] != null)
{
string userName = Request.Cookies["UserName"].Value;
// 自動填入登入框
}
// 設定過去時間即刪除
Response.Cookies["UserName"].Expires = DateTime.Now.AddDays(-1);
// 或
var cookie = new HttpCookie("UserName", "") { Expires = DateTime.Now.AddYears(-1) };
Response.Cookies.Add(cookie);
<form id="loginForm" method="post">
<input type="text" name="username" placeholder="帳號" />
<input type="password" name="password" placeholder="密碼" />
<label><input type="checkbox" name="rememberMe" /> 記住我 (7 天)</label>
<button type="submit">登入</button>
</form>
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(string username, string password, bool rememberMe)
{
// 1. 驗證帳密 (略,假設回傳 User 物件)
var user = _authService.Validate(username, password);
if (user == null)
{
ModelState.AddModelError("", "帳號或密碼錯誤");
return View();
}
// 2. 建立驗證 Ticket (FormsAuthentication)
var ticket = new FormsAuthenticationTicket(
version: 1,
name: user.Id.ToString(), // Name 存 UserId
issueDate: DateTime.Now,
expiration: DateTime.Now.AddDays(rememberMe ? 30 : 1), // 記住我 30 天,否則 1 天
isPersistent: rememberMe,
userData: user.Role // 可存角色、權限等自訂資料
);
// 3. 加密並寫入 Cookie
string encTicket = FormsAuthentication.Encrypt(ticket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket)
{
HttpOnly = true,
Secure = Request.IsSecureConnection,
SameSite = SameSiteMode.Lax,
Expires = ticket.Expiration
};
Response.Cookies.Add(authCookie);
// 4. 導向原頁或首頁
return Redirect(FormsAuthentication.GetRedirectUrl(user.Id.ToString(), rememberMe));
}
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
var cookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie != null)
{
try
{
var ticket = FormsAuthentication.Decrypt(cookie.Value);
if (ticket != null && !ticket.Expired)
{
var identity = new FormsIdentity(ticket);
var principal = new GenericPrincipal(identity, new[] { ticket.UserData }); // UserData 為角色
Context.User = Thread.CurrentPrincipal = principal;
}
}
catch { /* 解密失敗忽略 */ }
}
}
| 措施 | 說明 |
|---|---|
| HttpOnly | JavaScript 無法讀取 document.cookie,防 XSS 竊取 Session ID。 |
| Secure | 僅 HTTPS 傳送 Cookie,防中間人攻擊。web.config 設定 <httpCookies requireSSL="true" />。 |
| SameSite=Lax/Strict | 瀏覽器自動阻擋跨站請求攜帶 Cookie,防 CSRF。.NET 4.7.2+ 原生支援。 |
| 加密 Ticket | FormsAuthentication.Encrypt 使用 machineKey 加密簽章,防偽造。確保 web.config 設定固定 <machineKey validationKey="..." decryptionKey="..." />,避免叢集節點金鑰不一致。 |
| 不儲存明文密碼 | Cookie 僅存加密 Ticket 或 UserId,**絕不**存密碼 (即使 MD5)。 |
| 登出清除 | FormsAuthentication.SignOut() + 手動刪除自訂 Cookie。 |
Microsoft.AspNetCore.Authentication.Cookies 中介軟體:services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(...)。SignInAsync/SignOutAsync、ClaimsPrincipal、滑動過期、資料保護 (Data Protection API) 取代 MachineKey。Cookie 是 HTTP 無狀態協定下維持登入狀態的關鍵。正確實作「記住我」需:驗證帳密 → 建立加密 Ticket → 發送 HttpOnly+Secure+SameSite Cookie → 全域解析還原 Principal。嚴守安全性六大原則,並規劃遷移至現代化 Cookie Authentication 中介軟體,才能構建安全可靠的認證體系。