.NET 6 Web API中Session使用报错及替代内存存储方案咨询
.NET 6 Web API中Session使用报错及替代内存存储方案咨询
首先得说清楚:Web API默认是无状态设计,Session是基于Cookie的有状态机制,这俩天生不太搭,你遇到报错大概率是中间件配置顺序不对,或者客户端没处理Cookie导致的。
先给你解决Session报错的可能方案(如果非要用的话):
- 检查中间件顺序:
app.UseSession()必须放在app.UseRouting()之后,app.UseAuthorization()之前,跨域场景还要额外配置Cookie和CORS规则。完整配置示例如下:builder.Services.AddSession(option =>{ option.IdleTimeout = TimeSpan.FromMinutes(10); option.Cookie.IsEssential = true; // 跨域场景需要设置SameSite和Secure策略 option.Cookie.SameSite = SameSiteMode.None; option.Cookie.SecurePolicy = CookieSecurePolicy.Always; }); // 配置CORS允许携带凭据 builder.Services.AddCors(options => { options.AddPolicy("AllowCredentials", policy => { policy.WithOrigins("你的前端域名") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); var app = builder.Build(); app.UseHttpsRedirection(); app.UseCors("AllowCredentials"); // 先启用CORS app.UseRouting(); app.UseSession(); // 务必放在正确位置 app.UseAuthorization(); app.MapControllers(); app.Run(); - 客户端要支持Cookie:比如前端用axios时要设置
withCredentials: true;Postman里要开启「Settings」→「Cookies」→「Enable Cookies」,请求时自动携带Cookie。
不过说实话,Web API不推荐用Session,因为违背了无状态的设计原则,而且集群部署时Session还会有数据共享问题。下面给你推荐几个除了数据库之外的更适合API的存储方案:
1. 内存缓存(IMemoryCache)
适合单服务器部署的场景,简单易用,.NET 6默认已经注册了该服务:
// 在控制器中注入使用 private readonly IMemoryCache _cache; public YourController(IMemoryCache cache) { _cache = cache; } // 存储验证码 _cache.Set("UserCode_123", "ABC123", TimeSpan.FromMinutes(10)); // 验证时取出并校验 if (_cache.TryGetValue("UserCode_123", out string storedCode) && storedCode == userSubmittedCode) { // 验证通过后删除缓存 _cache.Remove("UserCode_123"); return Ok("验证成功"); }
优点:轻量、速度快;缺点:服务器重启数据丢失,多服务器集群无法共享缓存。
2. 分布式缓存(IDistributedCache)
如果是多服务器集群部署,推荐用这个,比如Redis、SQL Server缓存等,数据存在外部服务,重启服务器也不会丢失:
// 注册Redis分布式缓存(需先安装Microsoft.Extensions.Caching.StackExchangeRedis包) builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = "localhost:6379"; // 你的Redis服务地址 options.InstanceName = "ApiCacheInstance"; }); // 在控制器中注入使用 private readonly IDistributedCache _distributedCache; public YourController(IDistributedCache distributedCache) { _distributedCache = distributedCache; } // 异步存储验证码 await _distributedCache.SetStringAsync("UserCode_123", "ABC123", new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) }); // 异步验证验证码 var storedCode = await _distributedCache.GetStringAsync("UserCode_123"); if (storedCode == userSubmittedCode) { await _distributedCache.RemoveAsync("UserCode_123"); return Ok("验证成功"); }
优点:支持集群共享、数据持久化;缺点:需要额外部署缓存服务(比如Redis)。
3. JWT令牌携带加密数据
适合完全无状态的API场景,把需要验证的代码加密后放在JWT的Payload里,客户端每次请求携带令牌,服务端解密校验:
// 注册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 claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, "用户ID"), new Claim("VerificationCode", "ABC123") // 存入验证码 }; var token = new JwtSecurityToken( issuer: builder.Configuration["Jwt:Issuer"], audience: builder.Configuration["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddMinutes(10), signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])), SecurityAlgorithms.HmacSha256) ); var tokenString = new JwtSecurityTokenHandler().WriteToken(token); // 验证时从令牌中提取验证码 [Authorize] [HttpPost("verify")] public IActionResult Verify([FromBody] VerifyRequest request) { var codeClaim = User.Claims.FirstOrDefault(c => c.Type == "VerificationCode"); if (codeClaim != null && codeClaim.Value == request.Code) { return Ok("验证成功"); } return BadRequest("验证码错误"); }
优点:完全无状态,适合API场景,支持集群;缺点:Payload不能存过大数据,令牌生成后无法修改,只能等过期或生成新令牌。
4. 静态线程安全字典(仅测试用)
如果只是临时测试可以用,但生产环境绝对不推荐,因为存在线程安全隐患,且服务器重启数据丢失:
public static class TempStorage { // 使用线程安全的ConcurrentDictionary public static ConcurrentDictionary<string, string> VerificationCodes = new ConcurrentDictionary<string, string>(); } // 存储验证码 TempStorage.VerificationCodes.TryAdd("UserCode_123", "ABC123"); // 验证验证码 if (TempStorage.VerificationCodes.TryGetValue("UserCode_123", out string storedCode) && storedCode == request.Code) { TempStorage.VerificationCodes.TryRemove("UserCode_123", out _); return Ok("验证成功"); }
总结一下:单服务器优先用IMemoryCache;集群部署选分布式缓存(Redis);想要完全无状态就用JWT。Session尽量别在Web API里用,坑比较多。
备注:内容来源于stack exchange,提问作者AliKhansari




