在 ASP.NET 应用之间共享身份验证 cookie

作者:Rick Anderson

网站通常由各个协同工作的 Web 应用组成。 若要提供单一登录 (SSO) 体验,站点内的 Web 应用必须共享身份验证 cookie。 为了支持此方案,数据保护堆栈允许共享 Katana cookie 身份验证和 ASP.NET Core cookie 身份验证票证。

在以下示例中:

  • 身份验证 cookie 名称设置为通用值 .AspNet.SharedCookie
  • AuthenticationType 显式或默认设置为 Identity.Application
  • 通用应用名称SharedCookieApp用于使数据保护系统能够共享数据保护密钥。
  • Identity.Application 用作身份验证方案。 无论使用哪种方案,都必须通过以下方式在共享 cookie 应用内部及其之间始终如一地使用该方案:用作默认方案或进行显式设置。 该方案在加密和解密 cookie 时使用,因此必须跨应用使用一致的方案。
  • 使用通用的数据保护密钥存储位置。
  • DataProtectionProvider 需要 Microsoft.AspNetCore.DataProtection.Extensions NuGet 包:
  • SetApplicationName 设置通用应用名称。

与 ASP.NET Core Identity共享身份验证cookie

使用 ASP.NET Core Identity时:

Program.cs中:

using Microsoft.AspNetCore.DataProtection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"c:\PATH TO COMMON KEY RING FOLDER"))
    .SetApplicationName("SharedCookieApp");

builder.Services.ConfigureApplicationCookie(options => {
    options.Cookie.Name = ".AspNet.SharedCookie";
});

var app = builder.Build();

注意:上述说明不适用于 ITicketStore (CookieAuthenticationOptions.SessionStore)。 有关详细信息,请参阅此 GitHub 问题

出于安全原因,ASP.NET Core 中不压缩身份验证 cookie。 使用身份验证 cookie 时,开发人员应将声明信息数量减少到所需的量。

在没有 ASP.NET Core Identity的情况下共享身份验证cookie

在没有 ASP.NET Core Identity的情况下直接使用cookie时,请配置数据保护和身份验证。 在以下示例中,身份验证类型设置为 Identity.Application

using Microsoft.AspNetCore.DataProtection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"c:\PATH TO COMMON KEY RING FOLDER"))
    .SetApplicationName("SharedCookieApp");

builder.Services.AddAuthentication("Identity.Application")
    .AddCookie("Identity.Application", options =>
    {
        options.Cookie.Name = ".AspNet.SharedCookie";
    });

var app = builder.Build();

出于安全原因,ASP.NET Core 中不压缩身份验证 cookie。 使用身份验证 cookie 时,开发人员应将声明信息数量减少到所需的量。

跨不同的基路径共享cookie

身份验证cookie使用HttpRequest.PathBase作为其默认的Cookie.Path。 如果必须跨不同的基本路径共享应用 cookie,则必须替代 Path

using Microsoft.AspNetCore.DataProtection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"c:\PATH TO COMMON KEY RING FOLDER"))
    .SetApplicationName("SharedCookieApp");

builder.Services.ConfigureApplicationCookie(options => {
    options.Cookie.Name = ".AspNet.SharedCookie";
    options.Cookie.Path = "/";
});

var app = builder.Build();

跨不同子域共享cookie

当托管cookie跨子域共享的应用时,在Cookie.Domain属性中指定公共域。 若要在 contoso.com 上跨应用共享 cookie,例如 first_subdomain.contoso.comsecond_subdomain.contoso.com,请将 Cookie.Domain 指定为 .contoso.com

options.Cookie.Domain = ".contoso.com";

静态加密数据保护密钥

对于生产部署,请将 DataProtectionProvider 配置为使用 DPAPI 或 X509Certificate 对密钥进行静态加密。 有关详细信息,请参阅使用 ASP.NET Core 在 Windows 和 Azure 中实现密钥静态加密。 在以下示例中,系统向 ProtectKeysWithCertificate 提供了证书指纹:

using Microsoft.AspNetCore.DataProtection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDataProtection()
    .ProtectKeysWithCertificate("{CERTIFICATE THUMBPRINT}");

使用通用用户数据库

当应用使用相同的 Identity 架构(相同版本的 Identity)时,请确认每个应用的 Identity 系统均指向相同的用户数据库。 否则,当标识系统尝试将身份验证 cookie 中的信息与其数据库中的信息匹配时,会在运行时生成故障。

