如何解密ASP.NET Core 8跨服务器身份验证Cookie并获取用户名?
问题背景
某ASP.NET Core 8 MVC应用在Startup.cs中配置了Cookie身份验证,核心代码如下:
services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(@"Voti")); services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"Voti")); options.Cookie = new CookieBuilder() { Name = ".EevaAuth", IsEssential = true, SameSite = SameSiteMode.Strict }; options.LoginPath = new PathString("/Account/LogOn"); options.ReturnUrlParameter = "returnUrl"; options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromDays(30); });
应用部署在不同DNS服务器上,密钥文件保持一致,但解密跨服务器Cookie时遇到两个问题:
- 参考Stack Overflow编写的手动解密代码抛出异常:
System.Security.Cryptography.CryptographicException: The payload was invalid.
...
at MyApp.Controllers.Erp.AboutController.DecryptCookie()
- 使用
TicketDataFormat解密时,同服务器Cookie可正常获取用户名,但其他服务器Cookie返回null。
核心问题分析
- 手动解密报错:DataProtection的上下文不匹配,包括未统一应用名称、手动创建的
DataProtectionProvider与全局配置不一致,导致保护目的字符串校验失败。 - 跨服务器返回null:Cookie域名配置不统一,且
TicketDataFormat依赖的加密上下文未与生成Cookie的服务器对齐。
解决方案
步骤1:统一跨服务器的DataProtection与Cookie配置
修改Startup.cs中的配置,确保所有服务器共享相同的加密上下文和Cookie规则:
services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"Voti")) .SetApplicationName("EevaAuthApp"); // 所有服务器必须设置相同的应用名称 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { // 复用全局配置的DataProtectionProvider,避免手动创建导致上下文不一致 options.DataProtectionProvider = services.BuildServiceProvider().GetRequiredService<IDataProtectionProvider>(); options.Cookie = new CookieBuilder() { Name = ".EevaAuth", IsEssential = true, SameSite = SameSiteMode.Strict, Domain = ".yourdomain.com" // 设置根域名,让所有子服务器共享Cookie }; options.LoginPath = new PathString("/Account/LogOn"); options.ReturnUrlParameter = "returnUrl"; options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromDays(30); });
步骤2:正确的跨服务器Cookie解密代码
使用框架自带的TicketDataFormat解密,确保与认证中间件的配置完全对齐:
private readonly IOptionsMonitor<CookieAuthenticationOptions> _cookieOptionsMonitor; // 通过构造函数注入依赖 public YourController(IOptionsMonitor<CookieAuthenticationOptions> cookieOptionsMonitor) { _cookieOptionsMonitor = cookieOptionsMonitor; } public string DecryptCrossServerCookie(string cookieValue) { var authScheme = CookieAuthenticationDefaults.AuthenticationScheme; var options = _cookieOptionsMonitor.Get(authScheme); // 确保Cookie域名与生成时一致 options.Cookie.Domain = ".yourdomain.com"; // 使用框架原生的TicketDataFormat解密,自动处理DataProtection细节 AuthenticationTicket ticket = options.TicketDataFormat.Unprotect(cookieValue); if (ticket == null) { throw new InvalidOperationException("无法解密Cookie,可能是密钥不匹配或Cookie已失效"); } return ticket.Principal.Identity.Name; }
步骤3:关键注意事项
- 共享密钥存储:所有服务器必须能访问同一个
Voti目录(如网络共享文件夹),确保密钥文件完全一致,且应用池账户拥有读写权限。 - 统一应用名称:
SetApplicationName的值必须在所有服务器上完全相同,DataProtection会通过该名称隔离不同应用的加密上下文。 - Cookie域名配置:将
Cookie.Domain设为根域名(如.yourdomain.com),确保子域名服务器都能接收并处理该Cookie。 - 避免手动创建DataProtectionProvider:手动创建的Provider若未设置应用名称,会导致加密上下文不匹配,引发解密失败。
原错误代码问题说明
- 手动创建
DataProtectionProvider时未设置应用名称,与全局配置的加密上下文不一致,即使保护目的字符串参数正确也无法解密。 - 跨服务器解密时临时修改
Cookie.Domain的方式错误,应在全局配置中统一设置,否则TicketDataFormat的内部规则不匹配。
内容的提问来源于stack exchange,提问作者Andrus
相关产品推荐
相关产品推荐

