☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
ASP.NET Core 中級

身份驗證與授權

驗證 vs 授權

  • 身份驗證 Authentication:你是誰?(像門口的保全確認你的身份證)
  • 授權 Authorization:你能做什麼?(像VIP 識別決定你能進哪些區域)
用戶請求 → 身份驗證(你是誰?)→ 授權(你能做什麼?)→ 存取資源
         「請出示證件」      「確認你有 VIP 資格」

// Program.cs - 設定 Cookie 驗證
builder.Services.AddAuthentication(
    CookieAuthenticationDefaults.AuthenticationScheme)  // 使用 Cookie 方案
    .AddCookie(options =>
    {
        options.LoginPath = "/Account/Login";           // 未登入時導向登入頁
        options.AccessDeniedPath = "/Account/Denied";   // 權限不足時導向
        options.ExpireTimeSpan = TimeSpan.FromHours(2);  // Cookie 2 小時過期
    });
// AccountController.cs - 登入邏輯
public class AccountController : Controller
{
    [HttpPost]
    public async Task<IActionResult> Login(LoginViewModel model)
    {
        // 驗證帳號密碼(這裡簡化示範)
        if (model.Username == "admin" && model.Password == "pass123")
        {
            // 建立 Claims(聲明)
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, model.Username),     // 使用者名稱
                new Claim(ClaimTypes.Role, "Admin"),             // 角色
                new Claim("Department", "IT")                    // 自訂 Claim
            };

            // 建立身份識別
            var identity = new ClaimsIdentity(
                claims,
                CookieAuthenticationDefaults.AuthenticationScheme); // 驗證方案

            // 登入(寫入 Cookie)
            await HttpContext.SignInAsync(
                new ClaimsPrincipal(identity));                     // 建立主體並登入

            return RedirectToAction("Index", "Home");              // 導向首頁
        }

        ModelState.AddModelError("", "帳號或密碼錯誤");              // 驗證失敗
        return View(model);                                        // 回到登入頁
    }

    public async Task<IActionResult> Logout()
    {
        await HttpContext.SignOutAsync();                          // 登出(清除 Cookie)
        return RedirectToAction("Index", "Home");                  // 導向首頁
    }
}

JWT Token 驗證

// Program.cs - 設定 JWT 驗證
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,                              // 驗證發行者
            ValidateAudience = true,                            // 驗證受眾
            ValidateLifetime = true,                            // 驗證有效期
            ValidateIssuerSigningKey = true,                    // 驗證簽章金鑰
            ValidIssuer = builder.Configuration["Jwt:Issuer"],  // 合法發行者
            ValidAudience = builder.Configuration["Jwt:Audience"], // 合法受眾
            IssuerSigningKey = new SymmetricSecurityKey(         // 簽章金鑰
                Encoding.UTF8.GetBytes(
                    builder.Configuration["Jwt:Key"]!))          // 從設定檔讀取
        };
    });
// 產生 JWT Token
public string GenerateToken(User user)
{
    var claims = new[]
    {
        new Claim(ClaimTypes.Name, user.Username),              // 使用者名稱
        new Claim(ClaimTypes.Role, user.Role),                  // 角色
        new Claim(JwtRegisteredClaimNames.Jti,
            Guid.NewGuid().ToString())                          // Token 唯一識別碼
    };

    var key = new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));           // 金鑰

    var token = new JwtSecurityToken(
        issuer: _config["Jwt:Issuer"],                          // 發行者
        audience: _config["Jwt:Audience"],                      // 受眾
        claims: claims,                                         // 聲明
        expires: DateTime.Now.AddHours(1),                      // 1 小時後過期
        signingCredentials: new SigningCredentials(
            key, SecurityAlgorithms.HmacSha256));               // 簽章演算法

    return new JwtSecurityTokenHandler().WriteToken(token);      // 產生 Token 字串
}

[Authorize] 與 [AllowAnonymous]

// 需要登入才能存取的 Controller
[Authorize]                                     // 整個 Controller 需要登入
public class DashboardController : Controller
{
    public IActionResult Index()
    {
        var name = User.Identity?.Name;         // 取得登入者名稱
        return View();                          // 回傳儀表板頁面
    }

