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

身份驗證機制

什麼是身份驗證(Authentication)?

身份驗證就是確認「你是誰」的過程。

💡 比喻:進公司大門

  • Authentication(驗證) = 你刷員工證進大門(證明你是員工)
  • Authorization(授權) = 你能進哪些房間(你有什麼權限)
  • 先驗證身份,再決定權限

Session-based vs Token-based 驗證

Session-based(傳統方式)

登入流程:
瀏覽器                            伺服器
  |--- POST /login               -->|
  |    {帳號, 密碼}                  |
  |                                  | 驗證成功!
  |                                  | 建立 Session(存在伺服器記憶體)
  |<-- Set-Cookie: sessionId=abc123 -|
  |                                  |
  |--- GET /profile               -->|
  |    Cookie: sessionId=abc123      |
  |                                  | 用 sessionId 查找 Session
  |<-- {使用者資料}                --|

Session 存在伺服器端(記憶體或 Redis)

Token-based(JWT 方式)

登入流程:
瀏覽器                            伺服器
  |--- POST /login               -->|
  |    {帳號, 密碼}                  |
  |                                  | 驗證成功!
  |                                  | 產生 JWT Token
  |<-- { token: "eyJhbG..." }     --|
  |                                  |
  |--- GET /profile               -->|
  |    Authorization: Bearer eyJhbG..|
  |                                  | 驗證 Token 簽章
  |<-- {使用者資料}                --|

Token 存在客戶端(不需要伺服器存 Session)

兩種方式比較

特性           Session-based          Token-based (JWT)
──────────────────────────────────────────────────────
狀態           有狀態(伺服器存)      無狀態(客戶端存)
擴展性         難(多伺服器要共享)     易(每台都能驗證)
儲存位置       伺服器記憶體/Redis      客戶端 Cookie/Storage
跨域           困難                    容易(API 友好)
登出           刪除 Session 即可       需要額外機制(黑名單)
適用場景       傳統網站               API、SPA、手機 App

JWT 結構解析

JWT 的三個部分

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.abc123signature
|---- Header ----|.|---- Payload ----|.|--- Signature ---|

用「.」分隔三個部分,每個部分都是 Base64 編碼
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload(酬載)

{
  "sub": "1234",
  "name": "小明",
  "role": "admin",
  "exp": 1700000000,
  "iat": 1699900000
}
常見 Claims(聲明):
├── sub  → Subject(使用者 ID)
├── name → 使用者名稱
├── role → 角色/權限
├── exp  → Expiration(過期時間,Unix 時間戳)
├── iat  → Issued At(簽發時間)
└── iss  → Issuer(簽發者)

Signature(簽章)

簽章 = HMACSHA256(
    base64(header) + "." + base64(payload),
    secret_key
)

簽章的用途:
├── 確保 Token 沒有被竄改
├── 只有擁有 secret_key 的伺服器才能產生有效簽章
└── 注意:Payload 只是 Base64 編碼,不是加密!任何人都能解碼看到內容

C# 產生 JWT

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;

// 定義密鑰(至少 32 字元)
var key = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes("your-super-secret-key-at-least-32-chars!")
);
// 建立簽章憑證
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

// 定義 Token 中的 Claims(使用者資訊)
var claims = new[]
{
    // 使用者 ID
    new Claim(ClaimTypes.NameIdentifier, "1234"),
    // 使用者名稱
    new Claim(ClaimTypes.Name, "小明"),
    // 使用者角色
    new Claim(ClaimTypes.Role, "Admin"),
};

// 建立 JWT Token
var token = new JwtSecurityToken(
    issuer: "my-app",          // 簽發者
    audience: "my-app-users",  // 對象
    claims: claims,              // 使用者資訊
    expires: DateTime.UtcNow.AddHours(1),  // 1 小時後過期
    signingCredentials: credentials         // 簽章
);

// 序列化成字串
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
// 輸出類似:eyJhbGciOiJIUzI1NiIs...
Console.WriteLine(tokenString);

OAuth 2.0 流程

💡 比喻:LINE Login 你去一個新網站,它說「用 LINE 登入」:

  1. 網站把你「帶到 LINE 的登入頁面」
  2. 你在 LINE 輸入帳號密碼(不是在那個網站!)
  3. LINE 問你:「這個網站想要你的名字和大頭貼,可以嗎?」
  4. 你按「同意」
  5. LINE 給那個網站一個「通行證」(Access Token)
  6. 網站用通行證去 LINE 拿你的名字和大頭貼

OAuth 2.0 授權碼流程

使用者          你的網站            LINE(授權伺服器)
  |                |                      |
  |-- 點擊「LINE 登入」-->|               |
  |                |--- 302 重導向 ------->|
  |                |    到 LINE 登入頁     |
  |<------------ LINE 登入頁面 -----------|
  |-- 輸入帳密、同意 -------------------->|
  |                |                      |
  |                |<-- 302 帶 auth code --|
  |                |                      |
  |                |--- POST 用 code ----->|
  |                |    換 access token    |
  |                |<-- access token ------|
  |                |                      |
  |                |--- GET /userinfo ---->|
  |                |    帶 access token    |
  |                |<-- 使用者資料 --------|
  |<-- 登入成功 ---|                      |

