在 ASP.NET Core 中使用 SameSite cookie

作者:Rick Anderson

SameSite 是一种 IETF 草案标准,旨在针对跨网站请求伪造 (CSRF) 攻击提供一些保护。 最初于 2016 年起草,草案标准于 2019 年更新。 更新后的标准与以前的标准不向后兼容,以下是最明显的差异:

  • 默认情况下,没有 SameSite 标头的 Cookie 被视为 SameSite=Lax
  • SameSite=None 必须用于允许进行跨站点 cookie 使用。
  • 断言 SameSite=None 的 Cookie 还必须标记为 Secure
  • 使用 <iframe> 的应用程序可能会遇到与 sameSite=LaxsameSite=Strictcookie 有关的问题,因为 <iframe> 被视为跨站点方案。
  • SameSite=None 不被 2016 标准所允许,会导致某些实现将此类 cookie 视为 SameSite=Strict。 请参阅本文档中的支持较旧浏览器

SameSite=Lax 设置适用于大多数应用程序 cookie。 某些形式的身份验证(如 OpenID Connect (OIDC) 和 WS-Federation)默认为基于 POST 的重定向。 基于 POST 的重定向会触发 SameSite 浏览器保护,因此会为这些组件禁用 SameSite。 由于请求流程不同,大多数 OAuth 登录不受影响。

发出 cookie 的每个 ASP.NET Core 组件都需要确定 SameSite 是否合适。

SameSite 和 Identity

ASP.NET Core Identity 在很大程度上不受 SameSite cookie 的影响,IFramesOpenIdConnect 集成等高级方案除外。

使用 Identity 时,不要添加任何 cookie 提供程序或调用 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)Identity 会执行这些操作。

SameSite 测试示例代码

可下载和测试以下示例:

示例 文档
.NET Core Razor Pages ASP.NET Core 3.1 Razor Pages SameSite cookie 示例

.NET Core 对 sameSite 属性的支持

.NET Core 支持 SameSite 的 2019 年草案标准。 开发人员能够使用 HttpCookie.SameSite 属性以编程方式控制 sameSite 特性的值。 将 SameSite 属性设置为 StrictLaxNone 会导致在使用 cookie 的情况下在网络上写入这些值。 设置为 SameSiteMode.Unspecified 指示在使用 cookie 的情况下不应发送 sameSite。

    var cookieOptions = new CookieOptions
    {
        // Set the secure flag, which Chrome's changes will require for SameSite none.
        // Note this will also require you to be running on HTTPS.
        Secure = true,

        // Set the cookie to HTTP only which is good practice unless you really do need
        // to access it client side in scripts.
        HttpOnly = true,

        // Add the SameSite attribute, this will emit the attribute with a value of none.
        SameSite = SameSiteMode.None

        // The client should follow its default cookie policy.
        // SameSite = SameSiteMode.Unspecified
    };

    // Add the cookie to the response cookie collection
    Response.Cookies.Append("MyCookie", "cookieValue", cookieOptions);
}

使用 SameSite 的 API 用法

HttpContext.Response.Cookies.Append 默认为 Unspecified,这意味着不会将 SameSite 属性添加到 cookie,并且客户端会使用其默认行为(对于新浏览器为 Lax,对于旧浏览器为 None)。 下面的代码演示如何将 cookie SameSite 值更改为 SameSiteMode.Lax

HttpContext.Response.Cookies.Append(
                     "name", "value",
                     new CookieOptions() { SameSite = SameSiteMode.Lax });

发出 cookie 的所有 ASP.NET Core 组件都会使用适合于其方案的设置替代前面的默认值。 前面替代的默认值未更改。

组件 cookie 默认
CookieBuilder SameSite Unspecified
Session SessionOptions.Cookie Lax
CookieTempDataProvider CookieTempDataProviderOptions.Cookie Lax
IAntiforgery AntiforgeryOptions.Cookie Strict
Cookie Authentication CookieAuthenticationOptions.Cookie Lax
AddTwitter TwitterOptions.StateCookie Lax
RemoteAuthenticationHandler<TOptions> RemoteAuthenticationOptions.CorrelationCookie None
AddOpenIdConnect OpenIdConnectOptions.NonceCookie None
HttpContext.Response.Cookies.Append CookieOptions Unspecified