    [AllowAnonymous]                            // 這個 Action 允許匿名存取
    public IActionResult PublicPage()
    {
        return View();                          // 不用登入也能看
    }

    [Authorize(Roles = "Admin")]                // 只有 Admin 角色可以存取
    public IActionResult AdminOnly()
    {
        return View();                          // 管理員專用
    }

    [Authorize(Policy = "AtLeast18")]           // 自訂授權策略
    public IActionResult AdultContent()
    {
        return View();                          // 符合策略才能存取
    }
}
// Program.cs - 設定授權策略
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AtLeast18", policy =>
        policy.RequireClaim("Age")                             // 需要 Age Claim
              .RequireAssertion(ctx =>
              {
                  var age = int.Parse(
                      ctx.User.FindFirst("Age")?.Value ?? "0"); // 取得年齡
                  return age >= 18;                              // 年滿 18 歲
              }));
});

ASP.NET Identity 基礎

// Program.cs - 設定 ASP.NET Identity
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
{
    options.Password.RequireDigit = true;          // 密碼要有數字
    options.Password.RequiredLength = 8;            // 密碼至少 8 碼
    options.Password.RequireUppercase = true;       // 密碼要有大寫字母
    options.Lockout.MaxFailedAccessAttempts = 5;    // 失敗 5 次鎖定
    options.Lockout.DefaultLockoutTimeSpan =
        TimeSpan.FromMinutes(15);                   // 鎖定 15 分鐘
})
.AddRoles<IdentityRole>()                          // 啟用角色管理
.AddEntityFrameworkStores<AppDbContext>();           // 使用 EF Core 儲存

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:把密碼存成明文

// ❌ 明文儲存密碼(超級危險!)
var user = new User
{
    Username = "admin",
    Password = "mypassword123"   // ❌ 明文!資料庫被偷密碼就外洩了
};
db.Users.Add(user);
// ✅ 使用雜湊(Hash)
var passwordHasher = new PasswordHasher<User>(); // 建立密碼雜湊器
var user = new User { Username = "admin" };       // 建立使用者
user.PasswordHash = passwordHasher.HashPassword(
    user, "mypassword123");                        // 雜湊後儲存
db.Users.Add(user);                               // 存入資料庫

// 驗證時
var result = passwordHasher.VerifyHashedPassword(
    user, user.PasswordHash, inputPassword);       // 比對雜湊值

為什麼? 資料庫被入侵時,明文密碼會直接曝光。雜湊是單向的,即使被偷也無法還原。

❌ 錯誤 2:JWT 金鑰寫在程式碼裡

// ❌ 金鑰寫死在程式碼中(推上 Git 就洩漏了)
var key = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes("my-super-secret-key-12345")); // ❌ 硬編碼金鑰
// ✅ 從設定檔或環境變數讀取
var key = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes(
        builder.Configuration["Jwt:Key"]!));               // ✅ 從設定檔讀取
// appsettings.json(開發環境用)
{
    "Jwt": {
        "Key": "development-only-key-do-not-use-in-prod"
    }
}
// 正式環境用環境變數或 Azure Key Vault

為什麼? 金鑰寫在程式碼裡,推到 Git 就全世界都看得到。應該用設定檔或環境變數管理機密資訊。

❌ 錯誤 3:沒有驗證 JWT Token 的有效期

// ❌ 停用有效期驗證(永遠不過期的 Token)
options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateLifetime = false,  // ❌ 不檢查過期!Token 被偷就永遠能用
};
// ✅ 啟用所有驗證
options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateLifetime = true,                    // ✅ 檢查有效期
    ClockSkew = TimeSpan.FromMinutes(5),        // 允許 5 分鐘時鐘偏差
    ValidateIssuer = true,                      // 驗證發行者
    ValidateAudience = true,                    // 驗證受眾
    ValidateIssuerSigningKey = true,            // 驗證簽章
};

為什麼? 停用有效期驗證等於 Token 永遠有效,一旦被竊取,攻擊者可以永遠冒充該用戶。

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