在 ASP.NET Core 中使用 SameSite cookieWork with SameSite cookies in ASP.NET Core

本文内容

作者:Rick Anderson

SameSite 是一种IETF草案标准,旨在提供一些针对跨站点请求伪造(CSRF)攻击的防护。最初在2016中起草草案标准版已在2019中更新。更新的标准与以前的标准不是向后兼容,以下是最明显的区别:

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

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

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

SameSite 测试示例代码SameSite test sample code

可下载和测试以下示例:

示例Document
.NET Core MVCASP.NET Core 2.1 MVC SameSite cookie 示例
.NET Core Razor PagesASP.NET Core 2.1 Razor Pages SameSite cookie 示例

可下载并测试以下示例:

示例Document
.NET Core Razor PagesASP.NET Core 3.1 Razor Pages SameSite cookie 示例

.NET Core 对 sameSite 属性的支持.NET Core support for the sameSite attribute

由于2.2 年12月2019更新发布,.NET Core 支持 SameSite 的2019草案标准。开发人员能够使用 HttpCookie.SameSite 属性以编程方式控制 sameSite 属性的值。如果将 SameSite 属性设置为 Strict、宽松或 None,则会在网络上用 cookie 写入这些值。如果将其设置为(SameSiteMode)(-1),则表示网络上不应包含 cookie 为的 sameSite 属性

  1. var cookieOptions = new CookieOptions
  2. {
  3. // Set the secure flag, which Chrome's changes will require for SameSite none.
  4. // Note this will also require you to be running on HTTPS.
  5. Secure = true,
  6. // Set the cookie to HTTP only which is good practice unless you really do need
  7. // to access it client side in scripts.
  8. HttpOnly = true,
  9. // Add the SameSite attribute, this will emit the attribute with a value of none.
  10. // To not emit the attribute at all set
  11. // SameSite = (SameSiteMode)(-1)
  12. SameSite = SameSiteMode.None
  13. };
  14. // Add the cookie to the response cookie collection
  15. Response.Cookies.Append("MyCookie", "cookieValue", cookieOptions);

.NET Core 3.0 支持更新后的 SameSite 值,并将额外的枚举值 SameSiteMode.Unspecified 添加到 SameSiteMode 枚举。此新值指示不应向 cookie 发送任何 sameSite。

12月修补程序行为更改December patch behavior changes

.NET Framework 和 .NET Core 2.1 的特定行为更改是 SameSite 属性解释 None 值的方式。在修补程序的值 None 表示 "根本不发出属性" 之后,修补程序表示 "发出值为 None的属性"。修补后 SameSite 值为 (SameSiteMode)(-1) 会导致不发出属性。

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

使用 SameSite 的 API 用法API usage with SameSite

Httpcontext.current默认值为 Unspecified,这意味着,不会向 cookie 中添加 SameSite 属性,客户端将使用其默认行为(对于新浏览器而言,对于旧浏览器则为 "无")。下面的代码演示如何将 cookie SameSite 值更改为 SameSiteMode.Lax

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

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

组件票证默认
CookieBuilderSameSiteUnspecified
SessionSessionOptionsLax
CookieTempDataProviderCookieTempDataProviderOptionsLax
IAntiforgeryAntiforgeryOptionsStrict
Cookie 身份验证Cookieauthenticationoptions.authenticationtypeLax
AddTwitterTwitterOptions.StateCookieLax
RemoteAuthenticationHandler<TOptions>CorrelationCookieNoneRemoteAuthenticationOptions.CorrelationCookieNone
AddOpenIdConnectOpenIdConnectOptions.NonceCookieNone
Httpcontext.current。追加CookieOptionsUnspecified

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 属性:

历史记录和更改History and changes

SameSite 支持在2.0 中第一次 ASP.NET Core 实现,使用2016 草案标准2016标准已选择加入。ASP.NET Core 选择在默认情况下 Lax 的几个 cookie。在遇到身份验证的几个问题后,大多数 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月启用。浏览器已开始在2019中移动到此标准。

受从 2016 SameSite 草案标准到2019草案标准的更改影响的 ApiAPIs impacted by the change from the 2016 SameSite draft standard to the 2019 draft standard

支持旧版浏览器Supporting older browsers

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

Startup.Configure中,添加调用 UseCookiePolicy 的代码,然后调用 UseAuthentication任何写入 cookie 的方法:

  1. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  2. {
  3. if (env.IsDevelopment())
  4. {
  5. app.UseDeveloperExceptionPage();
  6. }
  7. else
  8. {
  9. app.UseExceptionHandler("/Error");
  10. app.UseHsts();
  11. }
  12. app.UseHttpsRedirection();
  13. app.UseStaticFiles();
  14. app.UseRouting();
  15. app.UseCookiePolicy();
  16. app.UseAuthentication();
  17. app.UseAuthorization();
  18. app.UseEndpoints(endpoints =>
  19. {
  20. endpoints.MapRazorPages();
  21. });
  22. }

