天天看點

在 ASP.NET Core Web API 中應用 JWT 通路令牌和重新整理令牌

在 ASP.NET Core Web API 中應用 JWT 通路令牌和重新整理令牌

作為全棧 .NET 開發人員,保護 API 是一項關鍵任務。利用 JWT(JSON Web 令牌)進行身份驗證和授權是一種常見且有效的政策。本文将指導你在 ASP.NET Core Web API 中實作 JWT 通路令牌和重新整理令牌。

為什麼要使用 JWT?

JWT 令牌是緊湊的 URL 安全令牌,易于在各方之間轉移。它們是自包含的,這意味着它們自身内部攜帶資訊,進而減少了對伺服器端會話存儲的需求。

設定項目

首先,建立一個新的 ASP.NET Core Web API 項目:

dotnet new webapi -n JwtAuthDemo 
cd JwtAuthDemo
           

添加必要的 NuGet 包:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer 
dotnet add package Microsoft.IdentityModel.Tokens 
dotnet add package System.IdentityModel.Tokens.Jwt
           

配置 JWT 身份驗證

在 中,添加 JWT 設定:appsettings.json

{
"JwtSettings": {
"SecretKey": "your_secret_key",
"Issuer": "your_issuer",
"Audience": "your_audience",
"AccessTokenExpirationMinutes": 30,
"RefreshTokenExpirationDays": 7
 }
}
           

更新以配置 JWT 身份驗證:Program.cs

var builder = WebApplication.CreateBuilder(args);

// Load JWT settings
var jwtSettings = builder.Configuration.GetSection("JwtSettings").Get<JwtSettings>();
var secretKey = Encoding.UTF8.GetBytes(jwtSettings.SecretKey);