当应用之间的 Identity 架构不同时(通常是因为应用使用不同的 Identity 版本),如果不在其他应用的 Identity 中重新映射和添加列,就不可能共享基于最新版 Identity 的通用数据库。 将其他应用升级为使用最新 Identity 版本通常更有效,这样应用就可以共享一个通用数据库。

应用程序名称更改

在 .NET 6 中,WebApplicationBuilder 会将内容根路径规范化以 DirectorySeparatorChar 结尾。 大多数从HostBuilderWebHostBuilder迁移的应用不会共享相同的应用名称,因为它们没有规范化。 有关详细信息,请参阅SetApplicationName

在 ASP.NET 4.x 和 ASP.NET Core 应用之间共享身份验证cookie

可以将使用 Microsoft.Owin Cookie身份验证中间件的 ASP.NET 4.x 应用配置为生成与 ASP.NET Core Cookie身份验证中间件兼容的身份验证cookie。 如果 Web 应用程序包含必须共享单一登录体验的 ASP.NET 4.x 应用和 ASP.NET Core 应用,这非常有用。 此类场景的具体示例是以增量方式将 Web 应用从 ASP.NET 迁移到 ASP.NET Core。 在这种情况下,应用的一些部分通常由原始 ASP.NET 应用提供,而其他部分由新的 ASP.NET Core 应用提供。 不过,用户只需登录一次。 这可以使用以下方法之一完成:

  • 使用 System.Web 适配器的远程身份验证功能,该功能使用 ASP.NET 应用将用户登录。
  • 将 ASP.NET 应用配置为使用 Microsoft.Owin Cookie身份验证中间件,以便与 ASP.NET Core 应用共享身份验证cookie。

要将 ASP.NET Microsoft.Owin Cookie身份验证中间件配置为与 ASP.NET Core 应用共享cookie,请按照前面的说明将 ASP.NET Core 应用配置为使用特定的cookie名称、应用名称,并将数据保护密钥保存到已知位置。 有关保留数据保护密钥的详细信息,请参阅配置 ASP.NET Core 数据保护

在 ASP.NET 应用中,安装Microsoft.Owin.Security.Interop包。

更新 Startup.Auth.cs 中的UseCookieAuthentication调用,以将 AspNetTicketDataFormat 配置为匹配 ASP.NET Core 应用的设置:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    { 
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
            validateInterval: TimeSpan.FromMinutes(30),
            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    },

    // Settings to configure shared cookie with ASP.NET Core app
    CookieName = ".AspNet.ApplicationCookie",
    AuthenticationType = "Identity.Application",                
    TicketDataFormat = new AspNetTicketDataFormat(
        new DataProtectorShim(
            DataProtectionProvider.Create(new DirectoryInfo(@"c:\PATH TO COMMON KEY RING FOLDER"),
            builder => builder.SetApplicationName("SharedCookieApp"))
            .CreateProtector(
                "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
                // Must match the Scheme name used in the ASP.NET Core app, i.e. IdentityConstants.ApplicationScheme
                "Identity.Application",
                "v2"))),
    CookieManager = new ChunkingCookieManager()
});

此处配置的重要项目包括:

  • cookie名称设置为与 ASP.NET Core 应用中的名称相同。
  • 使用同一密钥环路径创建数据保护提供程序。 请注意,在这些示例中,数据保护密钥存储在磁盘上,但可以使用其他数据保护提供程序。 例如,只要配置在应用之间匹配,就可以将 Redis 或 Azure Blob 存储用于数据保护提供程序。 有关保留数据保护密钥的详细信息,请参阅配置 ASP.NET Core 数据保护
  • 应用名称设置为与 ASP.NET Core 应用中使用的应用名称相同。
  • 身份验证类型设置为 ASP.NET Core 应用中身份验证方案的名称。
  • System.Web.Helpers.AntiForgeryConfig.UniqueClaimTypeIdentifier设置为来自 ASP.NET Core 标识的声明,该标识对用户是唯一的。

由于身份验证类型已更改以匹配 ASP.NET Core 应用的身份验证方案,因此还需要更新 ASP.NET 应用生成新标识以使用该相同名称的方式。 一般在Models/IdentityModels.cs中执行此操作:

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, "Identity.Application");
        
        // Add custom user claims here
        return userIdentity;
    }
}

通过这些更改,ASP.NET 和 ASP.NET Core 应用能够使用相同的身份验证cookie,以便登录或注销一个应用的用户反映在另一个应用中。

