在 .NET 8.0 中实现 JWT 刷新令牌

介绍

在 Web 开发领域,安全是重中之重。JSON Web Tokens (JWT) 已成为在各方之间安全传输信息的热门选择。然而,在 JWT 过期后,如何维护用户会话并避免频繁登录至关重要。这正是 JWT 刷新令牌应运而生的地方。

在本文中,我们将指导您在 .NET 8.0 环境中实现 JWT 刷新令牌。我们将通过完整的示例介绍每个步骤,以确保清晰地理解整个过程。

JWT 和刷新令牌

JSON Web Tokens (JWT) 由三部分组成:标头、有效负载和签名。它们经过数字签名以验证其真实性,并包含声明,这些声明是关于实体(用户)和其他数据的声明。JWT 具有到期时间 (exp),到期后将被视为无效。刷新令牌用于在原始令牌到期后获取新的 JWT,而无需用户重新输入其凭据。

设置.NET项目

让我们使用 JWT 和刷新令牌创建一个简单的身份验证系统。

首先,确保您已安装必要的包(System.IdentityModel.Tokens.Jwt 和 Microsoft.AspNetCore.Authentication.JwtBearer)。

模型

创建两个模型来表示登录请求和包含令牌的响应。

// LoginModel.cs

public class LoginModel

{

public string Username { get; set; }

public string Password { get; set; }

}

// TokenResponse.cs

public class TokenResponse

{

public string AccessToken { get; set; }

public string RefreshToken { get; set; }

}

授权控制器

创建一个负责处理身份验证和令牌生成的 AuthController。

ApiController

Route("\[controller\]")

public class AuthController : ControllerBase

{

private readonly IConfiguration _config;

private readonly IUserService _userService;

public AuthController(IConfiguration config, IUserService userService)

{

_config = config;

_userService = userService;

}

HttpPost("login")

public IActionResult Login(LoginModel loginModel)

{

// Authenticate user

var user = _userService.Authenticate(loginModel.Username, loginModel.Password);

if (user == null)

return Unauthorized();

// Generate tokens

var accessToken = TokenUtils.GenerateAccessToken(user, _config["Jwt:Secret"]);

var refreshToken = TokenUtils.GenerateRefreshToken();

// Save refresh token (for demo purposes, this might be stored securely in a database)

// _userService.SaveRefreshToken(user.Id, refreshToken);

var response = new TokenResponse

{

AccessToken = accessToken,

RefreshToken = refreshToken

};

return Ok(response);

}

HttpPost("refresh")

public IActionResult Refresh(TokenResponse tokenResponse)

{

// For simplicity, assume the refresh token is valid and stored securely

// var storedRefreshToken = _userService.GetRefreshToken(userId);

// Verify refresh token (validate against the stored token)

// if (storedRefreshToken != tokenResponse.RefreshToken)

// return Unauthorized();

// For demonstration, let's just generate a new access token

var newAccessToken = TokenUtils.GenerateAccessTokenFromRefreshToken(tokenResponse.RefreshToken, _config["Jwt:Secret"]);

var response = new TokenResponse

{

AccessToken = newAccessToken,

RefreshToken = tokenResponse.RefreshToken // Return the same refresh token

};

return Ok(response);

}

}

Token Utility

创建一个实用程序类 TokenUtils 来处理令牌的生成和验证。

public static class TokenUtils

{

public static string GenerateAccessToken(User user, string secret)

{

var tokenHandler = new JwtSecurityTokenHandler();

var key = Encoding.ASCII.GetBytes(secret);

var tokenDescriptor = new SecurityTokenDescriptor

{

Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),

Expires = DateTime.UtcNow.AddMinutes(15), // Token expiration time

SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)

};

var token = tokenHandler.CreateToken(tokenDescriptor);

return tokenHandler.WriteToken(token);

}

public static string GenerateRefreshToken()

{

var randomNumber = new byte[32];

using var rng = RandomNumberGenerator.Create();

rng.GetBytes(randomNumber);

return Convert.ToBase64String(randomNumber);

}

public static string GenerateAccessTokenFromRefreshToken(string refreshToken, string secret)

{

// Implement logic to generate a new access token from the refresh token

// Verify the refresh token and extract necessary information (e.g., user ID)

// Then generate a new access token

// For demonstration purposes, return a new token with an extended expiry

var tokenHandler = new JwtSecurityTokenHandler();

var key = Encoding.ASCII.GetBytes(secret);

var tokenDescriptor = new SecurityTokenDescriptor

{

Expires = DateTime.UtcNow.AddMinutes(15), // Extend expiration time

SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)

};

var token = tokenHandler.CreateToken(tokenDescriptor);

return tokenHandler.WriteToken(token);

}

}

用户服务(示例)

为了演示目的,这里有一个提供用户身份验证的简单 UserService。

public interface IUserService

{

User Authenticate(string username, string password);

// void SaveRefreshToken(int userId, string refreshToken);

// string GetRefreshToken(int userId);

}

public class UserService : IUserService

{

private readonly List<User> _users = new List<User>

{

new User { Id = 1, Username = "user1", Password = "password1" }

};

public User Authenticate(string username, string password)

{

var user = _users.SingleOrDefault(x => x.Username == username && x.Password == password);

return user;

}

// For demo purposes - methods to save and retrieve refresh tokens

}

用户模型(示例)

创建一个简单的用户模型。

public class User

{

public int Id { get; set; }

public string Username { get; set; }

public string Password { get; set; }

}

启动配置

在您的 Startup.cs 中,配置 JWT 身份验证。

public void ConfigureServices(IServiceCollection services)

{

// ...

var secret = Configuration["Jwt:Secret"];

var key = Encoding.ASCII.GetBytes(secret);

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)

.AddJwtBearer(options =>

{

options.TokenValidationParameters = new TokenValidationParameters

{

ValidateIssuer = false,

ValidateAudience = false,

ValidateLifetime = true,

ValidateIssuerSigningKey = true,

IssuerSigningKey = new SymmetricSecurityKey(key)

};

});

// ...

}

重要提示

这是一个用于演示的基本实现。在生产环境中,您应该增强安全性,安全地处理令牌存储,并实现适当的验证和错误处理。

为了更好的安全性,请考虑使用 IdentityServer4 库或其他成熟的身份验证解决方案。

结论

在 .NET 8.0 中实现 JWT 刷新令牌涉及配置身份验证中间件、在身份验证时生成令牌以及根据需要刷新过期令牌。此过程允许无缝令牌更新,而无需用户重复登录,从而增强了安全性。

记住要安全地处理令牌存储并实施适当的验证逻辑,以确保身份验证系统的安全性和完整性。

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。