You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

.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

火山引擎 最新活动