在 ASP.NET 中使用 SameSite Cookie

作者:Rick Anderson

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

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

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

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

有关安装 2019 .Net SameSite 更新后应用程序的问题,请参阅 已知问题

在 ASP.NET 4.7.2 和 4.8 中使用 SameSite

2019 年 12 月发布更新以来,.Net 4.7.2 和 4.8 支持 SameSite 的 2019 年草案标准。 开发人员可以使用 HttpCookie.SameSite 属性以编程方式控制 SameSite 标头的值。 将 SameSite 属性设置为 StrictLaxNone 会导致使用 Cookie 在网络上写入这些值。 将其设置为 (SameSiteMode)(-1) 等于 表示网络上不应包含 Cookie 的 SameSite 标头。 HttpCookie.Secure 属性(配置文件中的“requireSSL”)可用于将 Cookie 标记为或不标记为Secure

HttpCookie 实例将默认为 SameSite=(SameSiteMode)(-1)Secure=false。 这些默认值可以在配置节中 system.web/httpCookies 重写,其中 字符串 "Unspecified" 是 的 (SameSiteMode)(-1)友好仅配置语法:

<configuration>
 <system.web>
  <httpCookies sameSite="[Strict|Lax|None|Unspecified]" requireSSL="[true|false]" />
 <system.web>
<configuration>

ASP.Net 还会针对以下功能发布四个特定的 Cookie:匿名身份验证、表单身份验证、会话状态和角色管理。 与任何其他 HttpCookie 实例一样,可以使用 和 Secure 属性操作SameSite在运行时中获取的这些 Cookie 的实例。 但是,由于 SameSite 标准的拼凑出现,这四个功能 Cookie 的配置选项不一致。 下面显示了具有默认值的相关配置部分和属性。 如果某个功能没有 SameSiteSecure 相关属性,则该功能将回退到上面讨论的 system.web/httpCookies 部分中配置的默认值。

<configuration>
 <system.web>
  <anonymousIdentification cookieRequireSSL="false" /> <!-- No config attribute for SameSite -->
  <authentication>
   <forms cookieSameSite="Lax" requireSSL="false" />
  </authentication>
  <sessionState cookieSameSite="Lax" /> <!-- No config attribute for Secure -->
  <roleManager cookieRequireSSL="false" /> <!-- No config attribute for SameSite -->
 <system.web>
<configuration>

注意:“未指定”目前仅适用于 system.web/httpCookies@sameSite 。 我们希望在将来的更新中添加与前面显示的 cookieSameSite 属性类似的语法。 代码中的设置 (SameSiteMode)(-1) 仍然适用于这些 Cookie 的实例。*

如果你以英语以外的语言阅读本文,请在此 GitHub 讨论问题 中告知我们,如果你希望查看母语的代码注释。

重定 .NET 应用目标

以 .NET 4.7.2 或更高版本为目标:

  • 确保 web.config 包含以下内容:

    <system.web>
      <compilation targetFramework="4.7.2"/>
      <httpRuntime targetFramework="4.7.2"/>
    </system.web>
    
    
  • 验证项目文件是否包含正确的 TargetFrameworkVersion

    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    

    .NET 迁移指南提供了更多详细信息。

  • 验证项目中的 NuGet 包是否面向正确的框架版本。 可以通过检查 packages.config 文件来验证正确的框架版本,例如:

    <?xml version="1.0" encoding="utf-8"?>
    <packages>
      <package id="Microsoft.AspNet.Mvc" version="5.2.7" targetFramework="net472" />
      <package id="Microsoft.ApplicationInsights" version="2.4.0" targetFramework="net451" />
    </packages>
    

    在前面的 packages.config 文件中, Microsoft.ApplicationInsights 包:

    • 针对 .NET 4.5.1。
    • 如果存在面向框架目标的更新包,则应将其 targetFramework 属性更新为 net472

低于 4.7.2 的 .NET 版本

Microsoft 不支持低于 4.7.2 的 .NET 版本来编写同一站点 Cookie 属性。 我们还没有找到一种可靠的方法来:

  • 确保根据浏览器版本正确写入属性。
  • 截获和调整较旧框架版本上的身份验证和会话 Cookie。

