HttpContextAccessor是否存在缓存或线程安全问题?用户身份Claims混乱问题原因咨询
这问题我之前也碰到过,核心原因不是HttpContextAccessor的线程安全问题,而是服务生命周期不匹配导致的Scoped服务被意外提升为Singleton,进而引发了跨请求的状态污染。
先理清你的服务生命周期逻辑
- 你注册的
IJwtService是 Scoped 生命周期,正常情况下每个HTTP请求都会创建一个独立的实例,和请求绑定。 - 但你的
AccessCheckToRoutesMiddleware是ASP.NET Core中间件,中间件默认是 Singleton 生命周期——它在应用启动时就被初始化,整个应用运行期间只有这一个实例。
为什么会出现Claims混乱?
当Singleton的中间件直接依赖Scoped的JwtService时,ASP.NET Core的DI容器会被迫把JwtService的生命周期提升为Singleton(因为Singleton服务不能依赖比它生命周期短的服务,否则会导致短生命周期服务被长期持有)。
这就意味着整个应用里只有一个JwtService实例,所有请求共用它。你之前在构造函数或者_getIdentity方法里缓存了_identity变量:
- 第一个请求进来时,会把当前用户的Identity存入
_identity; - 后续请求进来时,因为
_identity已经不为空,就直接复用这个值,自然就拿到了前一个用户的Claims,出现混乱。
为什么改成每次直接获取就正常?
IHttpContextAccessor本身是Singleton,但它内部用AsyncLocal<T>来存储HttpContext——这个特性能保证在异步流程中,每个请求上下文都能拿到属于自己的HttpContext。
你每次调用httpContextAccessor.HttpContext.User.Identity时,拿到的都是当前请求的正确Identity,没有了跨请求的缓存,自然就不会出现Claims混淆的问题。
给你一个根本的解决方案
要避免这种问题,不要在中间件的构造函数里注入Scoped服务,而是在中间件的InvokeAsync方法中注入(ASP.NET Core允许在Invoke方法里注入Scoped服务,因为Invoke是每个请求执行一次):
public class AccessCheckToRoutesMiddleware { private readonly RequestDelegate _next; public AccessCheckToRoutesMiddleware(RequestDelegate next) { _next = next; } // 在InvokeAsync里注入Scoped的IJwtService,每个请求都会拿到新的实例 public async Task InvokeAsync(HttpContext context, IJwtService jwtService) { if (jwtService.IsPrivilegedUser()) { // 你的权限校验逻辑 } await _next(context); } }
这样每个请求都会使用独立的JwtService实例,彻底避免跨请求的状态污染。
备注:内容来源于stack exchange,提问作者Rajeev Menon