ASP.NET Core 3.1 及更高版本提供以下 SameSite 支持:

  • 重新定义 SameSiteMode.None 的行为以发出 SameSite=None
  • 添加新值 SameSiteMode.Unspecified 以省略 SameSite 属性。
  • 所有 cookie API 都默认为 Unspecified。 某些使用 cookie 的组件会设置更特定于其方案的值。 有关示例,请参阅上表。

在 ASP.NET Core 3.0 及更高版本中,SameSite 默认值已更改,以避免与不一致的客户端默认值发生冲突。 以下 API 已将默认值从 SameSiteMode.Lax 更改为 -1,以避免为这些 cookie 发出 SameSite 属性:

历史记录和更改

SameSite 支持首先在 ASP.NET Core 2.0 中使用 2016 年草案标准来实现。 2016 标准是选择加入的。 ASP.NET Core 通过在默认情况下将几个 cookie 设置为 Lax 来选择加入。 在遇到身份验证方面的几个问题后,大多数 SameSite 使用被禁用

2019 年 11 月发布了补丁,用于从 2016 标准更新为 2019 标准。 SameSite 规范的 2019 草案

  • 不与 2016 草案向后兼容。 有关详细信息,请参阅本文档中的支持较旧浏览器
  • 指定 cookie 在默认情况下被视为 SameSite=Lax
  • 指定显式断言 SameSite=None 以便启用跨站点交付的 cookie 应标记为 SecureNone 是要选择退出的新项。
  • 受为 ASP.NET Core 2.1、2.2 和 3.0 发布的补丁所支持。 ASP.NET Core 3.1 及更高版本具有额外 SameSite 支持。
  • 计划在 2020 年 2 月Chrome 默认启用。 浏览器已开始在 2019 年迁移到此标准。

受从 2016 SameSite 草案标准到 2019 草案标准的更改影响的 API

支持较旧浏览器

2016 SameSite 标准要求必须将未知值视为 SameSite=Strict 值。 从支持 2016 SameSite 标准的较旧浏览器访问的应用在遇到值为 None 的 SameSite 属性时可能会中断。 如果 Web 应用打算支持较旧浏览器,则必须实现浏览器检测。 ASP.NET Core 未实现浏览器检测,因为 User-Agent 值非常不稳定,经常更改。 Microsoft.AspNetCore.CookiePolicy 中的扩展点允许插入特定于用户代理的逻辑。

Program.cs 中,添加在调用 UseAuthentication 之前调用 UseCookiePolicy 的代码或任何写入 cookie 的方法:

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<CookiePolicyOptions>(options =>
{
    options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
    options.OnAppendCookie = cookieContext =>
        CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
    options.OnDeleteCookie = cookieContext =>
        CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});

void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
    if (options.SameSite == SameSiteMode.None)
    {
        var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
        if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
        {
            options.SameSite = SameSiteMode.Unspecified;
        }
    }
}

    builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseCookiePolicy();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

Program.cs 中,添加与以下突出显示的代码类似的代码:

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<CookiePolicyOptions>(options =>
{
    options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
    options.OnAppendCookie = cookieContext =>
        CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
    options.OnDeleteCookie = cookieContext =>
        CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});

void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
    if (options.SameSite == SameSiteMode.None)
    {
        var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
        if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
        {
            options.SameSite = SameSiteMode.Unspecified;
        }
    }
}

    builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseCookiePolicy();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

在上面的示例中,MyUserAgentDetectionLib.DisallowsSameSiteNone 是用户提供的库,可检测用户代理是否不支持 SameSite None

if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
{
    options.SameSite = SameSiteMode.Unspecified;
}

下面的代码演示示例 DisallowsSameSiteNone 方法:

警告

下面的代码仅用于演示:

  • 不应将其视为完整代码。
  • 它不进行维护或受支持。
