无法与新的 dotnet 核心 Web 应用共享身份验证 Cookie



新问题

更新:由于这始于一些一般性挑战,并且此后专注于一个更具体的问题,我在这里重新发布了一个新问题。


我一直在遵循Microsoft的建议,将 ASP.NET Web 应用程序颁发的身份验证 cookie 与在同一域上运行的单独 dotnet 核心 Web 应用程序共享。不幸的是,dotnet 核心应用程序并没有像预期的那样取消对 cookie 的保护,我正在努力诊断原因。

我会尝试简化我所做的工作。在我这样做之前,我应该指出两个应用程序将在同一路径下运行 - 我们称之为 mydomain.com/path - 因此身份验证cookie的范围将限定为该路径。有很多额外的复杂性,因为我实际上试图将其连接到旧的 OIDC 库中,但我认为我遇到的主要问题是在另一边,我有一个相当轻量级的 dotnet 核心应用程序试图使用相同的会话。

首先,在我的原始 .NET 应用程序(它是 4.7.2)中,我使用Microsoft.Owin.Security.Interop库创建一个新的数据保护器:

var appName = "<my-app-name>";
var encryptionSettings = new AuthenticatedEncryptionSettings()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
};
var interopProvider = DataProtectionProvider.Create(
new DirectoryInfo(keyRingSharePath),
builder =>
{
builder.SetApplicationName(appName);
builder.SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 20));
builder.UseCryptographicAlgorithms(encryptionSettings);
if (!generateNewKey)
{
builder.DisableAutomaticKeyGeneration();
}
});
return new DataProtectorShim(
interopProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2"));

请注意,<my-app-name>也是 cookie 的名称,如CookieAuthenticationOptions中所述。

keyRingSharePath现在只是我PC上的本地路径。第一次运行它时,我generateNewKey设置为true以确保在此路径上生成新密钥。此后,我保留此false以确保重复使用该密钥。

我还根据文档使用此数据保护器分配票证数据格式:new TicketDataFormat(dataProtector)

这按预期工作,因为身份验证仍然有效,我甚至可以通过使用上面创建的TicketDataFormat实例并使用身份验证 cookie 值调用其Unprotect方法并取回ClaimsIdentity来验证数据保护。

接下来,我创建了一个简单的 dotnet 核心应用,它与上述应用在同一域上运行。在Startup中,我添加了以下内容:

var primaryAuthenticationType = "<my-app-name>";
var cookieName = primaryAuthenticationType;
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keyRingSharePath))
.SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 20))
.DisableAutomaticKeyGeneration()
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
})
.SetApplicationName(primaryAuthenticationType);
services.ConfigureApplicationCookie(options => {
options.Cookie.Name = cookieName;
options.Cookie.Path = "/path";
});

显然,keyRingSharePath具有与 ASP.NET 应用程序中相同的价值。我在StartupConfigureServices方法中也有以下内容:

app.UseAuthentication();
app.UseAuthorization();

使用 ASP.NET 应用登录后,我切换到我的 dotnet 核心应用。但不幸的是,在调试任何具有/path下路由的控制器时,我发现User.Identity.IsAuthenticatedfalse.

我还尝试像这样手动取消对cookie的保护,使用注入的IDataProtectionProvider实例:

var protector = protectionProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
"<my-app-name>",
"v2");
var ticketDataFormat = new TicketDataFormat(dataProtector);
var ticket = ticketDataFormat.Unprotect("<auth-cookie-value>");
return ticket?.Principal;

但是,ticket被分配为 null,我找不到任何方法来调试为什么它不会取消保护 cookie 值。据我所知,这应该使用与我的 ASP.NET 应用程序在确认我可以取消保护该cookie时使用的相同逻辑。

任何指导将不胜感激。

<小时 />

更新 1

我一直在尝试解构取消保护 cookie 的代码。我已将以下代码添加到 dotnet 核心应用上的控制器:

