ASP.NET Core JWT能否实现无需存储的Refresh Token机制?
可以在ASP.NET Core JWT中实现无存储Refresh Token的机制
当然可以!你在ASP.NET Framework里实现的这种把用户身份信息和过期时间直接序列化到Refresh Token中、无需持久化存储的方案,完全可以在ASP.NET Core的JWT认证体系中复刻,核心思路和你原来的实现高度一致:将用户票据(包含身份信息和过期时间)直接编码进Refresh Token,验证时反序列化并检查有效性,同时刷新过期时间。
核心原理回顾
你原来的方案本质逻辑是:
- 生成Refresh Token时,把包含用户身份和过期时间的
AuthenticationTicket序列化后作为Token内容 - 刷新Token时,反序列化这个Ticket,验证过期时间,然后重新设置过期时间并生成新的Refresh Token
- 全程不需要将Token存到数据库或其他存储介质,所有必要信息都封装在Token本身里
ASP.NET Core中的实现步骤
下面是适配ASP.NET Core认证体系的完整代码实现:
1. 自定义Refresh Token处理服务
我们可以实现一个专门的服务来处理Refresh Token的生成、验证和刷新,对应你原来的Create和Receive方法逻辑:
using Microsoft.AspNetCore.Authentication; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; public class RefreshTokenService { private readonly IConfiguration _configuration; public RefreshTokenService(IConfiguration configuration) { _configuration = configuration; } // 生成Refresh Token:将用户身份票据序列化后转为Base64字符串 public string GenerateRefreshToken(ClaimsPrincipal principal) { var refreshTokenExpiryDays = Convert.ToInt32(_configuration["Jwt:RefreshTokenExpiryDays"]); // 创建包含用户身份和过期时间的认证票据 var ticket = new AuthenticationTicket( principal, new AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddDays(refreshTokenExpiryDays), IsPersistent = true }, JwtBearerDefaults.AuthenticationScheme); // 序列化票据为字节数组再转Base64(和Framework的SerializeTicket逻辑一致) var serializer = new TicketSerializer(); var ticketBytes = serializer.Serialize(ticket); return Convert.ToBase64String(ticketBytes); } // 验证并刷新Refresh Token:反序列化票据、检查有效性、更新过期时间 public AuthenticationTicket? ValidateAndRefreshToken(string refreshToken) { try { // 反序列化Token为认证票据 var ticketBytes = Convert.FromBase64String(refreshToken); var serializer = new TicketSerializer(); var ticket = serializer.Deserialize(ticketBytes); if (ticket == null) return null; // 检查Token是否已过期 if (ticket.Properties.ExpiresUtc <= DateTime.UtcNow) return null; // 刷新Token的过期时间 ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Convert.ToInt32(_configuration["Jwt:RefreshTokenExpiryDays"])); return ticket; } catch { // 序列化/反序列化失败,返回null表示Token无效 return null; } } }
2. 配置认证服务与依赖注入
在Program.cs中注册自定义服务并配置JWT认证:
var builder = WebApplication.CreateBuilder(args); // 注册RefreshTokenService到依赖注入容器 builder.Services.AddScoped<RefreshTokenService>(); // 配置JWT认证 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; }); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); // 其他中间件配置... app.Run();
3. 实现刷新Token的API接口
添加一个API控制器来处理Refresh Token的刷新请求,对应你原来的ReceiveAsync逻辑:
using Microsoft.AspNetCore.Mvc; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; [ApiController] [Route("api/auth")] public class AuthController : ControllerBase { private readonly RefreshTokenService _refreshTokenService; private readonly IConfiguration _configuration; public AuthController(RefreshTokenService refreshTokenService, IConfiguration configuration) { _refreshTokenService = refreshTokenService; _configuration = configuration; } [HttpPost("refresh-token")] public IActionResult RefreshToken([FromForm] string refreshToken) { if (string.IsNullOrEmpty(refreshToken)) return BadRequest("Invalid refresh token"); var ticket = _refreshTokenService.ValidateAndRefreshToken(refreshToken); if (ticket == null) return Unauthorized("Refresh token expired or invalid"); // 生成新的Access Token var newAccessToken = GenerateAccessToken(ticket.Principal.Claims); // 生成新的Refresh Token var newRefreshToken = _refreshTokenService.GenerateRefreshToken(ticket.Principal); return Ok(new { AccessToken = newAccessToken, RefreshToken = newRefreshToken }); } // 辅助方法:生成JWT格式的Access Token private string GenerateAccessToken(IEnumerable<Claim> claims) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var accessTokenExpiryMinutes = Convert.ToInt32(_configuration["Jwt:AccessTokenExpiryMinutes"]); var token = new JwtSecurityToken( issuer: _configuration["Jwt:Issuer"], audience: _configuration["Jwt:Audience"], claims: claims, expires: DateTime.UtcNow.AddMinutes(accessTokenExpiryMinutes), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } }
关键注意事项
- 安全性:因为Refresh Token包含用户敏感身份信息,务必确保所有请求通过HTTPS传输,防止Token被拦截。另外,建议设置合理的Refresh Token过期时间,若用户修改密码等敏感信息,可通过缩短现有Token有效期来降低泄露风险(纯无存储方案无法直接失效旧Token,只能靠过期时间控制)。
- Grant Type判断:你原代码中只在非
refresh_token的授权类型下生成Refresh Token的逻辑,可以在登录接口中实现(比如用户使用密码登录时生成,刷新Token时仅验证后生成新Token)。 - 序列化兼容性:ASP.NET Core的
TicketSerializer和Framework的实现兼容,若你需要迁移旧系统,原有的Refresh Token可以直接在Core中验证。
内容的提问来源于stack exchange,提问作者Mubashir Khan
相关产品推荐
相关产品推荐

