最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深入理解 Cookie 的 SameSite 属性

    正文概述 掘金(郑昊川)   2021-05-19   1396

    Cookie 简介

    HTTP 协议是无状态的,但可以通过 Cookie 来维持客户端与服务端之间的“会话状态”。

    简单来说就是:服务端通过 Set-Cookie 响应头设置 Cookie 到客户端,而客户端在下次向服务器发送请求时添加名为 Cookie 的请求头,以携带服务端之前“埋下”的内容,从而使得服务端可以识别客户端的身份。

    举个简单的?:

    // 服务端
    const http = require("http");
    
    http
      .createServer((req, res) => {
        if (req.url == "/") {
          res.end("hello world");
        } else if (req.url == "/favicon.ico") {
          res.statusCode = 204;
          res.end();
        } else {
          res.writeHead(200, [
            ["Set-Cookie", "name=haochuan9421"], // 设置 cookie
          ]);
          res.end("some data");
        }
      })
      .listen(80);
    
    // 客户端
    var xhr = new XMLHttpRequest();
    xhr.open('GET', "/someapi");
    xhr.send();
    

    深入理解 Cookie 的 SameSite 属性

    当客户端再次发起请求时就会自动携带上之前“埋下”的 Cookie:

    深入理解 Cookie 的 SameSite 属性

    简单的介绍完 Cookie 后,我们来看一下它的 SameSite 属性。

    SameSite 属性

    深入理解 Cookie 的 SameSite 属性

    SameSite 有三个可选值:

    • Strict
    • Lax
    • None

    从 Chrome 80 开始,如果不指定 SameSite 就等效于设置为 Lax。你可以通过 chrome://flags/#same-site-by-default-cookies 禁用这个行为,禁用后不指定 SameSite 就等效于设置为 None。关于他们的区别我们稍后结合具体的场景来介绍。

    深入理解 Cookie 的 SameSite 属性

    先来看看上图中出现的 third-party 这个概念,对 Cookie 来说什么是第三方 呢,"SameSite 同站" 又是什么意思呢?

    举个例子:假设我们的网站是 bar.com ,当我们引入 foo.com 的图片时,图片服务如果设置了 cookie,我们就称之为”第三方 cookie“。

    是否是 “第三方” 不是根据同源策略(协议,主机,端口)来判断,而是 PSL(公共后缀列表),比如 'bar.com' 和 'a.bar.com' 就不是 “第三方” 的关系,而是“同站”。检测两个域名是否是同站的方法也很简单,比如你在 'a.bar.com' 网站,设置 document.domain = 'bar.com' 并不会报错,但如何设置 document.domain = 'foo.com' 就会报错,那么 'foo.com' 相对于 'a.bar.com' 来说就是第三方。

    深入理解 Cookie 的 SameSite 属性

    深入理解 Cookie 的 SameSite 属性

    当端口不同时,比如我们的网站是 bar.com:8080 ,我们引入 bar.com:9000 的图片时不会判定为第三方的

    但是协议不同默认会判定为第三方。比如我们的网站是 bar.com ,我们引入 bar.com 的图片时会判定为第三方。不过在 Chrome 中你可以通过 chrome://flags/#schemeful-same-site 来忽略协议的限制。

    深入理解 Cookie 的 SameSite 属性

    除了图片这种场景,向第三方网站发起 AJAX/fetch 请求、嵌入第三方网站的 iframe、表单提交到第三方网站、链接跳转到第三方网站等都可能涉及到“第三方 cookie”。针对这些可能出现 “第三方cookie” 的场景,SameSite 设置为不同的值又会有哪些不同的效果呢?让我们来一一探究(多图警告?):

    1. AJAX 请求

    当我们跨域发送 AJAX 请求时,由于浏览器同源策略的限制,我们的请求是无法发送的:

    深入理解 Cookie 的 SameSite 属性

    不过我们可以使用 CORS 的方式来解决跨域的问题:

    const http = require("http");
    
    http
      .createServer((req, res) => {
        if (req.url == "/") {
          res.end("hello world");
        } else if (req.url == "/favicon.ico") {
          res.statusCode = 204;
          res.end();
        } else {
          res.writeHead(200, [
            ["Set-Cookie", "name=haochuan9421"], // 设置 cookie
            ["Access-Control-Allow-Origin", "*"], // 允许跨域请求
          ]);
          res.end("some data");
        }
      })
      .listen(80, "0.0.0.0");
    

    深入理解 Cookie 的 SameSite 属性

    但是当我们再次发起请求时,虽然这个跨域请求的响应头中有设置 Cookie,却发现下次请求时并不会携带之前服务器设置的 Cookie。

    深入理解 Cookie 的 SameSite 属性

    这就带来一个问题,我们失去了利用 Cookie 来维持服务端与客户端“会话状态”的能力。那么如何在向第三方网站请求的时候携带 Cookie 呢?需要满足如下条件:

    1. 网站开启 https 并将 Cookie 的 Secure 属性设置为 true
    2. Access-Control-Allow-Origin 设置为具体的 origin,而不是 *
    3. Access-Control-Allow-Credentials 设置为 true
    4. SameSite 属性设置为 None
    const https = require("https");
    const fs = require("fs");
    
    https
      .createServer(
        {
          key: fs.readFileSync(__dirname + "/key.pem"),
          cert: fs.readFileSync(__dirname + "/cert.pem"),
        },
        (req, res) => {
          if (req.url == "/") {
            res.end("hellow world");
          } else if (req.url == "/favicon.ico") {
            res.statusCode = 204;
            res.end();
          } else {
            res.writeHead(200, [
              ["Set-Cookie", "name=haochuan9421; Secure; SameSite=None"],
              ...(req.headers.origin // 跨域请求时请求头中会包含 origin,也就是请求发出的网站
                ? [
                    ["Access-Control-Allow-Origin", req.headers.origin], // 不可以使用 *,必须指定
                    ["Access-Control-Allow-Credentials", "true"], // 设置允许跨域请求携带 Cookie
                  ]
                : []),
            ]);
            res.end("some data");
          }
        }
      )
      .listen(443, "0.0.0.0");
    

    满足上面的条件之后,跨域请求就可以携带 Cookie 了:

    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
    xhr.open('GET', "https://www.bar.com/someapi");
    xhr.send();
    

    深入理解 Cookie 的 SameSite 属性

    这四个条件缺一不可:

    当不开启 https 的时候:

    深入理解 Cookie 的 SameSite 属性

    当不设置 Secure 属性:

    深入理解 Cookie 的 SameSite 属性

    当 Access-Control-Allow-Origin 设置为 * 时

    深入理解 Cookie 的 SameSite 属性

    当 Access-Control-Allow-Credentials 的值不为 true 时

    深入理解 Cookie 的 SameSite 属性

    当 SameSite 属性设置为 Strict 或 Lax 时

    深入理解 Cookie 的 SameSite 属性 深入理解 Cookie 的 SameSite 属性

    对于使用浏览器的 fetch API 发送请求也是一样的,使用 fetch 发起跨域请求时如果想携带 cookie,需要设置 "credentials" 为 "include":

    fetch("https://www.bar.com/somedata", {
      "method": "GET",
      "credentials": "include"
    })
    

    2. 嵌套第三方 iframe

    const https = require("https");
    const fs = require("fs");
    
    https
      .createServer(
        {
          key: fs.readFileSync(__dirname + "/key.pem"),
          cert: fs.readFileSync(__dirname + "/cert.pem"),
        },
        (req, res) => {
          console.log(req.headers.host);
          if (req.url == "/") {
            if (req.headers.host === "www.foo.com") {
              res.setHeader("Content-Type", "text/html;charset=utf-8");
              res.end(`<div>这是父页面</div>
    <iframe src="https://www.bar.com/"></iframe>`);
            } else {
              res.writeHead(200, [
                ["Set-Cookie", "name=haochuan9421; Secure; SameSite=None"],
                ["Content-Type", "text/html;charset=utf-8"],
              ]);
              res.end(`<div>这是子页面</div>`);
            }
          } else {
            res.statusCode = 204;
            res.end();
          }
        }
      )
      .listen(443, "0.0.0.0");
    
    

    如果设置了SameSite 为 Strict: 深入理解 Cookie 的 SameSite 属性 如果设置了SameSite 为 Lax: 深入理解 Cookie 的 SameSite 属性 如果不指定 SameSite: 深入理解 Cookie 的 SameSite 属性 如果设置了 SameSite 为 None: 深入理解 Cookie 的 SameSite 属性

    这说明只有明确的指定了 SameSite 为 None 时,跨域 iframe 页面被引入时 Cookie 才能生效。

    举例说明一下:假设我们希望在自己的网站内嵌 bilibili 的视频播放器,直接通过 iframe 把 B 站播放器引入到我们自己的网站是无法使用 1080p 画质的。

    <iframe
      src="//player.bilibili.com/player.html?bvid=BV1Vv41157uK&high_quality=1"
      allowfullscreen="allowfullscreen"
      width="100%"
      height="500"
      scrolling="no"
      frameborder="0"
    ></iframe>
    

    深入理解 Cookie 的 SameSite 属性

    这是由于 B 站 Cookie 的 SameSite 属性并没有设置为 None,内嵌在其他第三方网站时 B 站播放器无法传递 Cookie 到服务器,服务器也就拿不到用户的登录态,对于未登录的用户 B 站是不提供 1080p 播放的。

    不过在 Chrome 中我们可以通过禁用 chrome://flags/#same-site-by-default-cookies 来让”第三方 cookie“默认为 None,当我们关闭这个选项并重启浏览器之后,就可以在内嵌 iframe 中播放 1080p 的 B站视频了(前提是在 B 站已经登录过)。 深入理解 Cookie 的 SameSite 属性

    3. 加载第三方图片或脚本等

    const https = require("https");
    const fs = require("fs");
    
    https
      .createServer(
        {
          key: fs.readFileSync(__dirname + "/key.pem"),
          cert: fs.readFileSync(__dirname + "/cert.pem"),
        },
        (req, res) => {
          console.log(req.headers.host, req.url);
          if (req.url == "/") {
            if (req.headers.host === "www.foo.com") {
              res.setHeader("Content-Type", "text/html;charset=utf-8");
              res.end(`<div>这是父页面</div>
    <img src="https://www.bar.com/"></img>`);
            } else {
              res.writeHead(200, [
                ["Set-Cookie", "name=haochuan9421; Secure; SameSite=Strict"],
                ["Content-Type", "image/png"],
              ]);
              fs.createReadStream("logo.png").pipe(res);
            }
          } else {
            res.statusCode = 204;
            res.end();
          }
        }
      )
      .listen(443, "0.0.0.0");
    

    深入理解 Cookie 的 SameSite 属性 深入理解 Cookie 的 SameSite 属性 深入理解 Cookie 的 SameSite 属性 深入理解 Cookie 的 SameSite 属性

    这和引入第三方的 iframe 是一样的,只有 SameSite 属性为 None,Cookie 才能生效。

    举个应用的例子:下图是一个添加了谷歌广告的网站,可以看到谷歌广告相关的 Cookie 会把 SameSite 属性设置为 None。这样当足够多的网站引入了谷歌的广告脚本等资源时,他就可以构建出用户在各个网站的浏览轨迹以及访问偏好了,从而精准的推送广告。

    深入理解 Cookie 的 SameSite 属性

    4. 提交表单到第三方网站

    const https = require("https");
    const fs = require("fs");
    
    https
      .createServer(
        {
          key: fs.readFileSync(__dirname + "/key.pem"),
          cert: fs.readFileSync(__dirname + "/cert.pem"),
        },
        (req, res) => {
          if (req.url == "/") {
            if (req.headers.host === "www.foo.com") {
              res.setHeader("Content-Type", "text/html;charset=utf-8");
              res.end(`<form action="https://www.bar.com/" method="post" enctype="multipart/form-data">
    <input type="text" name="name" />
    <input type="number" name="age" />
    <button type="submit">提交</button>
    </form>`);
            } else {
              console.log(req.headers.host, req.url, req.method, req.headers.cookie);
              res.writeHead(200, [
                ["Set-Cookie", "name=haochuan9421; Secure; SameSite=Strict"],
              ]);
              res.end("ok");
            }
          } else {
            res.statusCode = 204;
            res.end();
          }
        }
      )
      .listen(443, "0.0.0.0");
    

    深入理解 Cookie 的 SameSite 属性 深入理解 Cookie 的 SameSite 属性 深入理解 Cookie 的 SameSite 属性

    从上面的测试中可以看出将 SameSite 设置为 None 是一种危险的行为,它会使得针对你的网站发起 CSRF (Cross-site request forgery) 攻击变得非常容易,因为从一个第三方恶意网站向你的网站发起的请求也会携带 Cookie,这使得伪造的请求会被识别为一次普通用户发起的请求。下面具体演示一下,我们假设 www.foo.com 是一个恶意网站,www.bar.com 是我们自己的网站:

    // 这是我们自己正常的网站
    const https = require("https");
    const fs = require("fs");
    
    https
      .createServer(
        {
          key: fs.readFileSync(__dirname + "/key.pem"),
          cert: fs.readFileSync(__dirname + "/cert.pem"),
        },
        (req, res) => {
          if (req.url == "/") {
            // 我们网站首页有一个转账的表单
            res.setHeader("Content-Type", "text/html;charset=utf-8");
            res.end(`<form action="/transfer" method="post">
    <input type="number" name="money" />
    <button type="submit">提交</button>
    </form>`);
          } else if (req.url == "/login") {
            // 登录后,客户端会存储用户的 Cookie 信息
            res.setHeader("Set-Cookie", "name=haochuan9421; Secure; SameSite=None");
            res.end("login success");
          } else if (req.url == "/transfer") {
            // 登录后的用户可以转账,未登录的不能转账
            res.end(req.headers.cookie ? "ok" : "fail");
          } else {
            res.statusCode = 204;
            res.end();
          }
        }
      )
      .listen(443, "0.0.0.0");
    

    用户直接访问 www.bar.com 提交表单转账,由于没有登录(没有 Cookie)会提示失败,所以用户会先进入 www.bar.com/login 登录,登录后客户端会有 Cookie,当用户回到首页再次提交转账表单时,就会转账成功,这模拟了一个简单的基于 Cookie 鉴权的网站。

    接下来我们一起来看看攻击者是如何突破 www.bar.com 的鉴权滴。当攻击者知道了你网站有转账的功能,那么他就可以诱导用户进入准备好的恶意网站,在这个恶意网站中向你的网站发起转账请求,如果进入恶意网站的用户之前登录过你的网站并且登录态没有过期,那么这次伪造的请求就会成功把用户的钱转走。下面是恶意网站的代码:

    // 这是一个要伪造请求的恶意网站
    const https = require("https");
    const fs = require("fs");
    
    https
      .createServer(
        {
          key: fs.readFileSync(__dirname + "/key.pem"),
          cert: fs.readFileSync(__dirname + "/cert.pem"),
        },
        (req, res) => {
          if (req.url == "/") {
            res.setHeader("Content-Type", "text/html;charset=utf-8");
            res.end(`<div>这是一个恶意网站</div>
    <form
    id="fake-form"
    action="https://www.bar.com/transfer"
    method="post"
    target="submit-target"
    >
        <input type="hidden" name="money" value="1000" />
    </form>
    <iframe name="submit-target"></iframe>
    <script>document.getElementById("fake-form").submit();</script>`);
          } else {
            res.statusCode = 204;
            res.end();
          }
        }
      )
      .listen(443, "0.0.0.0");
    

    深入理解 Cookie 的 SameSite 属性

    可以看到,用户被诱导进入恶意网站后,恶意网站自动像你的服务器发起了伪造的转账请求,由于你 Cookie 中的 SameSite 属性设置为 None,这就导致这次伪造的请求也会携带用户的 Cookie,单纯基于 Cookie 做的接口鉴权就被攻破了,用户的资金面临安全风险。这也是为什么最新版的浏览器都会把 SameSite 的默认值从 None 调整为 Lax 的一个重要原因。

    5. 链接跳转第三方网站

    const https = require("https");
    const fs = require("fs");
    
    https
      .createServer(
        {
          key: fs.readFileSync(__dirname + "/key.pem"),
          cert: fs.readFileSync(__dirname + "/cert.pem"),
        },
        (req, res) => {
          if (req.url == "/") {
            if (req.headers.host === "www.foo.com") {
              res.setHeader("Content-Type", "text/html;charset=utf-8");
              res.end(`<div>foo page</div>
    <a href="https://www.bar.com/">www.bar.com</a>`);
            } else {
              console.log(req.headers.host, req.url, req.headers.cookie);
              res.writeHead(200, [
                ["Set-Cookie", "name=haochuan9421; Secure; SameSite=None"],
                ["Content-Type", "text/html;charset=utf-8"],
              ]);
              res.end("bar page");
            }
          } else {
            res.statusCode = 204;
            res.end();
          }
        }
      )
      .listen(443, "0.0.0.0");
    

    深入理解 Cookie 的 SameSite 属性 深入理解 Cookie 的 SameSite 属性 深入理解 Cookie 的 SameSite 属性

    Strict 这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。

    总结

    现代浏览器针对 Cookie 的 SameSite 属性的默认值已经很合理了,作为网站所有者通常不需要手动设置这个属性,一般只有当我们的服务需要和“第三方”对接时才考虑怎么设置更合理。

    Strict 最为严格,表示完全禁止“第三方 Cookie”,只有当前网页的 URL 与请求目标一致时,才会带上 Cookie,一般用于保证系统的封闭性和安全性。

    Lax 是目前大多数现代浏览器的默认值,他在保证安全性的前提下,也可以避免一些不好的用户体验,比如从别的网站跳转过时会没有登录态。

    None 是最为宽松的一种设定,通常用于开放我们的服务给不同的第三方接入,同时又需要追踪用户的场景,比如广告,设置为 None 时需要考虑开放的安全性。


    起源地下载网 » 深入理解 Cookie 的 SameSite 属性

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元