12 月补丁行为变更

.NET Framework的特定行为更改是 属性解释None值的方式SameSite

  • 在修补之前,值 None 表示:
    • 根本不发出 属性。
  • 修补后:
    • 值为 None 表示“发出值为 的属性 None”。
    • SameSite值为 (SameSiteMode)(-1) 会导致不发出 属性。

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

更改对浏览器的影响摘要

如果安装修补程序并使用 发出 Cookie SameSite.None,则会发生以下两种情况之一:

  • Chrome v80 将根据新实现处理此 Cookie,并且不会对 Cookie 强制实施相同的站点限制。
  • 任何尚未更新以支持新实现的浏览器都将遵循旧实现。 旧实现显示:
    • 如果看到不理解的值,请忽略它并切换到严格的相同站点限制。

因此,应用在 Chrome 中中断,或者在许多其他位置中断。

历史记录和更改

SameSite 支持首先使用 2016 年草案标准在 .NET 4.7.2 中实现。

Windows 2019 年 11 月 19 日更新将 .NET 4.7.2+ 从 2016 标准更新为 2019 标准。 其他版本的 Windows 即将推出其他更新。 有关详细信息,请参阅在 .NET Framework 中支持 SameSite 的知识库文章

SameSite 规范的 2019 草案:

  • 不与 2016 草案向后兼容。 有关详细信息,请参阅本文档中的支持较旧浏览器
  • 指定默认情况下将 Cookie 视为 SameSite=Lax
  • 指定显式断言 SameSite=None 以启用跨站点传递的 Cookie 也应标记为 Secure
  • 受上面列出的知识库中所述发布的修补程序的支持。
  • 计划在 2020 年 2 月Chrome 默认启用。 浏览器已开始在 2019 年迁移到此标准。

已知问题

由于 2016 年和 2019 年草案规范不兼容,2019 年 11 月 .Net Framework 更新引入了一些可能会中断的更改。

  • 会话状态和表单身份验证 Cookie 现在将写入网络, Lax 而不是未指定。
    • 虽然大多数应用都使用 SameSite=Lax Cookie,但跨站点或应用程序 iframe POST 的应用可能会发现其会话状态或表单授权 Cookie 未按预期使用。 若要解决此问题,请 cookieSameSite 更改前面所述的相应配置部分中的值。
  • 在代码或配置中显式设置 SameSite=None 的 HttpCookies 现在具有使用 Cookie 编写的值,而以前省略该值。 这可能会导致仅支持 2016 草稿标准的旧浏览器出现问题。
    • 使用 Cookie 面向支持 2019 草稿标准的 SameSite=None 浏览器时,请记住还要标记它们 Secure ,否则可能无法识别它们。
    • 若要还原 2016 年不写入 SameSite=None的行为,请使用应用设置 aspnet:SupressSameSiteNone=true。 请注意,这适用于应用中的所有 HttpCookie。

有关 Azure 应用服务 如何在 .Net 4.7.2 应用中配置 SameSite 行为的信息,请参阅Azure 应用服务 - SameSite Cookie 处理和.NET Framework 4.7.2 修补程序。

支持较旧浏览器

2016 SameSite 标准要求必须将未知值视为 SameSite=Strict 值。 从支持 2016 SameSite 标准的较旧浏览器访问的应用在遇到值为 None 的 SameSite 属性时可能会中断。 如果 Web 应用打算支持较旧浏览器,则必须实现浏览器检测。 ASP.NET 不实现浏览器检测,因为 User-Agents 值具有高度可变性且经常更改。

Microsoft 修复此问题的方法是帮助你实现浏览器检测组件,以在已知浏览器不支持该属性时从 Cookie 中去除 sameSite=None 该属性。 谷歌的建议是发出双 Cookie,一个有新属性,一个根本没有属性。 然而,我们认为谷歌的建议是有限的。 某些浏览器(尤其是移动浏览器)对站点或域名可以发送的 Cookie 数限制非常小。 发送多个 Cookie(尤其是身份验证 Cookie 等大型 Cookie)可能会非常快地达到移动浏览器限制,从而导致难以诊断和修复的应用故障。 此外,作为框架,还有一个大型的第三方代码和组件生态系统,这些代码和组件可能不会更新为使用双重 Cookie 方法。