public static bool DisallowsSameSiteNone(string userAgent)
{
    // Check if a null or empty string has been passed in, since this
    // will cause further interrogation of the useragent to fail.
     if (String.IsNullOrWhiteSpace(userAgent))
        return false;
    
    // Cover all iOS based browsers here. This includes:
    // - Safari on iOS 12 for iPhone, iPod Touch, iPad
    // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
    // - Chrome on iOS 12 for iPhone, iPod Touch, iPad
    // All of which are broken by SameSite=None, because they use the iOS networking
    // stack.
    if (userAgent.Contains("CPU iPhone OS 12") ||
        userAgent.Contains("iPad; CPU OS 12"))
    {
        return true;
    }

    // Cover Mac OS X based browsers that use the Mac OS networking stack. 
    // This includes:
    // - Safari on Mac OS X.
    // This does not include:
    // - Chrome on Mac OS X
    // Because they do not use the Mac OS networking stack.
    if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
        userAgent.Contains("Version/") && userAgent.Contains("Safari"))
    {
        return true;
    }

    // Cover Chrome 50-69, because some versions are broken by SameSite=None, 
    // and none in this range require it.
    // Note: this covers some pre-Chromium Edge versions, 
    // but pre-Chromium Edge does not require SameSite=None.
    if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
    {
        return true;
    }

    return false;
}

针对 SameSite 问题测试应用

与远程站点交互(例如通过第三方登录)的应用需要:

使用可选择加入新 SameSite 行为的客户端版本测试 Web 应用。 Chrome、Firefox 和 Chromium Edge 都具有可用于测试的新的“选择加入”功能标志。 应用应用 SameSite 补丁后,请使用较旧客户端版本(尤其是 Safari)进行测试。 有关详细信息,请参阅本文档中的支持较旧浏览器

使用 Chrome 测试

Chrome 78+ 会提供误导性的结果,因为它实施了临时缓解。 Chrome 78+ 临时缓解允许使用两分钟内产生的 cookie。 启用合适的测试标志后,Chrome 76 或 77 会提供更准确的结果。 要测试新 SameSite 行为,请将 chrome://flags/#same-site-by-default-cookies 切换为“已启用”。 据报告,较旧版本的 Chrome(75 及更早版本)会在使用新的 None 设置时失败。 请参阅本文档中的支持较旧浏览器

Google 不提供较旧的 chrome 版本。 按照下载 Chromium 的说明测试较旧版本的 Chrome。 请勿从通过搜索较旧版本 chrome 而提供的链接下载 Chrome。

从 Canary 版本 80.0.3975.0 开始,可以使用新标志 --enable-features=SameSiteDefaultChecksMethodRigorously 为进行测试而禁用 Lax+POST 临时缓解,以便允许在删除了缓解的功能的最终状态下测试站点和服务。 有关详细信息,请参阅 Chromium 项目 SameSite 更新

使用 Safari 测试

Safari 12 严格执行了先前的草案,在 cookie 中存在新 None 值时会失败。 可通过本文档中浏览器检测代码支持较旧浏览器避免 None。 可使用 MSAL、ADAL 或所使用的任何库来测试 Safari 12、Safari 13 和基于 WebKit 的操作系统样式登录。 问题取决于基础 OS 版本。 已知 OSX Mojave (10.14) 和 iOS 12 存在与新 SameSite 行为相关的兼容性问题。 将操作系统升级到 OSX Catalina (10.15) 或 iOS 13 会解决此问题。 Safari 当前没有用于测试新规范行为的选择加入标志。

使用 Firefox 测试

通过在具有功能标志 network.cookie.sameSite.laxByDefaultabout:config 页面上选择加入,可在版本 68+ 上测试 Firefox 对新标准的支持。 较旧版本的 Firefox 未报告兼容性问题。

使用 Edge 浏览器测试

Edge 支持旧 SameSite 标准。 Edge 版本 44 与新标准不存在任何已知的兼容性问题。

使用 Edge (Chromium) 测试

SameSite 标志在 edge://flags/#same-site-by-default-cookies 页面上进行设置。 未发现与 Edge Chromium 的兼容性问题。

使用 Electron 测试

