一个 META 标签造成百度统计无法使用
缘起
本站从搭建以来至今一直使用百度统计作为流量分析工具,但后台显示从来没有访客。一直以为是本站知名度过低造成确实没有访客,但也不至于不把我自己算上吧?而且百度统计后台一直提示我“代码安装不正确”。按照官方文档进行故障排查仍没有找到原因。放弃。
直到今天我才找到原因:因为这一行代码,造成百度统计不能正常工作。
<meta name="referrer" content="no-referrer">
HTTP 请求头中 Referer 的含义和作用
什么是 Referer
Referer 是 HTTP 请求头 Request Header 中的一部分。当浏览器向 web 服务器发送请求的时候,请求头信息里有包含 Referer。比如我在 https://marksanders.cn/friends/ 里有一个 https://triplan.tech/ 的链接,那么点击该链接 ,HTTP 请求头的信息里就有:
Referer=https://marksanders.cn/friends/
由此可以看出来,它表示当前请求的来源。被请求方可以读取出 Referer 从而知道该请求的来源。
拼写问题
Referer 的正确英语拼法是 referrer。由于早期 HTTP 规范的拼写错误,为了保持向后兼容就将错就错了。其它网络技术的规范企图修正此问题,使用正确拼法,所以目前拼法不统一。还有它第一个字母是大写。
Referer 的作用
防盗链
图片服务器在每次请求时判断 Referer 是否在白名单中,如果是则继续访问,不是则拦截当前请求(通常返回错误码 403)。这样我网站上的图片资源仅允许在本网站使用,若被引用至其它网站中则图片不能正常显示。图为在其它域下无法正常显示豆瓣网的图片。
防止恶意请求
某网站有一个内部接口用于获取一些数据,现在只允许这个接口在本站调用。为防止被恶意请求,可以让该接口判断 Referer 的值是否为本站,极大增加了接口的安全性、私密性。
Referer 的不可靠性
Referer 不但可用于上述防盗链、防止恶意请求上,还可以根据需求用于其他用途。当然,Referer 只是一个参考,并不能完全依赖于它。
比方说,如下 python 代码则实现了如何伪造 HTTP 请求头中的 Referer 值:
#!/usr/bin/python
import urllib2
import sys
url = 'https://triplan.tech/'
req = urllib2.Request(url)
req.add_header('Referer','//marksanders.cn/') # 伪造 Referer
r = urllib2.urlopen(req)
html = r.read()
receive_header = r.info()
html = html.decode('utf-8').encode(sys.getfilesystemencoding())
print html
百度统计与 Referer
百度统计正是通过获取 Referer 值从而知道当前请求的页面。
referrer 策略与 meta 标签
referrer 的取值
1、no-referrer:所有请求不发送 referrer。
2、no-referrer-when-downgrade(默认值):当请求安全级别下降时不发送 referrer。目前,只有一种情况会发生安全级别下降,即从 HTTPS 到 HTTP。HTTPS 到 HTTP 的资源引用和链接跳转都不会发送 referrer。
3、same-origin:对于同源的链接和引用,会发送referrer,其他的不会。
4、origin:在任何情况下仅发送源信息作为引用地址。源信息包括访问协议和域名。
5、strict-origin:在安全级别下降时不发送 referrer;而在同等安全级别的情况下仅发送源信息。注意:这个是新加的标准,有些浏览器可能还不支持。
6、origin-when-cross-origin:同源的链接和引用,会发送完全的 referrer 信息;但非同源链接和引用时,只发送源信息。
7、strict-origin-when-cross-origin:同源的链接和引用,会发送 referrer。安全级别下降时不发送 referrer。其它情况下发送源信息。注意:这个是新加的标准,有些浏览器可能还不支持。
8、unsafe-url:无论是否发生协议降级,无论是本站链接还是站外链接,统统都发送 Referrer 信息。正如其名,这是最宽松而最不安全的策略。
Referrer Policy 的设置方法
CSP(Content Security Policy
CSP(Content Security Policy),是一个跟页面内容安全有关的规范。在 HTTP 中通过响应头中的 Content-Security-Policy 字段来告诉浏览器当前页面要使用何种 CSP 策略。
Content-Security-Policy: referrer no-referrer|no-referrer-when-downgrade|origin|origin-when-cross-origin|unsafe-url;
标签
<meta name="referrer" content="no-referrer|no-referrer-when-downgrade|origin|origin-when-crossorigin|unsafe-url">
如果 content 属性不是合法的取值,浏览器会自动选择 no-referrer 这种最严格的策略。
通过 a、area、link 元素的 referrer 属性
<a href="http://marksanders.cn/" referrer="no-referrer|origin|unsafe-url"></a>
解决方法
本站主页的“观影”和“书单”模块需要加载豆瓣网的少许电影海报图和图书封面图。由于豆瓣网图片服务器开启了防盗链,因此在主页设置了 no-referrer 标签。这样豆瓣图片服务器无法通过 Referer 判断来源,图片可正常显示,但这造成了百度统计无法正常工作。
如何在正常显示豆瓣网图片的同时,正常使用百度统计呢?
IMG 标签的 Referrer-Policy 属性
给引用外部图片资源的 <img>
标签增加 referrerpolicy 属性并将其值设为 no-referre
。这样浏览器仅在该图片资源的请求时屏蔽 Referer 的值,而不影响网页的其他部分。去 MDN 了解更多有关 Referrer-Policy 属性
<img referrerpolicy="no-referrer" src="https://marksanders.cn/static/img/logo@2x.png" />
但 referrerpolicy 属性的兼容性并不高,在一些浏览器(包括 Safari)至今仍不兼容该属性。
使用 iframe 放置图片
另一种方法是将设置了防盗链的图片放在 iframe 框架中而不是直接放置于网页上。本站采用的便是这种方法。通过设置 iframe 标签的宽度和高度,以及 scrolling、frameborder 和 allowtransparency 属性可让用户无法轻易察觉该图片是嵌在 iframe 框架中,除非查看页面源代码。当然 iframe 中子页面的 padding 和 margin 也要设为 0。
当所放置图片的大小未知时,可能需要根据图片的实际大小动态调节 iframe 框架的长度和宽度。可参考如下 HTML 代码:
<iframe
style="border: none"
scrolling="no"
frameborder="no
allowtransparency="true"
id="*********" <!-- 这里需要为 iframe 标签设置 ID -->
src="javascript:'
<!doctype html>
<html>
<head>
<meta charset=\'utf-8\'>
<style>*{margin: 0; padding: 0; border: 0}</style>
<script>
function resizeWindow() {
var elems = document.getElementsByTagName(\\\'*\\\'),
width = 0,
height = 0,
first = document.body.firstChild,
elem;
if (first.offsetHeight && first.offsetWidth) {
width = first.offsetWidth;
height = first.offsetHeight;
} else {
for (var i in elems) {
elem = elems[i];
if (!elem.offsetWidth) {
continue;
}
width = Math.max(elem.offsetWidth, width);
height = Math.max(elem.offsetHeight, height);
}
}
var ifr = parent.document.getElementById(\'*********\'); <!-- 这里是 iframe 标签的 ID -->
ifr.height = height;
ifr.width = width;
}
</script>
</head>
<body onload="resizeWindow()">
<img src="***************" /> <!-- 这里是需要显示的图片 -->
</body>
</html>
'"></iframe>
iframe 中的子页面在加载完 <img>
标签中的图片后,调用 resizeWindow()
方法,根据图片的实际大小调整父页面中 iframe 的长度和宽度。
本站主页运用此种方法,效果如下:
问题解决。
雁过留痕,风过留声