在较多的项目中,Cookie 是比较常用的一种状态保持的选择。比如常见的例子:用户登录成功后,服务器通过 set-cookie 将会话Id设置到当前域下,前端在调用后端接口时,会自动将同域下的 Cookie 携带上,然后后端接口再获取到会话Id进行用户登录状态的合法性验证。
了解过 Cookie 相关知识的同学都比较清楚,Cookie 的使用上一不小心就会出现安全性问题,如:CSRF(Cross-site request forgery 跨站请求伪造)、XSSI(Cross Site Script Inclusion 跨站脚本包含)攻击,网上也有很多这一类的介绍文章。为了降低这类风险,谷歌开发了一种称为 Cookie SameSite 安全机制,并已经在主流的浏览器中被应用较长时间。
什么是 Cookie SameSite
SameSite 是 Cookie 中的一个属性,它是用来约束第三方(跨域) Cookie 传递的,SameSite 有 Strict
、Lax
、None
三个值可设置。
Strict
Strict 是最严格的策略,在 Strict 下,浏览器不允许将 Cookie 从 A 域发送到B域。假设用户之前在 B 域下已经是登录状态,A 域某页面上有一个 B 域的跳转链接,当通过点击 A 域下这个跳转链接进入 B 域时,B 域下会出现未登录的情况。这种策略的安全性很高,但其实在用户体验上并不好,所以使用上并不常见。
set-cookie: sid=0920770230c103809305605a;samesite=strict
Lax
Lax 策略相比 Strict 会宽一些,大多数情况也是不发送第三方 Cookie,但 链接(<a href="..."></a>)
、预加载请求 <link rel="prerender" href="..."/>
、GET 表单 <form method="GET" action="...">
除外。假设用户之前在 B 域下已经是登录状态,A 域某页面上有一个 B 域的链接,当通过点击 A 域下这个跳转链接进入 B 域时,B 域下将依然显示登录状态。但如果在 A 域下发起了一个 Ajax POST 请求到 B 域的接口,这时接口是获取不到相关 Cookie 的。虽然 Lax 策略依然会存在一定的风险,但通常来说是相对合适的选择,所以 Lax 在一些开发语言中被设置为 Cookie SameSite 的默认值。
set-cookie: sid=0920770230c103809305605a;samesite=lax
None
在实际使用中,也会存在有一些场景确实是需要支持跨域 Cookie 传递(如比较常见的 A 域中 IFrame 嵌入 B 域链接进行使用),这时候可以选择将 SameSite 设置为 None,但是使用 None 却是有前提条件的,它要求必须同时设置 Secure 属性为 true,而 Secure 设置 true 又要求 B 域是基于 HTTPS 协议来访问的,否则依然无效。
set-cookie: sid=0920770230c103809305605a; secure; samesite=none
在 .NET Core 中的使用
下面将以 ASP.NET Core Web 应用程序为例
var options = new CookieOptions
{Expires = DateTime.Now.AddHours(1),
};
_httpContextAccessor.HttpContext.Response.Cookies.Append("sid", "0920770230c103809305605a", options);
常规情况下,以上代码即可完成 Cookie 的写入,CookieOptions 还支持一些其他的配置,可根据实际情况设定。不过需要注意的是在 .NET Core 2.2 和 .NET Core 3.1 中,Cookie 的 SameSite 属性默认值是不也一样的,升级需要注意。
2.2 中的默认值是 SameSiteMode.Lax
3.1 中的默认值变成了 SameSiteMode.Unspecified
(表示不写入 SameSite 属性值,继承浏览器默认的 Cookie 策略)
IFrame 中如何解决 SameSite 问题
在基于 ASP.NET Core set-cookie 的情况下,如果需要当前域被其他域的 IFrame 引入使用,最基本的要求是站点必须基于 HTTPS 协议,然后修改代码将 SameSite 属性值设置为 None,Secure 设置为 true。
var options = new CookieOptions
{SameSite = SameSiteMode.None,Secure = true
};