Electron 的版本包括较旧版本的 Chromium。 例如,Teams 使用的 Electron 版本为 Chromium 66,该版本呈现了较旧的行为。 你必须使用产品所用的 Electron 版本执行你自己的兼容性测试。 请参阅以下部分中的支持较旧浏览器

其他资源

示例 文档
.NET Core Razor Pages ASP.NET Core 3.1 Razor Pages SameSite cookie 示例

可下载和测试以下示例:

示例 文档
.NET Core Razor Pages ASP.NET Core 3.1 Razor Pages SameSite cookie 示例

.NET Core 对 sameSite 属性的支持

.NET Core 3.1 及更高版本支持 SameSite 的 2019 年草案标准。 开发人员能够使用 HttpCookie.SameSite 属性以编程方式控制 sameSite 特性的值。 将 SameSite 属性设置为 Strict、Lax 或 None 会导致在使用 cookie 的情况下在网络上写入这些值。 将其设置为等于 (SameSiteMode)(-1) 指示在使用 cookie 的情况下,网络中不应包含 sameSite 属性

var cookieOptions = new CookieOptions
{
    // Set the secure flag, which Chrome's changes will require for SameSite none.
    // Note this will also require you to be running on HTTPS.
    Secure = true,

    // Set the cookie to HTTP only which is good practice unless you really do need
    // to access it client side in scripts.
    HttpOnly = true,

    // Add the SameSite attribute, this will emit the attribute with a value of none.
    // To not emit the attribute at all set
    // SameSite = (SameSiteMode)(-1)
    SameSite = SameSiteMode.None
};

// Add the cookie to the response cookie collection
Response.Cookies.Append("MyCookie", "cookieValue", cookieOptions);

.NET Core 3.1 及更高版本支持更新后的 SameSite 值,并向 SameSiteMode 枚举添加额外枚举值 SameSiteMode.Unspecified。 此新值指示在使用 cookie 的情况下不应发送 sameSite。

使用 SameSite 的 API 用法

HttpContext.Response.Cookies.Append 默认为 Unspecified,这意味着不会将 SameSite 属性添加到 cookie,并且客户端会使用其默认行为(对于新浏览器为 Lax,对于旧浏览器为 None)。 下面的代码演示如何将 cookie SameSite 值更改为 SameSiteMode.Lax

HttpContext.Response.Cookies.Append(
                     "name", "value",
                     new CookieOptions() { SameSite = SameSiteMode.Lax });

发出 cookie 的所有 ASP.NET Core 组件都会使用适合于其方案的设置替代前面的默认值。 前面替代的默认值未更改。

组件 cookie 默认
CookieBuilder SameSite Unspecified
Session SessionOptions.Cookie Lax
CookieTempDataProvider CookieTempDataProviderOptions.Cookie Lax
IAntiforgery AntiforgeryOptions.Cookie Strict
Cookie Authentication CookieAuthenticationOptions.Cookie Lax
AddTwitter TwitterOptions.StateCookie Lax
RemoteAuthenticationHandler<TOptions> RemoteAuthenticationOptions.CorrelationCookie None
AddOpenIdConnect OpenIdConnectOptions.NonceCookie None
HttpContext.Response.Cookies.Append CookieOptions Unspecified

ASP.NET Core 3.1 及更高版本提供以下 SameSite 支持:

  • 重新定义 SameSiteMode.None 的行为以发出 SameSite=None
  • 添加新值 SameSiteMode.Unspecified 以省略 SameSite 属性。
  • 所有 cookie API 都默认为 Unspecified。 某些使用 cookie 的组件会设置更特定于其方案的值。 有关示例,请参阅上表。

在 ASP.NET Core 3.0 及更高版本中,SameSite 默认值已更改,以避免与不一致的客户端默认值发生冲突。 以下 API 已将默认值从 SameSiteMode.Lax 更改为 -1,以避免为这些 cookie 发出 SameSite 属性:

历史记录和更改

SameSite 支持首先在 ASP.NET Core 2.0 中使用 2016 年草案标准来实现。 2016 标准是选择加入的。 ASP.NET Core 通过在默认情况下将几个 cookie 设置为 Lax 来选择加入。 在遇到身份验证方面的几个问题后,大多数 SameSite 使用被禁用