builder.Services.AddAuthentication(options =>
{
 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
 options.TokenValidationParameters = new TokenValidationParameters
 {
 ValidateIssuer = true,
 ValidateAudience = true,
 ValidateIssuerSigningKey = true,
 ValidateLifetime = true,
 ValidIssuer = jwtSettings.Issuer,
 ValidAudience = jwtSettings.Audience,
 IssuerSigningKey = new SymmetricSecurityKey(secretKey)
 };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();
           

建立 JWT 令牌服務

建立一個新類來處理令牌的建立和驗證:JwtTokenService

public class JwtTokenService
{
private readonly JwtSettings _jwtSettings;

public JwtTokenService(IOptions<JwtSettings> jwtSettings)
 {
 _jwtSettings = jwtSettings.Value;
 }

public string GenerateAccessToken(IEnumerable<Claim> claims)
 {
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

var token = new JwtSecurityToken(
 issuer: _jwtSettings.Issuer,
 audience: _jwtSettings.Audience,
 claims: claims,
 expires: DateTime.Now.AddMinutes(_jwtSettings.AccessTokenExpirationMinutes),
 signingCredentials: creds);

return new JwtSecurityTokenHandler().WriteToken(token);
 }

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

實作身份驗證端點

建立一個新控制器來處理登入和令牌重新整理:AuthController

[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly JwtTokenService _jwtTokenService;

public AuthController(JwtTokenService jwtTokenService)
 {
 _jwtTokenService = jwtTokenService;
 }

 [HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest request)
 {
// Assuming the user is authenticated successfully
var claims = new[]
 {
new Claim(ClaimTypes.Name, request.Username),
// Add other claims as needed
 };

var accessToken = _jwtTokenService.GenerateAccessToken(claims);
var refreshToken = _jwtTokenService.GenerateRefreshToken();

// Save or update the refresh token in the database

return Ok(new { AccessToken = accessToken, RefreshToken = refreshToken });
 }

 [HttpPost("refresh")]
public IActionResult Refresh([FromBody] TokenRequest request)
 {
// Validate the refresh token and generate a new access token
// This should include checking the refresh token against the stored value in the database

var claims = new[]
 {
new Claim(ClaimTypes.Name, "username") // Replace with actual username from the refresh token
 };

var newAccessToken = _jwtTokenService.GenerateAccessToken(claims);
           

重新整理令牌終結點

在 中,我們需要實作重新整理令牌邏輯。這通常涉及驗證重新整理令牌,確定其未過期,然後生成新的通路令牌。以下是您如何做到的:AuthController

[HttpPost("refresh")]
public IActionResult Refresh([FromBody] TokenRequest request)
{
// Validate the refresh token (retrieve the stored refresh token from the database)
var storedRefreshToken = GetStoredRefreshToken(request.RefreshToken);

if (storedRefreshToken ==  || storedRefreshToken.ExpirationDate < DateTime.UtcNow)
 {
return Unauthorized("Invalid or expired refresh token.");
 }

// Assuming you have the username or user ID stored with the refresh token
var claims = new[]
 {
new Claim(ClaimTypes.Name, storedRefreshToken.Username)
// Add other claims as needed
 };

var newAccessToken = _jwtTokenService.GenerateAccessToken(claims);
var newRefreshToken = _jwtTokenService.GenerateRefreshToken();

// Update the stored refresh token
 storedRefreshToken.Token = newRefreshToken;
 storedRefreshToken.ExpirationDate = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpirationDays);
SaveRefreshToken(storedRefreshToken);

return Ok(new { AccessToken = newAccessToken, RefreshToken = newRefreshToken });
}
           

模型

為您的請求和設定建立必要的模型。例如, , , 和 :LoginRequestTokenRequestJwtSettings

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

public class TokenRequest 
{ 
public string AccessToken { get; set; } 
public string RefreshToken { get; set; } 
} 

public class JwtSettings 
{ 
public string SecretKey { get; set; } 
public string Issuer { get; set; } 
public string Audience { get; set; } 
public int AccessTokenExpirationMinutes { get; set; } 
public int RefreshTokenExpirationDays { get; set; } 
}
           

存儲和驗證重新整理令牌

為簡單起見,我們假設您有一種方法可以從資料庫中存儲和檢索重新整理令牌。下面是使用假設資料通路層的基本示例:

public class RefreshToken
{
public string Token { get; set; }
public string Username { get; set; }
public DateTime ExpirationDate { get; set; }
}

public RefreshToken GetStoredRefreshToken(string refreshToken)
{
// Implement your logic to retrieve the refresh token from the database
// For example, using Entity Framework Core:
// return _context.RefreshTokens.SingleOrDefault(rt => rt.Token == refreshToken);
return ;
}

public void SaveRefreshToken(RefreshToken refreshToken)
{
// Implement your logic to save the refresh token to the database
// For example, using Entity Framework Core:
// _context.RefreshTokens.Update(refreshToken);
// _context.SaveChanges();
}
           

總結

有了這些元件,就可以在 ASP.NET Core Web API 中為基于 JWT 的身份驗證提供可靠的設定。以下是我們所涵蓋内容的簡要概述:

  1. 項目設定:建立了新的 ASP.NET Core Web API 項目,并添加了必要的 NuGet 包。
  2. JWT 配置:在 和 中配置了 JWT 設定。appsettings.jsonProgram.cs
  3. 令牌服務:用于生成和驗證令牌。JwtTokenService
  4. 身份驗證終結點:使用登入和令牌重新整理終結點建立。AuthController
  5. 模型和存儲:定義了請求的模型,并實作了用于存儲和檢索重新整理令牌的方法。

通過執行這些步驟,您可以確定您的 API 是安全的,并且由于重新整理令牌機制,使用者可以維護其會話,而無需不斷重新進行身份驗證。此設定不僅可以增強安全性,還可以通過提供對受保護資源的無縫通路來改善使用者體驗。

保護您的端點

配置 JWT 身份驗證後,您需要保護 API 端點,以確定隻有經過身份驗證的使用者才能通路它們。您可以使用該屬性來保護控制器或特定操作。[Authorize]

例如,要保護整個:WeatherForecastController

[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
// Your actions here
}
           

處理令牌過期

JWT 是有過期時間的,一旦過期,用戶端需要使用 refresh token 來擷取新的通路 Token。正确處理令牌過期對于維護安全性和使用者體驗至關重要。

下面是一個示例,說明如何在用戶端處理令牌過期(例如,在前端應用程式中使用 JavaScript/TypeScript):

async function fetchWithAuth(url, options = {}) {
let response = await fetch(url, options);

if (response.status === 401) {
// Token might be expired, try to refresh it
const refreshResponse = await fetch('/api/auth/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
 },
body: JSON.stringify({
accessToken: localStorage.getItem('accessToken'),
refreshToken: localStorage.getItem('refreshToken')
 })
 });

if (refreshResponse.ok) {
const { accessToken, refreshToken } = await refreshResponse.json();
 localStorage.setItem('accessToken', accessToken);
 localStorage.setItem('refreshToken', refreshToken);

// Retry the original request
 options.headers = {
 ...options.headers,
'Authorization': `Bearer ${accessToken}`
 };
 response = await fetch(url, options);
 }
 }

return response;
}
           

管理重新整理令牌的最佳實踐

  1. 安全地存儲重新整理令牌:重新整理令牌是敏感的,應安全存儲。最好使用僅限 HTTP 的 cookie 來存儲重新整理令牌,因為它們不太容易受到 XSS 攻擊。
  2. 輪換重新整理令牌:每次使用重新整理令牌時,都會生成一對新的通路和重新整理令牌。這限制了被盜重新整理令牌的生命周期。
  3. 已用令牌黑名單:保留無效令牌清單,以防止重複使用。這可以使用資料庫或記憶體中存儲來實作。
  4. 限制重新整理嘗試次數:實施一種機制來限制可用于防止濫用的重新整理令牌的次數。

使用僅限 HTTP 的 Cookie 的示例

以下示例說明了如何在 HTTP 中使用僅限 HTTP 的 Cookie 發出和處理重新整理令牌:AuthController

[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest request)
{
// Assuming the user is authenticated successfully
var claims = new[]
 {
new Claim(ClaimTypes.Name, request.Username),
// Add other claims as needed
 };

var accessToken = _jwtTokenService.GenerateAccessToken(claims);
var refreshToken = _jwtTokenService.GenerateRefreshToken();

// Save or update the refresh token in the database
SaveRefreshToken(new RefreshToken
 {
 Token = refreshToken,
 Username = request.Username,
 ExpirationDate = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpirationDays)
 });

// Set the refresh token as an HTTP-only cookie
 Response.Cookies.Append("refreshToken", refreshToken, new CookieOptions
 {
 HttpOnly = true,
 Secure = true, // Set to true in production
 Expires = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpirationDays)
 });

return Ok(new { AccessToken = accessToken });
}

