Google Code Prettify

JWT 作為身份驗證要如何作登入、登出及重新驗證?

使用 JWT(JSON Web Token)作為身份驗證的系統中,登錄、登出和重新驗證的實現涉及生
成、驗證和管理令牌。以下是實現這些功能的詳細步驟。

登錄
登錄過程包括驗證用戶憑證並生成一個 JWT,該 JWT 會包含用戶身份信息和其他聲明(claims)。

1. 建立登入端點
創建一個登入端點,該端點接受用戶憑證(例如,電子郵件和密碼)並返回一個 JWT。

csharp

[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest loginRequest)
{
    // 1. 驗證用戶憑證
    var user = _userService.ValidateUser(loginRequest.Email, loginRequest.Password);
    if (user == null)
    {
        return Unauthorized();
    }

    // 2. 生成 JWT
    var token = GenerateJwtToken(user);

    // 3. 返回 JWT
    return Ok(new { Token = token });
}

private string GenerateJwtToken(User user)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes("Your_Secret_Key");
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new Claim[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Name, user.Username)
        }),
        Expires = DateTime.UtcNow.AddHours(1),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
        Issuer = "your_issuer",
        Audience = "your_audience"
    };
    var token = tokenHandler.CreateToken(tokenDescriptor);
    return tokenHandler.WriteToken(token);
}

2. 定義登入請求模型
csharp

public class LoginRequest
{
    public string Email { get; set; }
    public string Password { get; set; }
}

登出
由於 JWT 是無狀態的,不需要服務器端存儲,因此登出通常是前端的操作。登出只需要在前端刪除 JWT。

前端處理登出
javascript

function logout() {
    localStorage.removeItem("jwtToken");
    window.location.href = "/login";
}



重新驗證
重新驗證(令牌刷新)需要提供一個新的 JWT,這通常涉及到一個刷新令牌。以下是實現令牌刷新的步驟。

1. 創建刷新令牌
每當用戶登錄時,除了返回 JWT,還返回一個刷新令牌。

csharp

[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest loginRequest)
{
    var user = _userService.ValidateUser(loginRequest.Email, loginRequest.Password);
    if (user == null)
    {
        return Unauthorized();
    }

    var token = GenerateJwtToken(user);
    var refreshToken = GenerateRefreshToken();

    // 將刷新令牌保存到數據庫或內存中,關聯用戶
    _userService.SaveRefreshToken(user.Id, refreshToken);

    return Ok(new { Token = token, RefreshToken = refreshToken });
}

private string GenerateRefreshToken()
{
    var randomNumber = new byte[32];
    using (var rng = RandomNumberGenerator.Create())
    {
        rng.GetBytes(randomNumber);
        return Convert.ToBase64String(randomNumber);
    }
}

2. 創建刷新令牌請求端點
csharp

[HttpPost("refresh")]
public IActionResult Refresh([FromBody] RefreshRequest refreshRequest)
{
    var principal = GetPrincipalFromExpiredToken(refreshRequest.Token);
    var userId = principal.FindFirst(ClaimTypes.NameIdentifier).Value;
    
    var savedRefreshToken = _userService.GetRefreshToken(userId); // 從數據庫中獲取保存的刷新令牌

    if (savedRefreshToken != refreshRequest.RefreshToken)
    {
        return Unauthorized();
    }

    var newJwtToken = GenerateJwtToken(principal);
    var newRefreshToken = GenerateRefreshToken();
    
    _userService.SaveRefreshToken(userId, newRefreshToken); // 更新刷新令牌

    return Ok(new { Token = newJwtToken, RefreshToken = newRefreshToken });
}

private ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
{
    var tokenValidationParameters = new TokenValidationParameters
    {
        ValidateAudience = false,
        ValidateIssuer = false,
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Your_Secret_Key")),
        ValidateLifetime = false // 我們正在驗證過期的令牌,因此這裡不驗證過期時間
    };

    var tokenHandler = new JwtSecurityTokenHandler();
    var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out SecurityToken securityToken);
    var jwtSecurityToken = securityToken as JwtSecurityToken;

    if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
    {
        throw new SecurityTokenException("Invalid token");
    }

    return principal;
}

3. 定義刷新請求模型
csharp

public class RefreshRequest
{
    public string Token { get; set; }
    public string RefreshToken { get; set; }
}

總結
這樣的設計使得 JWT 可以用於用戶身份驗證,並且支持登錄、登出和重新驗證功能。JWT 的優點在於
其無狀態性,適合分布式系統中的身份驗證,但同時也需要考慮安全性問題,例如令牌過期時間、刷新
令牌的安全管理等。