2019 年 11 月发布了补丁,用于从 2016 标准更新为 2019 标准。 SameSite 规范的 2019 草案

  • 不与 2016 草案向后兼容。 有关详细信息,请参阅本文档中的支持较旧浏览器
  • 指定 cookie 在默认情况下被视为 SameSite=Lax
  • 指定显式断言 SameSite=None 以便启用跨站点交付的 cookie 应标记为 SecureNone 是要选择退出的新项。
  • 受为 ASP.NET Core 2.1、2.2 和 3.0 发布的补丁所支持。 ASP.NET Core 3.1 具有额外 SameSite 支持。
  • 计划在 2020 年 2 月Chrome 默认启用。 浏览器已开始在 2019 年迁移到此标准。

受从 2016 SameSite 草案标准到 2019 草案标准的更改影响的 API

支持较旧浏览器

2016 SameSite 标准要求必须将未知值视为 SameSite=Strict 值。 从支持 2016 SameSite 标准的较旧浏览器访问的应用在遇到值为 None 的 SameSite 属性时可能会中断。 如果 Web 应用打算支持较旧浏览器,则必须实现浏览器检测。 ASP.NET Core 未实现浏览器检测,因为 User-Agent 值非常不稳定,经常更改。 Microsoft.AspNetCore.CookiePolicy 中的扩展点允许插入特定于用户代理的逻辑。

Startup.Configure 中,添加在调用 UseAuthentication 之前调用 UseCookiePolicy 的代码或任何写入 cookie 的方法:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

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

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Startup.ConfigureServices 中,添加类似于以下内容的代码:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
        options.OnAppendCookie = cookieContext =>
            CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
        options.OnDeleteCookie = cookieContext =>
            CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
    });

    services.AddRazorPages();
}

private void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
    if (options.SameSite == SameSiteMode.None)
    {
        var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
        if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
        {
            options.SameSite = SameSiteMode.Unspecified;
        }
    }
}

在上面的示例中,MyUserAgentDetectionLib.DisallowsSameSiteNone 是用户提供的库,可检测用户代理是否不支持 SameSite None

if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
{
    options.SameSite = SameSiteMode.Unspecified;
}

下面的代码演示示例 DisallowsSameSiteNone 方法:

警告

下面的代码仅用于演示:

  • 不应将其视为完整代码。
  • 它不进行维护或受支持。
public static bool DisallowsSameSiteNone(string userAgent)
{
    // Check if a null or empty string has been passed in, since this
    // will cause further interrogation of the useragent to fail.
     if (String.IsNullOrWhiteSpace(userAgent))
        return false;
    
    // Cover all iOS based browsers here. This includes:
    // - Safari on iOS 12 for iPhone, iPod Touch, iPad
    // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
    // - Chrome on iOS 12 for iPhone, iPod Touch, iPad
    // All of which are broken by SameSite=None, because they use the iOS networking
    // stack.
    if (userAgent.Contains("CPU iPhone OS 12") ||
        userAgent.Contains("iPad; CPU OS 12"))
    {
        return true;
    }

    // Cover Mac OS X based browsers that use the Mac OS networking stack. 
    // This includes:
    // - Safari on Mac OS X.
    // This does not include:
    // - Chrome on Mac OS X
    // Because they do not use the Mac OS networking stack.
    if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
        userAgent.Contains("Version/") && userAgent.Contains("Safari"))
    {
        return true;
    }

    // Cover Chrome 50-69, because some versions are broken by SameSite=None, 
    // and none in this range require it.
    // Note: this covers some pre-Chromium Edge versions, 
    // but pre-Chromium Edge does not require SameSite=None.
    if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
    {
        return true;
    }

    return false;
}

针对 SameSite 问题测试应用

与远程站点交互(例如通过第三方登录)的应用需要:

使用可选择加入新 SameSite 行为的客户端版本测试 Web 应用。 Chrome、Firefox 和 Chromium Edge 都具有可用于测试的新的“选择加入”功能标志。 应用应用 SameSite 补丁后,请使用较旧客户端版本(尤其是 Safari)进行测试。 有关详细信息,请参阅本文档中的支持较旧浏览器