Startup.ConfigureServices中,添加类似于下面的代码:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.Configure<CookiePolicyOptions>(options =>
  4. {
  5. options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
  6. options.OnAppendCookie = cookieContext =>
  7. CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
  8. options.OnDeleteCookie = cookieContext =>
  9. CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
  10. });
  11. services.AddRazorPages();
  12. }
  13. private void CheckSameSite(HttpContext httpContext, CookieOptions options)
  14. {
  15. if (options.SameSite == SameSiteMode.None)
  16. {
  17. var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
  18. if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
  19. {
  20. options.SameSite = SameSiteMode.Unspecified;
  21. }
  22. }
  23. }
  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.Configure<CookiePolicyOptions>(options =>
  4. {
  5. options.MinimumSameSitePolicy = (SameSiteMode)(-1);
  6. options.OnAppendCookie = cookieContext =>
  7. CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
  8. options.OnDeleteCookie = cookieContext =>
  9. CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
  10. });
  11. services.AddRazorPages();
  12. }
  13. private void CheckSameSite(HttpContext httpContext, CookieOptions options)
  14. {
  15. if (options.SameSite == SameSiteMode.None)
  16. {
  17. var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
  18. if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
  19. {
  20. options.SameSite = (SameSiteMode)(-1);
  21. }
  22. }
  23. }

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

  1. if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
  2. {
  3. options.SameSite = SameSiteMode.Unspecified;
  4. }

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

警告

以下代码仅用于演示:

  • 不应将其视为已完成。
  • 它不维护或不受支持。
  1. public static bool DisallowsSameSiteNone(string userAgent)
  2. {
  3. // Check if a null or empty string has been passed in, since this
  4. // will cause further interrogation of the useragent to fail.
  5. if (String.IsNullOrWhiteSpace(userAgent))
  6. return false;
  7. // Cover all iOS based browsers here. This includes:
  8. // - Safari on iOS 12 for iPhone, iPod Touch, iPad
  9. // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
  10. // - Chrome on iOS 12 for iPhone, iPod Touch, iPad
  11. // All of which are broken by SameSite=None, because they use the iOS networking
  12. // stack.
  13. if (userAgent.Contains("CPU iPhone OS 12") ||
  14. userAgent.Contains("iPad; CPU OS 12"))
  15. {
  16. return true;
  17. }
  18. // Cover Mac OS X based browsers that use the Mac OS networking stack.
  19. // This includes:
  20. // - Safari on Mac OS X.
  21. // This does not include:
  22. // - Chrome on Mac OS X
  23. // Because they do not use the Mac OS networking stack.
  24. if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
  25. userAgent.Contains("Version/") && userAgent.Contains("Safari"))
  26. {
  27. return true;
  28. }
  29. // Cover Chrome 50-69, because some versions are broken by SameSite=None,
  30. // and none in this range require it.
  31. // Note: this covers some pre-Chromium Edge versions,
  32. // but pre-Chromium Edge does not require SameSite=None.
  33. if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
  34. {
  35. return true;
  36. }
  37. return false;
  38. }

测试应用的 SameSite 问题Test apps for SameSite problems

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

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

用 Chrome 测试Test with 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

从未加 80.0.3975.0的抑制版本开始,可以使用新的标志 —enable-features=SameSiteDefaultChecksMethodRigorously 禁用宽松的 + 后续暂时缓解功能,以允许在删除缓解功能的最终状态下测试站点和服务。有关详细信息,请参阅 Chromium 项目SameSite Updates

用 Safari 测试Test with Safari

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

用 Firefox 测试Test with Firefox

可以在版本 68 + 上测试对新标准的 Firefox 支持,方法是在 "about:config" 页面上选择 "network.cookie.sameSite.laxByDefault的功能标志。以前版本的 Firefox 没有出现兼容性问题的报告。

通过 Edge 浏览器进行测试Test with Edge browser

Edge 支持旧的 SameSite 标准。边缘版本44与新的标准没有任何已知的兼容性问题。

带边缘测试(Chromium)Test with Edge (Chromium)

edge://flags/#same-site-by-default-cookies 页上设置 SameSite 标志。未发现边缘 Chromium 的兼容性问题。

用 Electron 进行测试Test with Electron

Electron 的版本包括较早版本的 Chromium。例如,团队使用的 Electron 版本为 Chromium 66,该版本展示了较旧的行为。你必须使用你的产品使用的 Electron 版本执行你自己的兼容性测试。请参阅下一节中的支持旧版浏览器

其他资源Additional resources

示例Document
.NET Core MVCASP.NET Core 2.1 MVC SameSite cookie 示例
.NET Core Razor PagesASP.NET Core 2.1 Razor Pages SameSite cookie 示例
示例Document
.NET Core Razor PagesASP.NET Core 3.1 Razor Pages SameSite cookie 示例