📘 第六章 HTTP首部
📄 一句话概括

Referer 说「我是从哪个页面点过来的」。字段名少了一个 r——1996 年写错,只能一直错下去。 它有用,但会泄露 URL 里的查询串。

📝 本节和前面几节的关系

上一节是技术性的分段,这一节是个既实用又有隐私问题的字段。

一、那个拼写错误

正确英文是 referrer,两个 r。但 1996 年的规范里写成了 Referer,一个 r。 为了向后兼容,这个错误被永久保留。 有意思的是响应端的策略字段拼对了:Referrer-Policy。 所以同一个概念,请求头里是错的,响应头里是对的,两个拼法并存。 写代码时这是个高频 typo 来源:在服务端读 Referer 写成 Referrer,永远读不到值, 而且不会有任何报错——只是取不到。

二、它到底有用在哪

来源统计:知道流量从哪个页面来,是分析的基础。 防盗链:图片只允许从自己的域名引用,别家引用就回 403 或换成占位图。这是它最经典的用途。 CSRF 防护的辅助判断:检查请求是不是从自己的页面发起的。 注意只能当辅助——因为 Referer 可以被伪造,也可以被省略。 Demo 里五组请求覆盖了同站、带查询串、外站、跨协议、直接访问五种情况。 最后一组是空的,说明用户直接在地址栏输入了地址。

三、它会泄密,所以被削了

Demo 里那个 https://shop.example.com/list?uid=42&token=abc 如果原样发到外站,等于把令牌送人了。 所以现代浏览器默认用 strict-origin-when-cross-origin 策略:同站时发完整 URL, 跨站时只发「协议加域名」,路径和查询串全部砍掉。 服务端也可以主动声明策略,就是在响应里加 Referrer-Policy。 常见取值从松到紧:no-referrer-when-downgrade、strict-origin-when-cross-origin、no-referrer。 最严的 no-referrer 表示一个字节都不发。 代价是来源统计和防盗链都失效——所以选择策略时要想清楚,你到底需要它的哪一部分功能。

四、动手 Demo:五种来源,看服务端读到什么

服务端原样回显收到的 Referer, 并在响应里声明 Referrer-Policy: strict-origin-when-cross-origin。客户端依次模拟五种来源场景。

def responder(raw, st):
    first, hd, _b = H.parse_headers(raw)
    st[u'ref'] = hd.get(u'referer', u'')      # 注意只有一个 r
    return reply(u'HTTP/1.1 200 OK',
                 [(u'Referrer-Policy',      # 这个是两个 r
                   u'strict-origin-when-cross-origin')],
                 (u'来源:' + (st[u'ref'] or u'(空)')).encode(u'utf-8'))

实跑输出:

--------------------------------------------------------------------
Referer:从哪个页面点过来的
--------------------------------------------------------------------
  【同站跳转】
    Referer                  https://shop.example.com/list
    服务端读到               https://shop.example.com/list

  【带查询串】
    Referer                  https://shop.example.com/list?uid=42&token=abc
    服务端读到               https://shop.example.com/list?uid=42&token=abc

  【从外站过来】
    Referer                  https://search.example.net/?q=book
    服务端读到               https://search.example.net/?q=book

  【从 https 跳到 http】
    Referer                  https://secure.example.com/page
    服务端读到               https://secure.example.com/page

  【直接输入地址】
    Referer                  (空,说明是直接访问)
    服务端读到               (空)

  注意字段名的拼写:**Referer 少了一个 r**。
  正确英文是 referrer,但 1996 年的规范里就写错了,
  为了兼容只能一直错下去。所以响应端的策略字段叫 `Referrer-Policy`(拼对了),
  请求端的字段叫 `Referer`(拼错了)——两个拼法并存,这是历史遗留。

  它的实际用途:统计来源、防盗链、CSRF 防护的辅助判断。
  风险也很直接:URL 里的查询串会被带出去。
  上面那个 `?uid=42&token=abc` 如果原样发到外站,等于把令牌送人了。
  所以现代浏览器默认用 `strict-origin-when-cross-origin` 策略:
  跨站时只发协议加域名,路径和查询串全部砍掉。

同站跳转:https://shop.example.com/list,完整路径都在。 带查询串:https://shop.example.com/list?uid=42&token=abc —— 查询串完整泄露。 这就是要收紧策略的原因。 外站过来:https://search.example.net/?q=book,同样带查询串。 从 https 跳到 http:https://secure.example.com/page。 这种情况历史上会不发 Referer(避免向不安全的页面泄露信息),本 Demo 是自己拼的请求, 所以照发了——真实浏览器会砍掉。 直接输入地址:空。服务端读到「(空)」,这就是「没有来源」的信号。

30 秒实验

  1. 把响应的 Referrer-Policy 改成 no-referrer,想想浏览器会怎么变。
  2. 写一个防盗链判断:Referer 不是本站就回 403,试试会不会误伤直接访问。
  3. 想一想:为什么 CSRF 防护不能只靠 Referer?

常见误区

  1. 「字段名是 Referrer」——请求头里是 Referer,只有一个 r。
  2. 「Referer 一定会发」——可以被策略禁止,也可以被用户关掉。
  3. 「它适合做严格的访问控制」——可以伪造可以省略,只能当辅助判断。
自测三问(点开对答案) 1. 为什么字段名叫 `Referer` 而不是 `Referrer`? —— 1996 年的规范里拼错了,为了兼容只能一直错下去。 2. 它最经典的用途是什么? —— 防盗链:图片只允许从自己的域名引用。 3. 它有什么隐私风险? —— 会泄露来源 URL 的完整路径和查询串,可能带出令牌之类的敏感参数。

来源说完了,只剩两个:一个管传输编码协商,一个是客户端的自我介绍。

相关笔记