使用 Chrome 测试

Chrome 78+ 会提供误导性的结果,因为它实施了临时缓解。 Chrome 78+ 临时缓解允许使用两分钟内产生的 cookie。 启用合适的测试标志后,Chrome 76 或 77 会提供更准确的结果。 要测试新 SameSite 行为,请将 chrome://flags/#same-site-by-default-cookies 切换为“已启用”。 据报告,较旧版本的 Chrome(75 及更早版本)会在使用新的 None 设置时失败。 请参阅本文档中的支持较旧浏览器

Google 不提供较旧的 chrome 版本。 按照下载 Chromium 的说明测试较旧版本的 Chrome。 请勿从通过搜索较旧版本 chrome 而提供的链接下载 Chrome。

从 Canary 版本 80.0.3975.0 开始,可以使用新标志 --enable-features=SameSiteDefaultChecksMethodRigorously 为进行测试而禁用 Lax+POST 临时缓解,以便允许在删除了缓解的功能的最终状态下测试站点和服务。 有关详细信息,请参阅 Chromium 项目 SameSite 更新

使用 Safari 测试

Safari 12 严格执行了先前的草案,在 cookie 中存在新 None 值时会失败。 可通过本文档中浏览器检测代码支持较旧浏览器避免 None。 可使用 MSAL、ADAL 或所使用的任何库来测试 Safari 12、Safari 13 和基于 WebKit 的操作系统样式登录。 问题取决于基础 OS 版本。 已知 OSX Mojave (10.14) 和 iOS 12 存在与新 SameSite 行为相关的兼容性问题。 将操作系统升级到 OSX Catalina (10.15) 或 iOS 13 会解决此问题。 Safari 当前没有用于测试新规范行为的选择加入标志。

使用 Firefox 测试

通过在具有功能标志 network.cookie.sameSite.laxByDefaultabout:config 页面上选择加入,可在版本 68+ 上测试 Firefox 对新标准的支持。 较旧版本的 Firefox 未报告兼容性问题。

使用 Edge 浏览器测试

Edge 支持旧 SameSite 标准。 Edge 版本 44 与新标准不存在任何已知的兼容性问题。

使用 Edge (Chromium) 测试

SameSite 标志在 edge://flags/#same-site-by-default-cookies 页面上进行设置。 未发现与 Edge Chromium 的兼容性问题。

使用 Electron 测试

Electron 的版本包括较旧版本的 Chromium。 例如,Teams 使用的 Electron 版本为 Chromium 66,该版本呈现了较旧的行为。 你必须使用产品所用的 Electron 版本执行你自己的兼容性测试。 请参阅以下部分中的支持较旧浏览器

其他资源

示例 文档
.NET Core Razor Pages ASP.NET Core 3.1 Razor Pages SameSite cookie 示例

可下载和测试以下示例:

示例 文档
.NET Core MVC ASP.NET Core 2.1 MVC SameSite cookie 示例
.NET Core Razor Pages ASP.NET Core 2.1 Razor Pages SameSite cookie 示例

12 月补丁行为变更

针对 .NET Framework 和 .NET Core 2.1 的特定行为变更是 SameSite 属性如何解释 None 值。 在发布补丁之前,值 None 表示“完全不发出属性”,在发布补丁之后,它表示“发出值为 None 的属性”。 发布补丁之后,(SameSiteMode)(-1)SameSite 值会导致不发出属性。

窗体身份验证和会话状态 cookie 的默认 SameSite 值已从 None 更改为 Lax

使用 SameSite 的 API 用法

HttpContext.Response.Cookies.Append 默认为 Unspecified,这意味着不会将 SameSite 属性添加到 cookie,并且客户端会使用其默认行为(对于新浏览器为 Lax,对于旧浏览器为 None)。 下面的代码演示如何将 cookie SameSite 值更改为 SameSiteMode.Lax

HttpContext.Response.Cookies.Append(
                     "name", "value",
                     new CookieOptions() { SameSite = SameSiteMode.Lax });

