【问题标题】:Check if third-party cookies are enabled检查是否启用了第三方 cookie
【发布时间】:2011-04-02 19:43:57
【问题描述】:

我有一个应用程序需要检查客户端浏览器是否启用了第三方 cookie。有谁知道如何在 JavaScript 中做到这一点?

【问题讨论】:

    标签: javascript cookies


    【解决方案1】:

    理论上,您只需在某个地方调用一个页面,该页面会设置第三方 cookie,然后检查该 cookie 的存在。但是,标准浏览器安全性不允许来自域 A 的脚本对域 B、C 等上设置的 cookie 执行任何操作......例如您无法访问“外国”cookie。

    如果您有一些特定用途,例如检查广告是否被阻止(这也会阻止第 3 方跟踪 cookie),您可以检查广告服务器的内容是否在页面的 DOM 中,但您不能看看 cookie 有没有。

    【讨论】:

    • 确实我试过了,但它不起作用。我们实际上是在白标域上运行一个白标应用程序,但我们想放置我们自己的 cookie。对 cookie 的默认检查是不够的,因为我们的 cookie 被视为第三方。我们现在将尝试不同的方法。
    【解决方案2】:

    技术背景

    第三方通过 HTTP(不是 JavaScript)设置和读取 cookie。

    所以我们需要向外部域发出两个请求来测试是否启用了第三方 cookie:

    1. 第三方设置 cookie 的地方
    2. 第二个,响应不同,具体取决于浏览器是否在第二个请求中将 cookie 发送回同一第三方。

    由于 DOM 安全模型,我们不能使用 XMLHTTPRequest (Ajax)。

    显然,您不能同时加载两个脚本,或者第二个请求可能在第一个请求的响应返回之前发出,并且不会设置测试 cookie。

    代码示例

    给定:

    1. .html 文件位于一个域中,并且

    2. .js.php 文件位于第二个域中,我们有:

    HTML 测试页面

    另存为third-party-cookies.html

    <!DOCTYPE html>
    <html>
    <head id="head">
      <meta charset=utf-8 />
      <title>Test if Third-Party Cookies are Enabled</title>
    <style type="text/css">
    body {
      color: black;
      background: white none;
    }
    .error {
      color: #c00;
    }
    .loading {
      color: #888;
    }
    .hidden {
      display: none;
    }
    </style>
    <script type="text/javascript">
    window._3rd_party_test_step1_loaded = function(){
      // At this point, a third-party domain has now attempted to set a cookie (if all went to plan!)
      var step2Url = 'http://third-party.example.com/step2.js.php',
        resultsEl = document.getElementById('3rd_party_cookie_test_results'),
        step2El = document.createElement('script');
    
      // Update loading / results message
      resultsEl.innerHTML = 'Stage one complete, loading stage 2&hellip;';
      // And load the second part of the test (reading the cookie)
      step2El.setAttribute('src', step2Url);
      resultsEl.appendChild(step2El);
    }
    window._3rd_party_test_step2_loaded = function(cookieSuccess){
      var resultsEl = document.getElementById('3rd_party_cookie_test_results'),
        errorEl = document.getElementById('3rd_party_cookie_test_error');
      // Show message
      resultsEl.innerHTML = (cookieSuccess ? 'Third party cookies are <b>functioning</b> in your browser.' : 'Third party cookies appear to be <b>disabled</b>.');
    
      // Done, so remove loading class
      resultsEl.className = resultsEl.className.replace(/\bloading\b/,' ');
      // And remove error message
      errorEl.className = 'hidden';
    }
    </script>
    </head>
    <body id="thebody">
    
      <h1>Test if Third-Party Cookies are Enabled</h1>
    
      <p id="3rd_party_cookie_test_results" class='loading'>Testing&hellip;</p>
      <p id="3rd_party_cookie_test_error" class="error hidden">(If this message persists, the test could not be completed; we could not reach the third-party to test, or another error occurred.)</p>
    
      <script type="text/javascript">
      window.setTimeout(function(){
        var errorEl = document.getElementById('3rd_party_cookie_test_error');
        if(errorEl.className.match(/\berror\b/)) {
          // Show error message
          errorEl.className = errorEl.className.replace(/\bhidden\b/,' ');
        } else {
        }
      }, 7*1000); // 7 sec timeout
      </script>
      <script type="text/javascript" src="http://third-party.example.com/step1.js.php"></script>
    </body>
    </html>
    

    第一个第三方 JavaScript 文件

    另存为step1.js.php

    这是用 PHP 编写的,因此我们可以在文件加载时设置 cookie。 (当然,它可以用任何语言编写,甚至可以在服务器配置文件中完成。)

    <?php
      header('Content-Type: application/javascript; charset=UTF-8');
      // Set test cookie
      setcookie('third_party_c_t', 'hey there!', time() + 3600*24*2);
    ?>
    window._3rd_party_test_step1_loaded();
    

    第二个第三方JavaScript文件

    另存为step2.js.php

    这是用 PHP 编写的,因此我们可以在响应之前读取服务器端的 cookie。我们还会清除 cookie,以便可以重复测试(如果您想弄乱浏览器设置并重试)。

    <?php
      header('Content-Type: application/javascript; charset=UTF-8');
      // Read test cookie, if there
      $cookie_received = (isset($_COOKIE['third_party_c_t']) && $_COOKIE['third_party_c_t'] == 'hey there!');
      // And clear it so the user can test it again 
      setcookie('third_party_c_t', '', time() - 3600*24);
    ?>
    window._3rd_party_test_step2_loaded(<?php echo ($cookie_received ? 'true' : 'false'); ?>);
    

    最后一行使用三元运算符输出文字 Javascript truefalse,具体取决于测试 cookie 是否存在。

    Test it here.

    您可以在https://alanhogan.github.io/web-experiments/3rd/third-party-cookies.html 享受测试乐趣。

    (最后一点 - 请勿在未经他人许可的情况下使用他人的服务器测试第三方 Cookie。它可能会自发中断或注入恶意软件。这很粗鲁。)

    【讨论】:

    • 这还能用吗?我检查了您在 chrome 和 safari 上的链接(两次都在 mac 上),但消息仍然存在。 Inspector 显示 chrome 阻止对 img 的请求,因为它不安全(并且主域是 https)?
    • 我正在从上面删除一些过时的 cmets,但我在 @MaheshKulkarni 的有用说明的几天内修复了我的示例测试。由于我以前的网络主机在处理我的 SSL 续订时出现了史诗般的失败,我会遇到问题。如果将来测试中断,请告诉我:alanhogan.com/contact?reason=3rd%20party%20cookie%20test
    • 这似乎不适用于 iOS Safari。有什么想法吗?
    • @biko 您可能会遇到“智能跟踪保护”,请参阅webkit.org/blog/7675/intelligent-tracking-prevention
    • @AlanH,你知道这个解决方案目前是否仍在 Chrome 中运行(通常是基于 Chromium 的浏览器)?它适用于 Firefox,但不适用于 Chrome 或 Brave。即使在设置中允许所有 cookie,第三方 cookie 也始终显示为禁用。如果有这方面知识的人可以加入,我将不胜感激。
    【解决方案3】:

    Alan H's solution 很棒,但您不必使用 PHP 或任何其他服务器端编程语言。

    至少如果你使用 nginx。 :)

    这是 Alan 解决方案的纯* nginx 服务器端配置:

    server {
    
      listen 80;
      server_name third-party.example.com
      
      # Don't allow user's browser to cache these replies
      expires -1;
      add_header Cache-Control "private";
      etag off;
      
      # The first third-party "JavaScript file" - served by nginx
      location = /step1.js.php {
        add_header Content-Type 'application/javascript; charset=UTF-8';
        
        add_header Set-Cookie "third_party_c_t=hey there!;Max-Age=172800";
        
        return 200 'window._3rd_party_test_step1_loaded();';
      }
      
      # The second third-party "JavaScript file" - served by nginx
      location = /step2.js.php {
        add_header Content-Type 'application/javascript; charset=UTF-8';
        
        set $test 'false';
        if ($cookie_third_party_c_t = 'hey there!') {
          set $test 'true';
          # clear the cookie
          add_header Set-Cookie "third_party_c_t=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
        }
        
        return 200 'window._3rd_party_test_step2_loaded($test);';
      }
    
    }
    

    旁注:

    • 是的,是的,我知道IfIsEvil
    • 为了与 Alan 的“HTML 测试页”(third-party-cookies.html) 完全兼容,我保留了以“.php”结尾的名称,
    • 您还可以将两个位置的通用“设置 Content-Type 标头”行移动到配置的 server 部分(范围) - 我保留它是为了使它更像 Alan H 的解决方案。李>

    【讨论】:

      【解决方案4】:

      这是一个纯 JS 解决方案,不需要任何服务器端代码,因此它可以从静态 CDN 工作:https://github.com/mindmup/3rdpartycookiecheck - 第一个脚本在代码中设置 cookie,然后重定向到将发布消息的第二个脚本到父窗口。

      您可以使用https://jsfiddle.net/tugawg8y/ 试用实时版本。
      请注意,此演示似乎不再有效。可能window.postMessage 呼叫被阻止了。

      客户端 HTML:

      third party cookies are <span id="result"/>
      <iframe src="https://mindmup.github.io/3rdpartycookiecheck/start.html"
          style="display:none" />
      

      客户端 JS:

       var receiveMessage = function (evt) {
         if (evt.data === 'MM:3PCunsupported') {
           document.getElementById('result').innerHTML = 'not supported';
         } else if (evt.data === 'MM:3PCsupported') {
           document.getElementById('result').innerHTML = 'supported';
         }
       };
       window.addEventListener("message", receiveMessage, false);
      

      当然,这要求客户端运行 JavaScript,与基于服务器的解决方案相比,这是一个缺点;另一方面,它更简单,你问的是 JS 解决方案。

      【讨论】:

      • 万一start.html文件消失,文件内容为:&lt;body&gt; &lt;script&gt; if (window.parent) { if (/thirdparty=yes/.test(document.cookie)) { window.parent.postMessage('MM:3PCsupported', '*'); } else { window.parent.postMessage('MM:3PCunsupported', '*'); } } &lt;/script&gt; &lt;/body&gt;
      • 这是迄今为止最好的答案
      • 我确认此设置有效。对我来说,它不是来自 Chrome 中的 the Fidlle link,但在 在本地 构建它之后,它确实起作用了。我唯一需要更改的是删除 window.onload 处理程序,显然这是 Fiddle 放置用户代码的地方。
      • @TheStoryCoder 发布的内容属于文件complete.htmlstart.html 文件的内容如下所示:&lt;script&gt; document.cookie="thirdparty=yes"; document.location="complete.html"; &lt;/script&gt; 这会设置一个 cookie 并触发重定向到 complete.html 文件。
      • 似乎被最新的 Chrome 更新所破坏,其中同站点限制非常严格
      【解决方案5】:

      将 URL 列入白名单的第三方 Cookie 检测

      Alan HGojko Adzic 对于大多数用例来说已经足够了,但如果您希望您的用户仅将第三方 cookie 列入某些域的白名单,这些解决方案将不起作用。

      我正在展示Gojko Adzicanswer 的略微修改版本

      为此,我们需要两个域:

      • 域 1,这是您的用户登陆的页面,它最初设置为 tpc=pending,然后重定向到域 2
      • 域 2 在 iF​​rame 中注入域 1 的 url,尝试设置 cookie tpc=true 并重定向回域 1
      • 现在,域 1 读取 cookie tpc 并检查其 true 是否为真,如果它仍在 pending 中,则允许第三方 cookie 阻止第三方 cookie。李>

      现在,您可以要求您的用户将 Domain 1 列入白名单(允许第三方 cookie),如果用户将您的域。


      这是在 Chrome 74、75、76 和 Edge 78 中测试的

      不幸的是,Mozilla 没有像 Chrome 那样提供网址白名单,而 Safari 有自己的检测第三方 Cookie (ITP) 的机制。

      附:有时间会在我的github上传这个demo。

      【讨论】:

        【解决方案6】:

        我的解决方案通过从设置 cookie 的外部域加载

        HTML:

        <script>
        function myCallback(is_enabled) {
            if (is_enabled===1) {//third party cookies are enabled
            }
        }
        </script>
        <script src="https://third-party-domain/third-party-cookies.php?callback=myCallback"></script>
        

        如果你喜欢异步运行,你可以使用 async 和 defer 属性。

        这也适用于 jQuery:

        <script>
        $.ajax({
            url: 'https://third-party-domain/third-party-cookies.php',
            dataType: 'jsonp',
        }).done(function(is_enabled) {
            if (is_enabled===1) {//third party cookies are enabled
            }
        })
        </script>
        

        这里是第三方cookies.php 代码。这必须托管在不同的域上。服务器必须支持PHP:

        <?php
        
        header('Cache-Control: no-store');
        header('Content-Type: text/javascript');
        
        if ($_GET['callback']=='') {
            echo 'alert("Error: A callback function must be specified.")';
        }
        elseif (!isset($_GET['cookieName'])) {// Cookie not set yet
            $cookieName = strtr((string)$_SERVER['UNIQUE_ID'], '@', '_');
            while (isset($_COOKIE[$cookieName]) || $cookieName=='') {
                $cookieName = dechex(mt_rand());// Get random cookie name
            }
            setcookie($cookieName, '3rd-party', 0, '/');
            header('Location: '.$_SERVER['REQUEST_URI'].'&cookieName='.$cookieName);
        }
        elseif ($_COOKIE[$_GET['cookieName']]=='3rd-party') {// Third party cookies are enabled.
            setcookie($_GET['cookieName'], '', -1, '/'); // delete cookie
            echo $_GET['callback'].'(1)';
        }
        else {// Third party cookies are not enabled.
            echo $_GET['callback'].'(0)';
        }
        

        【讨论】:

          【解决方案7】:

          使用GregAlan's 解决方案检查第三方cookie 是否启用的步骤:

          我修改了文件,因为我唯一需要的是检查是否启用了第三方 cookie,这取决于我是否会做一些事情,比如将它们路由到一个告诉用户启用第三方 cookie 的页面。

          1) 编辑您的 nginx 站点配置

          (在 debian 9 中位于 /etc/nginx/sites-enabled/default)

          $ sudo nano /etc/nginx/sites-enabled/default
          

          您需要在您的域上安装 TLS/SSL,否则您将无法设置 cookies from a third party 域,并且您会收到一条错误消息:

          由于 cookie 的 SameSite 属性未设置或无效,它默认为 SameSite=Lax,这会阻止 cookie 在跨站点请求中发送。这种行为可以保护用户数据不被意外泄露给第三方和跨站点请求伪造。通过更新 cookie 的属性来解决此问题:如果 cookie 应在跨站点请求中发送,请指定 SameSite=None 和 Secure。这允许第三方使用。如果不应在跨站点请求中发送 cookie,请指定 SameSite=Strict 或 SameSite=Lax。

          • 在“Access-Control-Allow-Origin”中指定您允许的域,不建议将其保留为“*”(公共访问)。

          • 您可以指定'Access-Control-Allow-Methods "GET";'是唯一使用的方法。

          (我将这些标题设置为“*”(公共)只是为了确保它可以正常工作,之后,您可以对其进行编辑。)

          您可以更改端点的名称(step1.js.php 和 step2.js.php),但您需要在 js 脚本中进行更改。 (除非您更改它,否则会向 your-custom-domain.com/step1.js.php o your-custom-domain.com/step2.js.php 发出请求。扩展名并不重要,您可以将其更改为“ step1”和“step2”或任何你喜欢的)

          # Nginx config start
          server {
                  server_name your-custom-domain.com;
                  # Check if third party cookies are allowed
                  # The first third-party "JavaScript file" - served by nginx
                  location = /step1.js.php {
                          expires -1;
                          add_header Cache-Control "private";
                          etag off;
                          add_header Access-Control-Allow-Origin "*";
                          add_header Access-Control-Allow-Methods "*";
                          add_header Content-Type 'application/javascript; charset=UTF-8';
                          add_header Set-Cookie "third_party_c_t=hey there!;Max-Age=172000; Secure; SameSite=none";
                          return 200 'window._3rd_party_test_step1_loaded();';
                  }
                  # The second third-party "JavaScript file" - served by nginx
                  location = /step2.js.php {
                          add_header Access-Control-Allow-Origin "*";
                          add_header Access-Control-Allow-Methods "*";
                          add_header Content-Type 'application/javascript; charset=UTF-8';
                          set $test 'false';
                          if ($cookie_third_party_c_t = 'hey there!') {
                                  set $test 'true';
                                  # clear the cookie
                                  add_header Set-Cookie "third_party_c_t=;expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; SameSite=none";
                          }
                          return 200 'window._3rd_party_test_step2_loaded($test);';
                  }
          
              # managed by Certbot, here is where your certificates goes.
              listen [::]:443 ssl ipv6only=on; 
              listen 443 ssl; # managed by Certbot
              ssl_certificate /etc/letsencrypt/live/www.couchdb.me/fullchain.pem; # managed by Certbot
              ssl_certificate_key /etc/letsencrypt/live/www.couchdb.me/privkey.pem; # managed by Certbot
              include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
              ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
          }
          

          保存它(ctrl+x - 如果要保存,请按字母 Y - Enter 确认)并重新启动/重新加载您的 nginx:

          $ sudo systemctl restart nginx.service
          

          2) 在您的着陆页中(不同于 your-custom-domain.com)

          您可以更改方法的名称(_3rd_party_test_step1_loaded 和 _3rd_party_test_step2_loaded),但您还需要在 nginx 的配置中更改它。 (确保名称是唯一的)

          2.1) 将此脚本添加到 html 的标题中(必须先加载):

          <script type="text/javascript">
          window._3rd_party_test_step1_loaded = function () {
                // At this point, a third-party domain has now attempted to set a cookie (if all went to plan!)
                var step2El = document.createElement("script");
                const url = your-custom-domain.com + "/step2.js.php";
                step2El.setAttribute("src", url);
                document.head.appendChild(step2El);
          };
          window._3rd_party_test_step2_loaded = function (cookieSuccess) {
                // If true, the third-party domain cookies are enabled
                // If false, the third-party domain cookies are disable
                cookieSuccess ? callMethodIfTrue() : callMethodIfFalse();
          };
          </script>
          

          2.2) 在正文 html 的末尾添加脚本:

          <script type="text/javascript" src="https://your-custom-domain/step1.js.php"></script>
          

          或者如果您正在使用 js 文件(请记住,您需要将文件添加到您的 html 登录页面上,例如: &lt;script type="text/javascript" src="path/to/your/js/file"&gt;&lt;/script&gt;

          JS 文件:

          window._3rd_party_test_step1_loaded = function () {
                // At this point, a third-party domain has now attempted to set a cookie (if all went to plan!)
                var step2El = document.createElement("script");
                const url = that.$url + "/step2.js.php";
                step2El.setAttribute("src", url);
                document.head.appendChild(step2El);
          };
          window._3rd_party_test_step2_loaded = function (cookieSuccess) {
                // If true, the third-party domain cookies are enabled
                // If false, the third-party domain cookies are disable
                cookieSuccess ? callMethodIfTrue() : callMethodIfFalse();
          };
          
          window.onload = function () {
                const url = "your-custom-domain.com" + "/step1.js.php";
                var step1El = document.createElement("script");
                step1El.setAttribute("src", url);
                document.body.appendChild(step1El);
          };
          

          【讨论】:

            【解决方案8】:

            这是为了检查第三方 cookie 是否已被用户阻止。

            我只是尝试访问浏览器的本地存储。如果用户启用了第三方 cookie,那么它应该是可用的,否则会抛出错误。

            try {
                    if(window.localStorage) {
                        //cookies enabled
                    }
                } catch (err) {
                    //cookies disabled
                }
            

            【讨论】:

            • 我在 Chrome 中试过这个,它没有抛出异常,即使启用了 3rd Party cookie 阻止。
            • 我使用的是 Chrome97,它对我来说非常好用。你的意思是说即使启用了第三方cookie拦截,window.localStorage也是可以访问的?
            • 是的,这正是我要说的。
            • @JeffReddy 你找到答案了吗?
            猜你喜欢
            • 2021-05-21
            • 2021-01-10
            • 2011-10-03
            • 2013-03-10
            • 2012-02-26
            • 1970-01-01
            • 1970-01-01
            • 2011-02-03
            • 2017-02-15
            相关资源
            最近更新 更多