登录成功后无法访问当前用户Claims的ASP.NET Core问题
我正在开发一个ASP.NET Core应用,为实现多租户功能,添加了TenancyProvider服务并注入到DbContext中(后续将用于设置全局查询过滤器),代码如下:
public class TenancyProvider : ITenancyProvider { private readonly IHttpContextAccessor _context; public TenancyProvider(IHttpContextAccessor context) { _context = context; } public string? AuthenticatedUser() => _context.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; }
现在遇到的问题是:AuthenticatedUser方法始终返回null。调试发现,即便登录成功后,ClaimsPrincipal也没有任何数据,Claims数量为0且IsAuthenticated始终为false,但Chrome开发者工具中能看到Identity Cookie确实存在。
我使用AspNetCore.Identity做身份验证,登录接口代码如下:
[HttpPost] public async Task<IActionResult> Login(LoginDTO loginDTO, string? ReturnUrl) { if (ModelState.IsValid == false) { ViewBag.Errors = ModelState.Values .SelectMany(x => x.Errors) .Select(x => x.ErrorMessage); return View(loginDTO); } var user = await _userManager.FindByEmailAsync(loginDTO.Email); if (user == null) { ModelState.AddModelError("Login", "无效用户"); return View(loginDTO); } var result = await _signInManager.PasswordSignInAsync(loginDTO.Email, loginDTO.Password, isPersistent: false, lockoutOnFailure: false); if (result.Succeeded) { //Return URL Redirect if (!string.IsNullOrEmpty(ReturnUrl) && Url.IsLocalUrl(ReturnUrl)) { return LocalRedirect(ReturnUrl); } return RedirectToAction("Index", "Orders"); } ModelState.AddModelError("Login", "用户名或密码无效"); return View(loginDTO); }
我曾尝试手动添加Claims,但没有效果,已移除相关代码。
我的Program.cs代码如下:
... builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped<ITenancyProvider, TenancyProvider>(); builder.Services.AddDbContext<ApplicationDbContext>( options => { options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")); }); builder.Services.AddIdentity<ApplicationUser, ApplicationRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders() .AddUserStore<UserStore<ApplicationUser, ApplicationRole, ApplicationDbContext, Guid>>() .AddRoleStore<RoleStore<ApplicationRole, ApplicationDbContext, Guid>>(); builder.Services.AddAuthorization(options => { options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); }); builder.Services.AddControllersWithViews(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); } else { app.UseDeveloperExceptionPage(); } app.UseHsts(); app.UseHttpsRedirection(); Rotativa.AspNetCore.RotativaConfiguration.Setup("wwwroot", wkhtmltopdfRelativePath: "Rotativa"); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); //app.MapRazorPages(); app.Run();
我感觉只是漏掉了某个细节,但已经花了好几个小时搜索和尝试各种方案。
更新
以下是我在DbContext中调用Tenancy Provider的代码:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid> { private readonly string _tenantId; public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ITenancyProvider tenancyProvider) : base(options) { _tenantId = tenancyProvider.AuthenticatedUser(); } ...
另外有截图显示,用户已登录状态下,Claims列表为空且IsAuthenticated为false,但应用表现出用户已登录的状态——我可以访问未登录时无法进入的控制器和页面。
解决方案
问题核心在于DbContext构造函数的执行时机:
DbContext在请求管道的早期就会被实例化,此时身份验证中间件还未完成ClaimsPrincipal的初始化工作,所以在构造函数中调用tenancyProvider.AuthenticatedUser()时,HttpContext里的用户信息还未被填充。
修复步骤:
- 不要在DbContext的构造函数中直接获取租户ID,改为延迟获取,比如在需要使用的场景(如设置全局查询过滤器)中再调用TenancyProvider的方法:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid> { private readonly ITenancyProvider _tenancyProvider; private string? _tenantId; public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ITenancyProvider tenancyProvider) : base(options) { _tenancyProvider = tenancyProvider; } private string GetTenantId() { if (_tenantId == null) { _tenantId = _tenancyProvider.AuthenticatedUser() ?? throw new InvalidOperationException("无法获取当前租户ID"); } return _tenantId; } // 重写OnModelCreating设置全局查询过滤器 protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 示例:给需要多租户的实体添加过滤器 modelBuilder.Entity<Order>().HasQueryFilter(o => o.TenantId == GetTenantId()); } }
确保
ITenancyProvider保持Scoped生命周期(你已经正确配置了这部分,无需调整),这样它会和每个请求绑定,能正确获取当前请求的HttpContext。验证中间件顺序:你的Program.cs中
UseAuthentication()在UseRouting()之后、UseAuthorization()之前,这个顺序是正确的,不需要调整。
额外说明:
应用表现出用户已登录(能访问受保护页面)的原因是,UseAuthorization()中间件在身份验证之后执行,此时ClaimsPrincipal已经被正确初始化,但DbContext的构造函数执行阶段更早,所以拿不到用户信息。
内容的提问来源于stack exchange,提问作者BiTo