[HttpPost("refresh")]
public IActionResult Refresh()
{
var refreshToken = Request.Cookies["refreshToken"];
if (string.IsOrEmpty(refreshToken))
 {
return Unauthorized("Refresh token is missing.");
 }

var storedRefreshToken = GetStoredRefreshToken(refreshToken);
if (storedRefreshToken ==  || storedRefreshToken.ExpirationDate < DateTime.UtcNow)
 {
return Unauthorized("Invalid or expired refresh token.");
 }

var claims = new[]
 {
new Claim(ClaimTypes.Name, storedRefreshToken.Username)
 };

var newAccessToken = _jwtTokenService.GenerateAccessToken(claims);
var newRefreshToken = _jwtTokenService.GenerateRefreshToken
           

輪換重新整理令牌

如前所述,在每次使用時輪換重新整理令牌是一種很好的做法。這意味着每次使用重新整理令牌擷取新的通路令牌時,也應生成新的重新整理令牌并将其發送到用戶端。這限制了重新整理令牌遭到破壞時的潛在損害。

在 中更新重新整理令牌終結點以處理令牌輪換:AuthController

[HttpPost("refresh")]
public IActionResult Refresh()
{
var oldRefreshToken = Request.Cookies["refreshToken"];
if (string.IsOrEmpty(oldRefreshToken))
 {
return Unauthorized("Refresh token is missing.");
 }

var storedRefreshToken = GetStoredRefreshToken(oldRefreshToken);
if (storedRefreshToken ==  || storedRefreshToken.ExpirationDate < DateTime.UtcNow)
 {
return Unauthorized("Invalid or expired refresh token.");
 }

var claims = new[]
 {
new Claim(ClaimTypes.Name, storedRefreshToken.Username)
 };

var newAccessToken = _jwtTokenService.GenerateAccessToken(claims);
var newRefreshToken = _jwtTokenService.GenerateRefreshToken();

// Update the stored refresh token
 storedRefreshToken.Token = newRefreshToken;
 storedRefreshToken.ExpirationDate = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpirationDays);
SaveRefreshToken(storedRefreshToken);

// Set the new refresh token as an HTTP-only cookie
 Response.Cookies.Append("refreshToken", newRefreshToken, new CookieOptions
 {
 HttpOnly = true,
 Secure = true, // Set to true in production
 Expires = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpirationDays)
 });