发出 cookie 的所有 ASP.NET Core 组件都会使用适合于其方案的设置替代前面的默认值。 前面替代的默认值未更改。

组件 cookie 默认
CookieBuilder SameSite Unspecified
Session SessionOptions.Cookie Lax
CookieTempDataProvider CookieTempDataProviderOptions.Cookie Lax
IAntiforgery AntiforgeryOptions.Cookie Strict
Cookie Authentication CookieAuthenticationOptions.Cookie Lax
AddTwitter TwitterOptions.StateCookie Lax
RemoteAuthenticationHandler<TOptions> RemoteAuthenticationOptions.CorrelationCookie None
AddOpenIdConnect OpenIdConnectOptions.NonceCookie None
HttpContext.Response.Cookies.Append CookieOptions Unspecified

历史记录和更改

SameSite 支持首先在 ASP.NET Core 2.0 中使用 2016 年草案标准来实现。 2016 标准是选择加入的。 ASP.NET Core 通过在默认情况下将几个 cookie 设置为 Lax 来选择加入。 在遇到身份验证方面的几个问题后,大多数 SameSite 使用被禁用

2019 年 11 月发布了补丁,用于从 2016 标准更新为 2019 标准。 SameSite 规范的 2019 草案

  • 不与 2016 草案向后兼容。 有关详细信息,请参阅本文档中的支持较旧浏览器
  • 指定 cookie 在默认情况下被视为 SameSite=Lax
  • 指定显式断言 SameSite=None 以便启用跨站点交付的 cookie 应标记为 SecureNone 是要选择退出的新项。
  • 受为 ASP.NET Core 2.1、2.2 和 3.0 发布的补丁所支持。 ASP.NET Core 3.1 具有额外 SameSite 支持。
  • 计划在 2020 年 2 月Chrome 默认启用。 浏览器已开始在 2019 年迁移到此标准。

受从 2016 SameSite 草案标准到 2019 草案标准的更改影响的 API

支持较旧浏览器

2016 SameSite 标准要求必须将未知值视为 SameSite=Strict 值。 从支持 2016 SameSite 标准的较旧浏览器访问的应用在遇到值为 None 的 SameSite 属性时可能会中断。 如果 Web 应用打算支持较旧浏览器,则必须实现浏览器检测。 ASP.NET Core 未实现浏览器检测,因为 User-Agent 值非常不稳定,经常更改。 Microsoft.AspNetCore.CookiePolicy 中的扩展点允许插入特定于用户代理的逻辑。

Startup.Configure 中,添加在调用 UseAuthentication 之前调用 UseCookiePolicy 的代码或任何写入 cookie 的方法:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

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

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Startup.ConfigureServices 中,添加类似于以下内容的代码:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.MinimumSameSitePolicy = (SameSiteMode)(-1);
        options.OnAppendCookie = cookieContext =>
            CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
        options.OnDeleteCookie = cookieContext =>
            CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
    });

    services.AddRazorPages();
}

private void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
    if (options.SameSite == SameSiteMode.None)
    {
        var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
        if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
        {
            options.SameSite = (SameSiteMode)(-1);
        }

    }
}

在上面的示例中,MyUserAgentDetectionLib.DisallowsSameSiteNone 是用户提供的库,可检测用户代理是否不支持 SameSite None

if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
{
    options.SameSite = SameSiteMode.Unspecified;
}

下面的代码演示示例 DisallowsSameSiteNone 方法:

警告

下面的代码仅用于演示:

  • 不应将其视为完整代码。
  • 它不进行维护或受支持。
