Referrer-Policy 是什么?默认会把哪些信息发给其他网站?
Referer 请求头告诉目标服务器请求是从哪个页面来的,Referrer-Policy 决定这个头里带多少信息。浏览器默认用 strict-origin-when-cross-origin:同源发完整地址,跨源只发源,从 HTTPS 降到 HTTP 不发。
简答
用户在
https://shop.example/order?id=123&token=abc页面点了一个外部链接,浏览器会在请求里带上Referer头,告诉对方“我是从哪个页面来的”。Referrer-Policy就是用来控制这个头里带多少信息的。之所以要控制,是因为地址里常常有隐私数据,比如订单号、搜索词、甚至令牌。完整地址发给第三方,就等于把这些数据泄露了出去。
现在浏览器的默认策略是
strict-origin-when-cross-origin:同源请求带完整地址,跨源请求只带源,比如https://shop.example/,从 HTTPS 页面请求 HTTP 地址时不带。需要更严格可以设成no-referrer或same-origin。设置方式有响应头、<meta name="referrer">、元素上的referrerpolicy属性,链接还能用rel="noreferrer"。要注意,
unsafe-url会把完整地址发给任何目标,包括 HTTP 网站,MDN 明确提醒要慎用。另外不要拿Referer做安全校验,它可能被策略去掉。
Referer 里会带什么
Referer 头的名字本身就是 referrer 的拼写错误,规范沿用了下来,而控制它的 Referrer-Policy 拼对了。按 MDN 的说明,Referer 最多包含源、路径和查询字符串,不会包含 #section 这样的片段,也不会包含 username:password 这类用户信息。
所以真正可能泄露的是路径和查询字符串。比如重置密码页面的地址带着令牌,页面上又引用了第三方统计脚本,要是 Referer 发了完整地址,第三方服务器的日志里就会记下这个令牌。
各个取值是什么意思
规范定义了八个取值,区别在于“同源还是跨源”和“安全等级有没有降低”。安全等级降低指从 HTTPS 页面请求 HTTP 地址。
no-referrer:任何请求都不发Referer。no-referrer-when-downgrade:安全等级不降低时发完整地址,降到 HTTP 时不发。origin:任何请求都只发源,比如https://example.com/。origin-when-cross-origin:同源发完整地址,跨源和降级时只发源。same-origin:同源发完整地址,跨源不发。strict-origin:安全等级不变时只发源,降级时不发。strict-origin-when-cross-origin:同源发完整地址;跨源且安全等级不变时只发源;降级时不发。unsafe-url:任何请求都发完整地址,不管安全不安全。
默认值是 strict-origin-when-cross-origin。没有指定策略,或者指定的值无效时,都用它。MDN 提到,这是 2020 年 11 月规范修订后的默认值,以前的默认值是 no-referrer-when-downgrade,那时跨源请求也会带完整地址。
用 MDN 的例子看一下默认策略:
| 当前页面 | 请求目标 | 发出的 Referer |
|---|---|---|
https://example.com/page | https://example.com/otherpage | https://example.com/page |
https://example.com/page | https://mozilla.org | https://example.com/ |
https://example.com/page | http://example.com | 不发 |
第三行可能让人意外,明明是同一个域名,为什么不发?因为 http://example.com 和 https://example.com 协议不同,不是同源;而且从 HTTPS 到 HTTP 是降级,默认策略在降级时不发。
另外,W3C 规范规定,完整地址序列化后超过 4096 个字符时,浏览器会改为只发源。
怎样设置
最常用的是在 HTML 响应上加响应头,整个文档都按它来:
Referrer-Policy: strict-origin-when-cross-origin
响应头还能写多个值作为兜底,浏览器用最后一个它支持的值。下面这行在不支持 strict-origin-when-cross-origin 的旧浏览器里会退到 no-referrer:
Referrer-Policy: no-referrer, strict-origin-when-cross-origin
没法改响应头时,可以在页面里用 <meta> 设置整个文档的策略,也可以在单个元素上用 referrerpolicy 属性覆盖:
<meta name="referrer" content="no-referrer" />
<a href="https://partner.example" referrerpolicy="origin">合作方</a>
<img src="https://cdn.example/a.png" referrerpolicy="no-referrer" alt="" />
<a href="https://partner.example" rel="noreferrer">合作方</a>
referrerpolicy 属性可以用在 <a>、<area>、<img>、<iframe>、<script>、<link> 上,只能写一个值,多个值兜底只有响应头支持。rel="noreferrer" 可以用在 a、area、link 上。有个容易写错的地方:rel 里的 noreferrer 没有连字符,<meta> 里的 no-referrer 有连字符。
用 fetch 发请求时,可以通过 referrerPolicy 选项单独指定,取值和语义跟响应头一样:
fetch('https://api.partner.example/track', { referrerPolicy: 'no-referrer' });
CSS 里引用的资源也遵循策略。MDN 说明,外部样式表里的资源默认用 strict-origin-when-cross-origin,样式表自己的响应带了 Referrer-Policy 头就按那个;<style> 元素和 style 属性里的资源用所在文档的策略。
常见追问
能不能用 Referer 防 CSRF?
不能把它当唯一手段。
Referer会被no-referrer等策略去掉,服务器看到没有这个头时无法判断请求来源。防 CSRF 应该用 SameSite Cookie、CSRF 令牌这类机制,Referer或Origin只能作为辅助检查。
设置成 no-referrer 有什么副作用?
同源请求也不发
Referer了,依赖它的功能会受影响,比如统计站内的来源页面、某些按来源做防盗链的图片服务。如果只是想不泄露给第三方,same-origin更合适,它在站内仍然发完整地址,跨源才不发。
为什么默认值从 no-referrer-when-downgrade 改了?
旧默认值只在降级到 HTTP 时不发,HTTPS 网站之间的跨源请求都会带完整地址,路径和查询字符串里的数据很容易泄露给第三方。新默认值跨源只发源,在保留“从哪个网站来”这个信息的同时,把路径和参数藏了起来。