return Ok(new { AccessToken = newAccessToken });
}
           

處理使用者登出

當使用者登出時,您應使其重新整理令牌失效,以防止其用于生成新的通路令牌。您可以通過從資料庫中删除重新整理令牌并清除僅限 HTTP 的 Cookie 來實作此目的。

将登出端點添加到您的:AuthController

[HttpPost("logout")]
public IActionResult Logout()
{
var refreshToken = Request.Cookies["refreshToken"];
if (!string.IsOrEmpty(refreshToken))
 {
// Remove the refresh token from the database
RemoveRefreshToken(refreshToken);

// Clear the HTTP-only cookie
 Response.Cookies.Delete("refreshToken");
 }

return NoContent();
}
           

提高安全性

以下是一些需要牢記的其他安全注意事項:

  1. **使用 HTTPS:**始終使用 HTTPS 對用戶端和伺服器之間傳輸的資料(包括令牌)進行加密。這樣可以防止中間人攻擊。
  2. 短期通路令牌:保持通路令牌的短期通路令牌(例如,15-30 分鐘)。這限制了攻擊者設法竊取通路令牌的時間視窗。
  3. 更改密碼時撤銷令牌:如果使用者更改了密碼或執行了敏感操作,請撤銷與使用者關聯的所有重新整理令牌,以防止未經授權的通路。
  4. 速率限制:對身份驗證終結點實施速率限制,以防止暴力攻擊。

示例:Entity Framework Core 內建

對于實際應用程式,通常使用資料庫來存儲重新整理令牌。下面是一個示例,說明如何內建 Entity Framework Core 來管理重新整理令牌:

  1. 定義 RefreshToken 實體:
public class RefreshToken 
{ 
public int Id { get; set; } 
public string Token { get; set; } 
public string Username { get; set; } 
public DateTime ExpirationDate { get; set; } 
}
           

2. 将 DbSet 添加到 DbContext:

public class ApplicationDbContext : DbContext
{
public DbSet<RefreshToken> RefreshTokens { get; set; }

public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
 : base(options)
 {
 }
}
           

3. 實作管理重新整理令牌的方法:

public class RefreshTokenService
{
private readonly ApplicationDbContext _context;

public RefreshTokenService(ApplicationDbContext context)
 {
 _context = context;
 }

public void SaveRefreshToken(RefreshToken refreshToken)
 {
 _context.RefreshTokens.Update(refreshToken);
 _context.SaveChanges();
 }

public RefreshToken GetStoredRefreshToken(string token)
 {
return _context.RefreshTokens.SingleOrDefault(rt => rt.Token == token);
 }

public void RemoveRefreshToken(string token)
 {
var refreshToken = GetStoredRefreshToken(token);
if (refreshToken != )
 {
 _context.RefreshTokens.Remove(refreshToken
           

如果你喜歡我的文章,請給我一個贊!謝謝