【发布时间】:2011-04-02 19:43:57
【问题描述】:
我有一个应用程序需要检查客户端浏览器是否启用了第三方 cookie。有谁知道如何在 JavaScript 中做到这一点?
【问题讨论】:
标签: javascript cookies
我有一个应用程序需要检查客户端浏览器是否启用了第三方 cookie。有谁知道如何在 JavaScript 中做到这一点?
【问题讨论】:
标签: javascript cookies
理论上,您只需在某个地方调用一个页面,该页面会设置第三方 cookie,然后检查该 cookie 的存在。但是,标准浏览器安全性不允许来自域 A 的脚本对域 B、C 等上设置的 cookie 执行任何操作......例如您无法访问“外国”cookie。
如果您有一些特定用途,例如检查广告是否被阻止(这也会阻止第 3 方跟踪 cookie),您可以检查广告服务器的内容是否在页面的 DOM 中,但您不能看看 cookie 有没有。
【讨论】:
第三方通过 HTTP(不是 JavaScript)设置和读取 cookie。
所以我们需要向外部域发出两个请求来测试是否启用了第三方 cookie:
由于 DOM 安全模型,我们不能使用 XMLHTTPRequest (Ajax)。
显然,您不能同时加载两个脚本,或者第二个请求可能在第一个请求的响应返回之前发出,并且不会设置测试 cookie。
给定:
.html 文件位于一个域中,并且
.js.php 文件位于第二个域中,我们有:
另存为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…';
// 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…</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>
另存为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();
另存为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 true 或 false,具体取决于测试 cookie 是否存在。
您可以在https://alanhogan.github.io/web-experiments/3rd/third-party-cookies.html 享受测试乐趣。
(最后一点 - 请勿在未经他人许可的情况下使用他人的服务器测试第三方 Cookie。它可能会自发中断或注入恶意软件。这很粗鲁。)
【讨论】:
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);';
}
}
旁注:
third-party-cookies.html) 完全兼容,我保留了以“.php”结尾的名称,server 部分(范围) - 我保留它是为了使它更像 Alan H 的解决方案。李>
【讨论】:
这是一个纯 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文件消失,文件内容为:<body> <script> if (window.parent) { if (/thirdparty=yes/.test(document.cookie)) { window.parent.postMessage('MM:3PCsupported', '*'); } else { window.parent.postMessage('MM:3PCunsupported', '*'); } } </script> </body>
window.onload 处理程序,显然这是 Fiddle 放置用户代码的地方。
complete.html。 start.html 文件的内容如下所示:<script> document.cookie="thirdparty=yes"; document.location="complete.html"; </script> 这会设置一个 cookie 并触发重定向到 complete.html 文件。
将 URL 列入白名单的第三方 Cookie 检测
Alan H 和 Gojko Adzic 对于大多数用例来说已经足够了,但如果您希望您的用户仅将第三方 cookie 列入某些域的白名单,这些解决方案将不起作用。
我正在展示Gojko Adzic 的answer 的略微修改版本
为此,我们需要两个域:
tpc=pending,然后重定向到域 2tpc=true 并重定向回域 1tpc 并检查其 true 是否为真,如果它仍在 pending 中,则允许第三方 cookie 阻止第三方 cookie。李>
现在,您可以要求您的用户将 Domain 1 列入白名单(允许第三方 cookie)仅,如果用户将您的域。
这是在 Chrome 74、75、76 和 Edge 78 中测试的
不幸的是,Mozilla 没有像 Chrome 那样提供网址白名单,而 Safari 有自己的检测第三方 Cookie (ITP) 的机制。
附:有时间会在我的github上传这个demo。
【讨论】:
我的解决方案通过从设置 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)';
}
【讨论】:
使用Greg 和Alan's 解决方案检查第三方cookie 是否启用的步骤:
我修改了文件,因为我唯一需要的是检查是否启用了第三方 cookie,这取决于我是否会做一些事情,比如将它们路由到一个告诉用户启用第三方 cookie 的页面。
(在 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
您可以更改方法的名称(_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 登录页面上,例如:
<script type="text/javascript" src="path/to/your/js/file"></script>
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);
};
【讨论】:
这是为了检查第三方 cookie 是否已被用户阻止。
我只是尝试访问浏览器的本地存储。如果用户启用了第三方 cookie,那么它应该是可用的,否则会抛出错误。
try {
if(window.localStorage) {
//cookies enabled
}
} catch (err) {
//cookies disabled
}
【讨论】: