<asp:Panel ID="pnlLogin" runat="server" DefaultButton="btnLogin">
<table class="login-form">
<tr>
<td><label for="<%= txtUser.ClientID %>">帳號</label></td>
<td><asp:TextBox ID="txtUser" runat="server" AutoCompleteType="Username" /></td>
</tr>
<tr>
<td><label for="<%= txtPwd.ClientID %>">密碼</label></td>
<td><asp:TextBox ID="txtPwd" runat="server" TextMode="Password" AutoCompleteType="Password" /></td>
</tr>
<tr>
<td></td>
<td><asp:CheckBox ID="chkRemember" runat="server" Text="記住我 (7天)" /></td>
</tr>
<tr>
<td></td>
<td><asp:Button ID="btnLogin" runat="server" Text="登入" OnClick="btnLogin_Click" /></td>
</tr>
</table>
</asp:Panel>
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack) LoadSavedCredentials();
}
private void LoadSavedCredentials()
{
// 讀取 Cookie 填入表單
if (Request.Cookies["Login_User"] != null && Request.Cookies["Login_Pwd"] != null)
{
txtUser.Text = Request.Cookies["Login_User"].Value;
// 密碼需設定 Attributes["value"] 才會顯示在 TextMode=Password 的輸入框
txtPwd.Attributes["value"] = Request.Cookies["Login_Pwd"].Value;
chkRemember.Checked = true;
}
}
protected void btnLogin_Click(object sender, EventArgs e)
{
// 1. 先清除舊 Cookie (避免殘留)
ClearLoginCookies();
// 2. 驗證帳密
var user = UserService.Validate(txtUser.Text.Trim(), txtPwd.Text);
if (user == null)
{
lblError.Text = "帳號或密碼錯誤";
return;
}
// 3. 建立 Session / FormsAuthentication Ticket
Session["CurrentUser"] = user;
FormsAuthentication.SetAuthCookie(user.UserName, chkRemember.Checked); // 同步設定認證 Cookie
// 4. 若勾選「記住我」,儲存帳密到 Cookie (7天)
if (chkRemember.Checked)
{
SaveLoginCookies(user.UserName, txtPwd.Text); // 注意:實務建議只存帳號,密碼加密或不存
}
// 5. 導向原頁或首頁
string returnUrl = Request.QueryString["ReturnUrl"];
Response.Redirect(string.IsNullOrEmpty(returnUrl) ? "~/Default.aspx" : returnUrl);
}
private void SaveLoginCookies(string username, string password)
{
var cookieUser = new HttpCookie("Login_User", username)
{
Expires = DateTime.Now.AddDays(7),
HttpOnly = true,
Secure = Request.IsSecureConnection
};
var cookiePwd = new HttpCookie("Login_Pwd", password)
{
Expires = DateTime.Now.AddDays(7),
HttpOnly = true,
Secure = Request.IsSecureConnection
};
Response.Cookies.Add(cookieUser);
Response.Cookies.Add(cookiePwd);
}
private void ClearLoginCookies()
{
Response.Cookies["Login_User"].Expires = DateTime.Now.AddDays(-1);
Response.Cookies["Login_Pwd"].Expires = DateTime.Now.AddDays(-1);
}
| 風險 | 對策 |
|---|---|
| 密碼明文儲存於 Cookie | 嚴禁 儲存真實密碼。改為: 1. 僅儲存帳號 2. 或儲存加密 Token (AES/DataProtection) 3. 或使用「記住我」延長 FormsAuthentication Ticket 過期時間,不存密碼 |
| XSS 竊取 Cookie | Cookie 設定 HttpOnly=true、Secure=true (HTTPS)、SameSite=Lax |
| CSRF 攻擊 | 登入表單加入 AntiForgeryToken,驗證 POST 來源 |
| 暴力破解 | 失敗計數器、帳號鎖定、驗證碼、IP 限流 |
| 瀏覽器自動填入 | autocomplete="off" 或 autocomplete="new-password" (現代瀏覽器可能忽略) |
// 登入成功
if (chkRemember.Checked)
{
// 1. 只存帳號方便下次輸入
var cookie = new HttpCookie("RememberedUser", user.UserName)
{
Expires = DateTime.Now.AddDays(30),
HttpOnly = true,
Secure = Request.IsSecureConnection
};
Response.Cookies.Add(cookie);
// 2. FormsAuthentication Ticket 設為持久 (7天/30天)
// web.config: (30天分鐘數)
FormsAuthentication.SetAuthCookie(user.UserName, true); // true = persistent cookie
}
else
{
// 臨時 Ticket (瀏覽器關閉即失效)
FormsAuthentication.SetAuthCookie(user.UserName, false);
}
使用者輸入帳密 + 勾選記住我
│
▼
驗證成功?
│
├─ 否 ──▶ 顯示錯誤、清除 Cookie
│
└─ 是 ──▶ 建立 Session / FormsAuth Ticket
│
├─ 記住我 = true ──▶ 儲存帳號 Cookie (30天) + 持久 Auth Cookie (30天)
│
└─ 記住我 = false ──▶ 臨時 Auth Cookie (Session 級)
│
▼
導向目標頁面