如何在ASP.NET Core中为JwtSecurityTokenHandler实现依赖注入
解决JwtSecurityTokenHandler中注入Scoped服务的生命周期冲突问题
这个问题我之前也碰到过,核心原因是服务生命周期不匹配导致的,让我给你拆解清楚:
为什么会出现这个错误?
你注册的ApplicationDbContext是Scoped生命周期(EF Core默认的DbContext生命周期就是Scoped),而你的RevokableJwtSecurityTokenHandler虽然注册成了Transient,但一旦通过CustomJwtBearerOptionsPostConfigureOptions被添加到JwtBearerOptions中,就会被Singleton级别的认证核心服务持有——Singleton服务是不能依赖Scoped服务的,这就触发了那个经典的错误:Cannot consume scoped service ... from singleton。
之前无依赖注入时Handler能以Singleton正常运行,是因为它没有依赖任何Scoped/Transient服务,完全无状态,生命周期不冲突。
解决方案:通过IServiceProvider手动创建作用域获取Scoped服务
不要直接在RevokableJwtSecurityTokenHandler中注入ApplicationDbContext,而是注入IServiceProvider,在需要使用DbContext的方法(比如ValidateToken)里临时创建一个作用域,从中获取Scoped的DbContext,用完自动释放。这样既避免了生命周期冲突,又能正常使用DbContext。
修改后的RevokableJwtSecurityTokenHandler代码:
public class RevokableJwtSecurityTokenHandler : JwtSecurityTokenHandler { private readonly IServiceProvider _serviceProvider; public RevokableJwtSecurityTokenHandler(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public override ClaimsPrincipal ValidateToken( string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken) { var claimsPrincipal = base.ValidateToken(token, validationParameters, out validatedToken); var jtiClaim = claimsPrincipal.FindFirst(JwtRegisteredClaimNames.Jti); if (jtiClaim?.ValueType == ClaimValueTypes.String) { // 创建临时作用域,确保DbContext是Scoped生命周期 using var scope = _serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); // 在这里执行你的业务逻辑,比如检查Token是否被撤销 // 示例:var isRevoked = await context.RevokedTokens.AnyAsync(t => t.Jti == jtiClaim.Value); // if (isRevoked) throw new SecurityTokenValidationException("该Token已被撤销"); } return claimsPrincipal; } }
Startup中的注册代码保持不变即可:
services.AddTransient<RevokableJwtSecurityTokenHandler>(); services.AddTransient<IPostConfigureOptions<JwtBearerOptions>, CustomJwtBearerOptionsPostConfigureOptions>();
注意事项
- 一定要用
using包裹CreateScope()返回的作用域,确保作用域被正确释放,DbContext也能被EF Core正常回收。 - 如果你的验证逻辑是异步的,记得把
ValidateToken改成异步版本(ValidateTokenAsync),并使用异步的EF Core方法,避免阻塞线程。
内容的提问来源于stack exchange,提问作者Ray
相关产品推荐
相关产品推荐