public static bool DisallowsSameSiteNone(string userAgent)
{
    // Check if a null or empty string has been passed in, since this
    // will cause further interrogation of the useragent to fail.
     if (String.IsNullOrWhiteSpace(userAgent))
        return false;
    
    // Cover all iOS based browsers here. This includes:
    // - Safari on iOS 12 for iPhone, iPod Touch, iPad
    // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
    // - Chrome on iOS 12 for iPhone, iPod Touch, iPad
    // All of which are broken by SameSite=None, because they use the iOS networking
    // stack.
    if (userAgent.Contains("CPU iPhone OS 12") ||
        userAgent.Contains("iPad; CPU OS 12"))
    {
        return true;
    }

    // Cover Mac OS X based browsers that use the Mac OS networking stack. 
    // This includes:
    // - Safari on Mac OS X.
    // This does not include:
    // - Chrome on Mac OS X
    // Because they do not use the Mac OS networking stack.
    if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
        userAgent.Contains("Version/") && userAgent.Contains("Safari"))
    {
        return true;
    }

    // Cover Chrome 50-69, because some versions are broken by SameSite=None, 
    // and none in this range require it.
    // Note: this covers some pre-Chromium Edge versions, 
    // but pre-Chromium Edge does not require SameSite=None.
    if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
    {
        return true;
    }

    return false;
}

针对 SameSite 问题测试应用

与远程站点交互(例如通过第三方登录)的应用需要:

使用可选择加入新 SameSite 行为的客户端版本测试 Web 应用。 Chrome、Firefox 和 Chromium Edge 都具有可用于测试的新的“选择加入”功能标志。 应用应用 SameSite 补丁后,请使用较旧客户端版本(尤其是 Safari)进行测试。 有关详细信息,请参阅本文档中的支持较旧浏览器

使用 Chrome 测试

Chrome 78+ 会提供误导性的结果,因为它实施了临时缓解。 Chrome 78+ 临时缓解允许使用两分钟内产生的 cookie。 启用合适的测试标志后,Chrome 76 或 77 会提供更准确的结果。 要测试新 SameSite 行为,请将 chrome://flags/#same-site-by-default-cookies 切换为“已启用”。 据报告,较旧版本的 Chrome(75 及更早版本)会在使用新的 None 设置时失败。 请参阅本文档中的支持较旧浏览器

Google 不提供较旧的 chrome 版本。 按照下载 Chromium 的说明测试较旧版本的 Chrome。 请勿从通过搜索较旧版本 chrome 而提供的链接下载 Chrome。

从 Canary 版本 80.0.3975.0 开始,可以使用新标志 --enable-features=SameSiteDefaultChecksMethodRigorously 为进行测试而禁用 Lax+POST 临时缓解,以便允许在删除了缓解的功能的最终状态下测试站点和服务。 有关详细信息,请参阅 Chromium 项目 SameSite 更新

使用 Safari 测试

Safari 12 严格执行了先前的草案,在 cookie 中存在新 None 值时会失败。 可通过本文档中浏览器检测代码支持较旧浏览器避免 None。 可使用 MSAL、ADAL 或所使用的任何库来测试 Safari 12、Safari 13 和基于 WebKit 的操作系统样式登录。 问题取决于基础 OS 版本。 已知 OSX Mojave (10.14) 和 iOS 12 存在与新 SameSite 行为相关的兼容性问题。 将操作系统升级到 OSX Catalina (10.15) 或 iOS 13 会解决此问题。 Safari 当前没有用于测试新规范行为的选择加入标志。

使用 Firefox 测试

通过在具有功能标志 network.cookie.sameSite.laxByDefaultabout:config 页面上选择加入,可在版本 68+ 上测试 Firefox 对新标准的支持。 较旧版本的 Firefox 未报告兼容性问题。

使用 Edge 浏览器测试

Edge 支持旧 SameSite 标准。 Edge 版本 44 与新标准不存在任何已知的兼容性问题。

使用 Edge (Chromium) 测试

SameSite 标志在 edge://flags/#same-site-by-default-cookies 页面上进行设置。 未发现与 Edge Chromium 的兼容性问题。

使用 Electron 测试

Electron 的版本包括较旧版本的 Chromium。 例如,Teams 使用的 Electron 版本为 Chromium 66,该版本呈现了较旧的行为。 你必须使用产品所用的 Electron 版本执行你自己的兼容性测试。 请参阅以下部分中的支持较旧浏览器

其他资源

示例 文档
.NET Core MVC ASP.NET Core 2.1 MVC SameSite cookie 示例
.NET Core Razor Pages ASP.NET Core 2.1 Razor Pages SameSite cookie 示例