Refresh Token 機制

為什麼需要 Refresh Token?

Access Token 壽命短(例如 15 分鐘),過期了怎麼辦?
不能每次都要使用者重新登入吧!

解決方案:
├── Access Token  → 短期(15 分鐘),用來存取 API
└── Refresh Token → 長期(7 天),用來換新的 Access Token

流程:
1. 登入 → 拿到 Access Token + Refresh Token
2. 用 Access Token 存取 API
3. Access Token 過期了
4. 用 Refresh Token 換一組新的 Access Token + Refresh Token
5. 繼續存取 API
// 模擬 Refresh Token 流程
public class TokenService
{
    // 換發新的 Token 組
    public async Task<TokenResponse> RefreshTokenAsync(string refreshToken)
    {
        // 驗證 Refresh Token 是否有效
        var storedToken = await _db.RefreshTokens
            .FirstOrDefaultAsync(t => t.Token == refreshToken);

        // 檢查 Token 是否存在且未過期
        if (storedToken == null || storedToken.ExpiresAt < DateTime.UtcNow)
        {
            // Refresh Token 無效或已過期,需要重新登入
            throw new UnauthorizedAccessException("Refresh Token 已過期,請重新登入");
        }

        // 產生新的 Access Token
        var newAccessToken = GenerateJwtToken(storedToken.UserId);
        // 產生新的 Refresh Token(舊的作廢)
        var newRefreshToken = GenerateRefreshToken();

        // 把舊的 Refresh Token 標記為已使用
        storedToken.IsRevoked = true;
        // 儲存新的 Refresh Token
        await _db.RefreshTokens.AddAsync(new RefreshToken
        {
            Token = newRefreshToken,
            UserId = storedToken.UserId,
            ExpiresAt = DateTime.UtcNow.AddDays(7)
        });
        await _db.SaveChangesAsync();

        // 回傳新的 Token 組
        return new TokenResponse
        {
            AccessToken = newAccessToken,
            RefreshToken = newRefreshToken
        };
    }
}

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:把 JWT 存在 localStorage

// ❌ 錯誤:localStorage 容易被 XSS 攻擊竊取
localStorage.setItem("token", jwtToken);
// 如果網站有 XSS 漏洞,駭客可以用 JavaScript 讀取你的 Token

// ✅ 正確:存在 HttpOnly Cookie 中
// HttpOnly Cookie 無法被 JavaScript 讀取,更安全
// 在伺服器端設定:
// Set-Cookie: token=eyJhbG...; HttpOnly; Secure; SameSite=Strict
// ✅ ASP.NET Core 設定 HttpOnly Cookie
Response.Cookies.Append("token", jwtToken, new CookieOptions
{
    HttpOnly = true,    // JavaScript 無法讀取
    Secure = true,      // 只在 HTTPS 傳送
    SameSite = SameSiteMode.Strict,  // 防止 CSRF
    Expires = DateTimeOffset.UtcNow.AddHours(1)  // 1 小時後過期
});

❌ 錯誤 2:沒有驗證 JWT 簽章

// ❌ 錯誤:只解碼 Token,沒有驗證簽章
var handler = new JwtSecurityTokenHandler();
// 這只是把 Base64 解碼,任何人都可以偽造一個 Token!
var token = handler.ReadJwtToken(tokenString);
var userId = token.Claims.First(c => c.Type == "sub").Value;

// ✅ 正確:用 ValidateToken 驗證簽章
var validationParams = new TokenValidationParameters
{
    // 驗證簽發者
    ValidateIssuer = true,
    ValidIssuer = "my-app",
    // 驗證對象
    ValidateAudience = true,
    ValidAudience = "my-app-users",
    // 驗證簽章(最重要!)
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes("your-super-secret-key-at-least-32-chars!")
    ),
    // 驗證過期時間
    ValidateLifetime = true,
};
// ValidateToken 會驗證簽章、過期時間等
var principal = handler.ValidateToken(tokenString, validationParams, out _);

❌ 錯誤 3:JWT 放敏感資料

❌ 錯誤:把密碼或信用卡放在 JWT Payload 中
{
  "sub": "1234",
  "password": "mypassword123",  ← 任何人都能看到!
  "creditCard": "4111-1111-1111-1111"  ← 超危險!
}

JWT 的 Payload 只是 Base64 編碼,不是加密!
任何人拿到 Token 都可以解碼看到所有內容。

✅ 正確:只放不敏感的識別資訊
{
  "sub": "1234",
  "name": "小明",
  "role": "user"
}

💡 重點整理

概念 說明
Authentication 驗證身份(你是誰)
Authorization 授權(你能做什麼)
Session-based 伺服器端存狀態,用 Cookie 傳 Session ID
JWT 無狀態的 Token,包含 Header.Payload.Signature
OAuth 2.0 第三方登入的標準協議(LINE、Google 登入)
Refresh Token 用來換發新 Access Token,避免頻繁重新登入
HttpOnly Cookie 最安全的 Token 儲存方式

💡 大家的想法 · 0

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