var formatVersion = 3;
var protector = _dataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", "<my-app-name>", "v2");
var cookieValue = Request.Cookies["<my-app-name>"];
var cookieValueDecoded = Base64UrlTextEncoder.Decode(cookieValue);
var unprotectedBytes = protector.Unprotect(cookieValueDecoded);
using (MemoryStream memoryStream = new MemoryStream(unprotectedBytes))
{
using (GZipStream gzipStream = new GZipStream((Stream)memoryStream, CompressionMode.Decompress))
{
using (BinaryReader reader = new BinaryReader((Stream) gzipStream))
{
if (reader.ReadInt32() != formatVersion) return (AuthenticationTicket) null;
string authenticationType = reader.ReadString();
string str1 = ReadWithDefault(reader, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name");
string roleType = ReadWithDefault(reader, "http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
int length = reader.ReadInt32();
Claim[] claimArray = new Claim[length];
for (int index = 0; index != length; ++index)
{
string type = ReadWithDefault(reader, str1);
string str2 = reader.ReadString();
string valueType = ReadWithDefault(reader, "http://www.w3.org/2001/XMLSchema#string");
string str3 = ReadWithDefault(reader, "LOCAL AUTHORITY");
string originalIssuer = ReadWithDefault(reader, str3);
claimArray[index] = new Claim(type, str2, valueType, str3, originalIssuer);
}
ClaimsIdentity identity = new ClaimsIdentity((IEnumerable<Claim>)claimArray, authenticationType, str1, roleType);
}
}
}

这些代码大部分来自Microsoft.Owin.Security, Version=3.0.1中的Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer。因此,它实质上是对原始 ASP.NET 应用中使用的保护逻辑的反转,并且工作正常。它以与在其他应用程序上进行身份验证的帐户匹配的ClaimsIdentity结尾。这告诉我,应用程序之间的加密配置和密钥是匹配的。

因此,在两端取消保护身份验证 cookie 的内置代码之间必须存在其他一些差异。但我不清楚如何诊断差异在哪里。我的假设是,我在dotnet核心端错过了一些使cookie身份验证可互操作的内容。

<小时 />

更新2

经过更多的挖掘,我认为这归结为数据序列化程序格式版本。在我的 dotnet 核心应用程序中,如果我深入研究TicketDataFormat,我看到它使用TicketSerializer.Default这是具有硬编码5FormatVersionIDataSerializer<AuthenticationTicket>的实现。TicketSerializer顶部还有一条评论说:

这必须与Microsoft.Owin.Security.Interop.AspNetTicketSerializer保持同步

但是,您可以在上面的 UPDATE 1 中看到,当我从 ASP.NET Web 应用程序中撕下一些序列化逻辑时,它正在使用格式版本 3。请注意,此应用使用的是Microsoft.Owin.Security, Version=3.0.1.0附带的TicketDataFormat版本,并且该程序集具有硬编码FormatVersion3TicketSerializer

那么,如何确保这些序列化程序在两端兼容呢?

<小时 />

更新3

意识到我是一个完整的工具,并且缺少Microsoft文档的关键部分。上面我声明这一点:

我还根据文档使用此数据保护器分配票证数据格式:new TicketDataFormat(dataProtector)

好吧,实际上我应该使用Microsoft.Owin.Security.Interop库提供的AspNetTicketDataFormat类型。更正此问题后,我现在可以使用以下命令在我的 dotnet 核心应用中获取声明主体:

var dataProtector = _dataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", "<my-app-name>", "v2");
var ticketDataFormat = new TicketDataFormat(dataProtector);
var ticket = ticketDataFormat.Unprotect(cookieValue, "");

在这里,我可以看到ticket.Principal.Identity从cookie中填充了我的身份。

但是,我仍然无法使我的应用处于经过身份验证的状态。我显然仍然没有正确连接 cookie 身份验证中间件。我的Startup仍然看起来像我原始帖子中的第二个代码块。如果有人能帮忙,感觉就像最后的障碍。

最终,当我到达更新 3 时,我已经为此工作了足够长的时间,以至于会话已过期,但我仍在使用该会话进行测试。所以我的cookie被拒绝的原因是由于会话过期,而不是一些编码问题。一旦我向应用程序添加了跟踪级日志记录,我就发现了这一点,并且有答案,从控制台盯着我!将把它留在这里,以防帖子中描述的过程使其他人受益。

最新更新