Web Api与Identity Server 4并发请求出现401授权错误求助
并发请求下IdentityServer4授权仅单个请求成功,其余返回401
我使用Web Api结合IdentityServer4做授权验证,单个请求能正常返回200响应;但发送2个及以上并发请求时,仅一个请求返回200,其余均返回401。反之,如果移除授权验证且不携带Bearer token,可以正常发起多个并发请求。
调试代码
async void Main() { List<string> tokens = new List<string> { "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // 已隐藏真实token,实际使用的是带正确scope的新鲜生成token }; Console.WriteLine("=========TaskList=========="); List<Task> tasks = new List<Task>(); foreach (var t in tokens) tasks.Add(call(t)); Task.WaitAll(tasks.ToArray()); // 结果:一个200,多个401 Console.WriteLine("=========Await=========="); foreach (var t in tokens) await call(t); // 结果:全部200 async Task call(string tok) { var handler = new HttpClientHandler { UseDefaultCredentials = true, PreAuthenticate = true }; var httpClient = new HttpClient(handler); httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + tok); // 移除这行后多并发请求正常;即使服务端去掉授权,加这行也会导致并发请求失败 var url = "https://localhost:44315/api/admin/user/GetUser"; var httpResponseMessage = await httpClient.PostAsJsonAsync(url, "myUser"); Console.WriteLine(httpResponseMessage.StatusCode); } }
控制器代码
[ApiController] [Authorize(Policy = "AdminApi")] [Route("api/admin/user")] public class AdminUserController : ControllerBase { [HttpPost("GetUser")] [ProducesResponseType(typeof(UserCreateResponseView), 200)] public async Task<IActionResult> GetUser([FromBody] string username) { var user = new MockUser(username); return Ok(user); } }
补充错误堆栈跟踪
[16:34:33 ERR] JWT token验证错误:尝试在上下文配置期间使用该上下文。DbContext实例不能在OnConfiguring内部使用,因为此时它仍在配置中。如果在之前的操作完成前对该上下文启动第二个操作,就会发生这种情况。任何实例成员都不保证是线程安全的。 System.InvalidOperationException: 尝试在上下文配置期间使用该上下文。DbContext实例不能在OnConfiguring内部使用,因为此时它仍在配置中。如果在之前的操作完成前对该上下文启动第二个操作,就会发生这种情况。任何实例成员都不保证是线程安全的。 在 Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider() 在 Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies() 在 Microsoft.EntityFrameworkCore.DbContext.get_Model() 在 Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType() 在 Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable() 在 Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Linq.IQueryable.get_Provider() 在 Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.Include[TEntity,TProperty](IQueryable`1 source, Expression`1 navigationPropertyPath) 在 IdentityServer4.EntityFramework.Stores.ClientStore.FindClientByIdAsync(String clientId) 位置 C:\local\identity\server4\EntityFramework\src\IdentityServer4.EntityFramework\Stores\ClientStore.cs:第47行 在 IdentityServer4.Stores.IClientStoreExtensions.FindEnabledClientByIdAsync(IClientStore store, String clientId) 位置 C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Extensions\IClientStoreExtensions.cs:第23行 在 IdentityServer4.Validation.TokenValidator.ValidateJwtAsync(String jwt, String audience, IEnumerable`1 validationKeys, Boolean validateLifetime) 位置 C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Validation\TokenValidator.cs:第301行
解决方案
从堆栈跟踪可以看出,问题核心是EntityFramework DbContext的线程安全问题:DbContext实例本身不是线程安全的,当多个并发请求共享同一个DbContext实例时,会触发上下文配置冲突,导致JWT验证失败并返回401。
解决步骤:
- 检查DbContext生命周期注册:确保IdentityServer4使用的配置库、操作库DbContext是
Scoped生命周期(每个请求对应一个实例),而非Singleton。注册时显式指定Scoped更保险:
// 正确示例:注册为Scoped(默认也是Scoped,显式指定避免误配置) services.AddDbContext<YourIdentityDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("IdentityDb")), ServiceLifetime.Scoped);
- 规范IdentityServer的EF存储配置:确保配置Store和操作Store时,使用依赖注入的DbContext,不要手动创建实例:
services.AddIdentityServer() .AddConfigurationStore(options => { options.ConfigureDbContext = b => b.UseSqlServer(Configuration.GetConnectionString("IdentityDb"), sql => sql.MigrationsAssembly(typeof(YourIdentityDbContext).Assembly.GetName().Name)); }) .AddOperationalStore(options => { options.ConfigureDbContext = b => b.UseSqlServer(Configuration.GetConnectionString("IdentityDb"), sql => sql.MigrationsAssembly(typeof(YourIdentityDbContext).Assembly.GetName().Name)); }) // 其他IdentityServer配置...
- 自定义Store避免手动实例化DbContext:如果有自定义的ClientStore、ResourceStore等实现,必须通过构造函数注入获取Scoped的DbContext实例,禁止自己new DbContext。
额外提示:调试代码中的async void Main虽不是直接诱因,但建议改为async Task Main,避免潜在的线程调度问题。
内容的提问来源于stack exchange,提问作者Jorge Zuverza
相关产品推荐
相关产品推荐

