.NET 4x与.NET 5应用共享认证Cookie授权失败问题排查
看起来你已经搞定了Cookie的解密逻辑,说明数据保护的密钥共享是通的,但ASP.NET Core的认证管道没正确把Cookie转换成ClaimsPrincipal,才导致[Authorize]跳登录、UserManager.GetUserAsync(User)返回null的问题。我之前处理过几乎一样的场景,给你几个关键的修复点:
1. 统一认证方案配置,避免多中间件冲突
你当前的代码里,AddIdentity已经默认注册了Identity的ApplicationCookie认证方案,但又额外调用了AddAuthentication("Identity.Application").AddCookie(...),这会导致管道里存在两个Cookie认证中间件,反而没用到正确的那个来处理共享Cookie。
正确的做法是直接配置Identity自带的ApplicationCookie,把共享Cookie的参数、DataProtection逻辑都绑到这个方案上:
services.AddDbContext<RosterDBContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("databaseconnection"), sqlServerOptions => sqlServerOptions.CommandTimeout(120)); options.EnableSensitiveDataLogging(); }); // 先配置DataProtection,确保和.NET 4x用完全相同的密钥存储和应用名称 services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp\keyring")) .SetApplicationName("YourSharedAppName"); // 这个名称必须和.NET 4x侧的DataProtection应用名称一致 services.AddIdentity<RosterUser, RosterRole>() .AddEntityFrameworkStores<RosterDBContext>(); services.Configure<SecurityStampValidatorOptions>(options => { options.ValidationInterval = TimeSpan.FromSeconds(10); // 临时测试可以把验证间隔设为最大值,排除SecurityStamp不匹配的问题 // options.ValidationInterval = TimeSpan.MaxValue; }); // 重点:直接配置Identity的ApplicationCookie,替代单独的AddCookie services.ConfigureApplicationCookie(options => { options.Cookie.Name = ".AspNetCore.SharedCookie"; options.Cookie.Path = "/"; options.LoginPath = "/Identity/Account/Login/"; // 绑定和手动解密时完全一致的DataProtector var dataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"c:\temp\keyring")); options.DataProtectionProvider = dataProtectionProvider; options.TicketDataFormat = new TicketDataFormat( dataProtectionProvider.CreateProtector( "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", "Identity.Application", "v2")); }); services.AddHttpContextAccessor(); services.AddRazorPages();
2. 确保认证中间件的管道顺序正确
在Configure方法里,UseAuthentication必须在UseAuthorization之前,否则认证逻辑还没执行,[Authorize]就会判定用户未登录:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... 省略异常处理、静态文件等中间件 app.UseAuthentication(); // 先做认证 app.UseAuthorization(); // 再做授权 app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); }
3. 验证.NET 4x侧的Cookie配置匹配
要确保.NET 4x的Owin认证配置和.NET 5完全对齐,尤其是DataProtection的保护参数和Cookie名称:
首先安装Microsoft.Owin.Security.Interop包,然后在Startup.Auth.cs里配置:
app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = "Identity.Application", CookieName = ".AspNetCore.SharedCookie", CookiePath = "/", TicketDataFormat = new AspNetTicketDataFormat( new DataProtectorShim( DataProtectionProvider.Create(new DirectoryInfo(@"c:\temp\keyring")) .CreateProtector( "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", "Identity.Application", "v2"))) });
4. 检查Claims映射是否正确
.NET 4x生成的Claims可能和.NET Core的ClaimTypes命名有细微差别,比如确保NameIdentifier声明存在且对应数据库里的用户ID。你可以在手动解密后打印ticket.Principal.Claims,确认关键声明(比如http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier)的值和数据库里的用户ID一致,这样UserManager才能通过User找到对应的实体。
为什么原来的配置不生效?
你之前的代码里,单独添加的AddCookie并没有和Identity的认证体系关联起来,导致Identity的[Authorize]特性还是用默认的ApplicationCookie方案,但这个方案没有配置共享Cookie的解密逻辑,所以无法识别.NET 4x生成的Cookie。而手动解密是绕开了认证管道,直接用DataProtector解析,所以能拿到Ticket,但管道里的认证逻辑没跟上。
内容的提问来源于stack exchange,提问作者Paul