请注意,由于 ASP.NET Identity和 ASP.NET Core Identity的数据库架构之间存在差异,因此建议用户仅使用其中一个应用(ASP.NET 或 ASP.NET Core 应用)登录。 用户登录后,本部分中所述的步骤将允许任一应用使用身份验证cookie,并且两个应用都应能够注销用户。

其他资源

在以下示例中:

与 ASP.NET Core Identity共享身份验证cookie

使用 ASP.NET Core Identity时:

Startup.ConfigureServices中:

services.AddDataProtection()
    .PersistKeysToFileSystem("{PATH TO COMMON KEY RING FOLDER}")
    .SetApplicationName("SharedCookieApp");

services.ConfigureApplicationCookie(options => {
    options.Cookie.Name = ".AspNet.SharedCookie";
});

注意:上述说明不适用于 ITicketStore (CookieAuthenticationOptions.SessionStore)。 有关详细信息,请参阅此 GitHub 问题

出于安全原因,ASP.NET Core 中不压缩身份验证 cookie。 使用身份验证 cookie 时,开发人员应将声明信息数量减少到所需的量。

在没有 ASP.NET Core Identity的情况下共享身份验证cookie

在没有 ASP.NET Core Identity的情况下直接使用cookie时,请在Startup.ConfigureServices中配置数据保护和身份验证。 在以下示例中,身份验证类型设置为 Identity.Application

services.AddDataProtection()
    .PersistKeysToFileSystem("{PATH TO COMMON KEY RING FOLDER}")
    .SetApplicationName("SharedCookieApp");

services.AddAuthentication("Identity.Application")
    .AddCookie("Identity.Application", options =>
    {
        options.Cookie.Name = ".AspNet.SharedCookie";
    });

出于安全原因,ASP.NET Core 中不压缩身份验证 cookie。 使用身份验证 cookie 时,开发人员应将声明信息数量减少到所需的量。

跨不同的基路径共享cookie

身份验证cookie使用HttpRequest.PathBase作为其默认的Cookie.Path。 如果必须跨不同的基本路径共享应用 cookie,则必须替代 Path

services.AddDataProtection()
    .PersistKeysToFileSystem("{PATH TO COMMON KEY RING FOLDER}")
    .SetApplicationName("SharedCookieApp");

services.ConfigureApplicationCookie(options => {
    options.Cookie.Name = ".AspNet.SharedCookie";
    options.Cookie.Path = "/";
});

跨不同子域共享cookie

当托管cookie跨子域共享的应用时,在Cookie.Domain属性中指定公共域。 若要在 contoso.com 上跨应用共享 cookie,例如 first_subdomain.contoso.comsecond_subdomain.contoso.com,请将 Cookie.Domain 指定为 .contoso.com

options.Cookie.Domain = ".contoso.com";

静态加密数据保护密钥

对于生产部署,请将 DataProtectionProvider 配置为使用 DPAPI 或 X509Certificate 对密钥进行静态加密。 有关详细信息,请参阅使用 ASP.NET Core 在 Windows 和 Azure 中实现密钥静态加密。 在以下示例中,系统向 ProtectKeysWithCertificate 提供了证书指纹:

services.AddDataProtection()
    .ProtectKeysWithCertificate("{CERTIFICATE THUMBPRINT}");

在 ASP.NET 4.x 和 ASP.NET Core 应用之间共享身份验证cookie

可以将使用 Katana Cookie 身份验证中间件的 ASP.NET 4.x 应用配置为生成与 ASP.NET Core Cookie 身份验证中间件兼容的身份验证 cookie。 有关详细信息,请参阅在 ASP.NET 4.x 和 ASP.NET Core 应用(dotnet/AspNetCore.Docs #21987)之间共享身份验证cookie

使用通用用户数据库

当应用使用相同的 Identity 架构(相同版本的 Identity)时,请确认每个应用的 Identity 系统均指向相同的用户数据库。 否则,当标识系统尝试将身份验证 cookie 中的信息与其数据库中的信息匹配时,会在运行时生成故障。

当应用之间的 Identity 架构不同时(通常是因为应用使用不同的 Identity 版本),如果不在其他应用的 Identity 中重新映射和添加列,就不可能共享基于最新版 Identity 的通用数据库。 将其他应用升级为使用最新 Identity 版本通常更有效,这样应用就可以共享一个通用数据库。

其他资源