使用 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 的優點在於
其無狀態性,適合分布式系統中的身份驗證,但同時也需要考慮安全性問題,例如令牌過期時間、刷新
令牌的安全管理等。