此 GitHub 存储库的示例项目中使用的浏览器检测代码包含在两个文件中

这些检测是我们所看到的支持 2016 标准的最常见浏览器代理,需要对其完全删除属性。 它不是一个完整的实现:

  • 你的应用可能会看到我们的测试站点没有的浏览器。
  • 应准备好根据需要为环境添加检测。

连接检测的方式因使用的 .NET 版本和 Web 框架而异。 可以在 HttpCookie 调用站点上调用以下代码:

private void CheckSameSite(HttpContext httpContext, HttpCookie cookie)
{
    if (cookie.SameSite == SameSiteMode.None)
    {
        var userAgent = httpContext.Request.UserAgent;
        if (BrowserDetection.DisallowsSameSiteNone(userAgent))
        {
            cookie.SameSite = (SameSiteMode)(-1);
        }
    }
}

请参阅以下 ASP.NET 4.7.2 SameSite Cookie 主题:

确保网站重定向到 HTTPS

对于 ASP.NET 4.x、WebForms 和 MVC, 可以使用 IIS 的 URL 重写 功能将所有请求重定向到 HTTPS。 以下 XML 显示了一个示例规则:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Redirect to https" stopProcessing="true">
          <match url="(.*)"/>
          <conditions>
            <add input="{HTTPS}" pattern="Off"/>
            <add input="{REQUEST_METHOD}" pattern="^get$|^head$" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent"/>
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

在本地安装 中,IIS URL 重写 是一项可选功能,可能需要安装。

针对 SameSite 问题测试应用

必须使用你支持的浏览器测试应用,并浏览涉及 Cookie 的方案。 Cookie 方案通常涉及

  • 登录表单
  • 外部登录机制,如 Facebook、Azure AD、OAuth 和 OIDC
  • 接受来自其他网站的请求的页面
  • 设计为嵌入 iframe 的应用中的页面

应检查,在应用中正确创建、持久化和删除 Cookie。

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

使用可选择加入新 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 更新

使用 Chrome 80+ 进行测试

下载 支持其新属性的 Chrome 版本。 在撰写本文时,当前版本为 Chrome 80。 Chrome 80 需要启用 标志 chrome://flags/#same-site-by-default-cookies 才能使用新行为。 还应启用 (chrome://flags/#cookies-without-same-site-must-be-secure) ,以测试未启用相同Site 属性的 Cookie 即将发生的行为。 Chrome 80 位于目标上,用于切换将没有 属性的 Cookie 视为 SameSite=Lax,尽管某些请求具有计时宽限期。 若要禁用计时宽限期,可以使用以下命令行参数启动 Chrome 80:

--enable-features=SameSiteDefaultChecksMethodRigorously

Chrome 80 在浏览器控制台中显示有关缺少同一站点属性的警告消息。 使用 F12 打开浏览器控制台。

使用 Safari 测试

Safari 12 严格实现了上一个草稿,当新 None 值位于 Cookie 中时失败。 可通过本文档中浏览器检测代码支持较旧浏览器避免 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 版本执行自己的兼容性测试。 请参阅 支持较旧的浏览器

还原 SameSite 修补程序

可以在.NET Framework应用中将更新的 sameSite 行为还原为其以前的行为,其中不为 值None发出 sameSite 属性,还原身份验证和会话 Cookie 以不发出该值。 这应被视为非常 临时的修补程序,因为 Chrome 更改会中断任何外部 POST 请求或用户使用支持标准更改的浏览器进行身份验证。

还原 .NET 4.7.2 行为

更新 web.config 以包含以下配置设置:

<configuration> 
  <appSettings>
    <add key="aspnet:SuppressSameSiteNone" value="true" />
  </appSettings>
 
  <system.web> 
    <authentication> 
      <forms cookieSameSite="None" /> 
    </authentication> 
    <sessionState cookieSameSite="None" /> 
  </system.web> 
</configuration>

其他资源