有状态Cookie

📅 2026/7/7 7:19:52
有状态Cookie
相比上面无状态模式有状态模式需要额外加一些代码用来存储读写和过期清理。如果是单服务实例可以直接存入本地内存但需注意服务重启的话会导致票据丢失用户直接退出登录了。如果服务多实例负载均衡的情况下需要引入分布式存储来共享把票据存入Redis或者Memcached中。如果你坚持本地内存可能会破坏负载均衡策略。集成思路第一步将认证方案注入容器将认证方案例如 Cookie 和它的配置注入容器跟无状态一模一样的套路就不贴代码了参照无状态第1、2步。第二步实现 ITicketStore 接口如果需要使用Cookie的有状态模式框架的CookieAuthenticationOptions选项提供了SessionStore配置来进行实现他是ITicketStore类型这个接口是框架提供给用户扩展的主要用于认证票据存在哪我们需要实现它。public class MemoryTicketStore(private readonly IMemoryCache _cache) : ITicketStore { private const string KeyPrefix auth-session:; // 存 public Task StoreAsync(AuthenticationTicket ticket) { var key ${KeyPrefix}{Guid.NewGuid():N}; RenewAsync(key, ticket); return Task.FromResult(key); } // 更新 public Task RenewAsync(string key, AuthenticationTicket ticket) { var expires ticket.Properties.ExpiresUtc ?? DateTimeOffset.UtcNow.AddHours(8); _cache.Set(key, ticket, new MemoryCacheEntryOptions { AbsoluteExpiration expires }); return Task.CompletedTask; } // 查询 public Task RetrieveAsync(string key) Task.FromResult(_cache.Get(key)); // 删除 public Task RemoveAsync(string key) { _cache.Remove(key); return Task.CompletedTask; } }你同样可以自己实现Redis存储的逻辑只需替换MemoryTicketStore为RedisTicketStore然后修改ITicketStore的注册根本不需要改动身份验证配置。为了方便我使用的内存缓存接下来需要将实现注入容器再将它配置起来。第三步注入并配置// 注入组件 builder.Services.AddMemoryCache(); builder.Services.AddSingletonITicketStore, MemoryTicketStore(); // 配置实例延迟执行防止依赖的服务没注册 builder.Services.AddOptionsCookieAuthenticationOptions(CookieAuthenticationDefaults.AuthenticationScheme) .ConfigureITicketStore((options, store) options.SessionStore store);第四步在控制器中使用在控制器中使用和无状态模式一样使用。2.3 使用 CookieAuthentication 登录实现用Cookie认证的方式登录不需要我们实现生成Cookie框架完全控制会话生命周期SignInAsync()触发标准化的Cookie生成流程登录代码只需要构建principal就行。构建ClaimsPrincipal含用户标识和权限声明调用SignInAsync()传递该主体public async Task Login([FromBody] LoginRequest request) { if (request.Username ! admin || request.Password ! 123456) { return Unauthorized(new { message 用户名或密码错误 }); } var claims new[] { new Claim(ClaimTypes.Name, request.Username), new Claim(ClaimTypes.Role, User) }; var identity new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var principal new ClaimsPrincipal(identity); await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { IsPersistent true, ExpiresUtc DateTimeOffset.UtcNow.AddHours(8) }); // 模式一return Ok(new { message 登录成功 }); return Ok(new { message 登录成功会话已保存在服务端 }); // 模式二当前 }3. 使用JWT的认证方式3.1 集成JWT接下来我们继续看看JWT认证的方式如何在NETCORE中集成和实现其实jwt也是无状态的通常服务器一旦颁发在token过期之前后期是不可控的。集成思路第一步注册JWT服务和集成Cookie认证一样都需要先注册然后启用认证中间件而这里注册的是JWT相关的服务。var authenticationBuilder builder.Services.AddAuthentication(options { // 将默认方案设置为JWT options.DefaultAuthenticateScheme JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme JwtBearerDefaults.AuthenticationScheme; }); authenticationBuilder.AddJwtBearer(options { options.TokenValidationParameters new TokenValidationParameters { ValidateIssuer true, // 验证签发者令牌中的 iss 字段必须与 ValidIssuer 完全匹配 ValidateAudience true, // 是否验证受众保证令牌是发给当前服务的 ValidateLifetime true, // 是否验证令牌有效期 ValidateIssuerSigningKey true, // 是否验证令牌签名 ValidIssuer jwtIssuer, // 合法签发者标识 https://鉴权服务.com ValidAudience jwtAudience, // 受众标识 // 令牌签名验证密钥对称加密 IssuerSigningKey new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JWTjiamimiyao!)) }; // JWT 认证流程中的事件钩子未认证时触发 options.Events new JwtBearerEvents { OnChallenge context { // 移除 WW-Authenticate 避免暴露敏感信息 context.HandleResponse(); context.Response.StatusCode StatusCodes.Status401Unauthorized; context.Response.ContentType application/json; return context.Response.WriteAsync(JsonSerializer.Serialize(new { error 未认证, message Token无效或者过期. })); } }; });第二步在接口中使用直接接口中使用[Authorize(AuthenticationSchemes Bearer)]就代表这一组接口都会启用jwt认证的方式来认证框架就可以完成验签与claims的解析。[ApiController] [Route([controller])] [Authorize(AuthenticationSchemes JwtBearerDefaults.AuthenticationScheme)] public class ProductController : ControllerBase { private static readonly Product[] Products [ new Product { Id 1, Name 机械键盘, Price 599.00m } ]; [HttpGet({id:int}, Name GetProductById)] public ActionResult GetById(int id) { var product Products.FirstOrDefault(p p.Id id); if (product is null) { return NotFound(); } return product; } }那么集成进来之后当有接口请求时就会按照如下流程来执行因为定义了未授权事件一旦触发就会收到友好的提示