.NET Core 3.1中如何实现JWT存Cookie的登出方法?
JWT本身是无状态令牌,一旦签发无法在服务端直接作废,所以登出的核心是让客户端停止携带有效令牌,同时可通过服务端黑名单机制进一步提升安全性,以下是具体实现步骤:
1. 客户端清除Cookie(必备步骤)
登出的核心操作是删除存储JWT的Cookie,确保后续请求不再携带有效令牌。注意Cookie的名称和配置要和登录时完全匹配,否则可能清除失败。
基础清除代码
[HttpPost("logout")] public IActionResult Logout() { // 清除存储JWT的Cookie,名称需与登录时一致 Response.Cookies.Delete("JwtToken"); // 如果使用了Refresh Token,同步清除对应的Cookie Response.Cookies.Delete("RefreshToken"); return Ok("登出成功"); }
带配置匹配的清除(针对有特殊Cookie配置的场景)
如果登录时设置了Domain、Path等属性,删除时必须指定相同参数:
Response.Cookies.Delete("JwtToken", new CookieOptions { Domain = "your-domain.com", Path = "/", SameSite = SameSiteMode.Strict });
2. 服务端Token黑名单机制(可选,增强安全性)
如果担心已签发的JWT在过期前被滥用,可以实现黑名单存储(推荐用Redis,内存缓存仅适合单实例场景),登出时将令牌加入黑名单,后续认证时校验。
步骤1:配置缓存服务(以Redis为例)
在Program.cs中添加Redis缓存配置:
builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = builder.Configuration.GetConnectionString("Redis"); });
步骤2:登出时将Token加入黑名单
private readonly IDistributedCache _distributedCache; public AuthController(IDistributedCache distributedCache) { _distributedCache = distributedCache; } [HttpPost("logout")] public async Task<IActionResult> Logout() { var jwtToken = Request.Cookies["JwtToken"]; if (!string.IsNullOrEmpty(jwtToken)) { // 解析Token获取过期时间,设置黑名单有效期为Token剩余时长 var tokenHandler = new JwtSecurityTokenHandler(); var jwtSecurityToken = tokenHandler.ReadJwtToken(jwtToken); var expiration = jwtSecurityToken.ValidTo; await _distributedCache.SetStringAsync( $"Blacklist:{jwtToken}", "invalid", new DistributedCacheEntryOptions { AbsoluteExpiration = expiration } ); } // 清除Cookie Response.Cookies.Delete("JwtToken"); return Ok("登出成功"); }
步骤3:自定义校验逻辑验证黑名单
创建自定义JWT验证处理器,在默认校验前检查令牌是否在黑名单:
public class JwtBlacklistValidator : ISecurityTokenValidator { private readonly JwtSecurityTokenHandler _innerHandler = new(); private readonly IDistributedCache _cache; public JwtBlacklistValidator(IDistributedCache cache) { _cache = cache; } public bool CanValidateToken => true; public int MaximumTokenSizeInBytes => _innerHandler.MaximumTokenSizeInBytes; public bool CanReadToken(string securityToken) => _innerHandler.CanReadToken(securityToken); public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken) { // 先检查黑名单,存在则抛出异常 if (_cache.GetString($"Blacklist:{securityToken}") != null) { throw new SecurityTokenInvalidException("令牌已失效"); } // 执行默认JWT校验逻辑 return _innerHandler.ValidateToken(securityToken, validationParameters, out validatedToken); } }
然后在Program.cs中替换默认的JWT验证器:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { var jwtSettings = builder.Configuration.GetSection("JwtSettings"); options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = jwtSettings["Issuer"], ValidateAudience = true, ValidAudience = jwtSettings["Audience"], ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["SecretKey"])) }; // 替换为自定义黑名单验证器 options.SecurityTokenValidators.Clear(); options.SecurityTokenValidators.Add(new JwtBlacklistValidator(builder.Services.BuildServiceProvider().GetRequiredService<IDistributedCache>())); });
3. 登录时的Cookie配置注意事项
为了安全,登录时设置Cookie需开启HttpOnly(防止XSS窃取)和Secure(生产环境仅HTTPS传输):
[HttpPost("login")] public IActionResult Login(LoginRequest model) { // 用户验证逻辑... // 生成JWT var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.UTF8.GetBytes(_configuration["JwtSettings:SecretKey"]); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, model.Username) }), Expires = DateTime.UtcNow.AddHours(1), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); var tokenString = tokenHandler.WriteToken(token); // 写入Cookie Response.Cookies.Append("JwtToken", tokenString, new CookieOptions { HttpOnly = true, Secure = Environment.IsProduction(), SameSite = SameSiteMode.Strict, Expires = DateTime.UtcNow.AddHours(1) }); return Ok("登录成功"); }
内容的提问来源于stack exchange,提问作者arp
相关产品推荐
相关产品推荐

