前情提要

今天在工作中遇到一个问题,Chrome浏览器请求应用注册在127.0.0.1:xxxxx 服务的接口总是超时;

而且奇怪的是,这里发起的是个GET请求,为什么会有OPTIONS预检呢?看下这个预检的请求。

排查了Web页面和本地应用,对应的模块都没有做改动;

浏览器的请求复制成curl命令后,再在命令行中发起请求也能正常响应。

查自己的代码无果,最后不得不开始怀疑是不是浏览器发生了变化;上网查了一通资料,发现是Chrome的安全策略发生了变化,导致了这个问题:Chrome 安全策略更新-网站访问本地localhost的限制 - 掘金

可以说是天降横锅。

什么样的请求会受到限制?

Address blockNameReferenceAddress space
127.0.0.0/8IPv4 LoopbackRFC1122local
10.0.0.0/8Private UseRFC1918private
172.16.0.0/12Private UseRFC1918private
192.168.0.0/16Private UseRFC1918private
169.254.0.0/16Link LocalRFC3927private
::1/128IPv6 LoopbackRFC4291local
fc00::/7Unique LocalRFC4193private
fe80::/10Link-Local UnicastRFC4291private
::ffff:0:0/96IPv4-mappedRFC4291see mapped IPv4 address

上表摘自WICG对Private Network Access的提案,从外到内的访问会受到限制:

  • public访问private
  • public访问local
  • private访问local

即下图蓝色箭头所示。

发起这样的请求时,Chrome浏览器会先发送预检请求;需要预检成功,才能继续发起请求。

为什么要这么改?

这个改动的动机是保护私有网络中的路由器或其他设备免于CSRF攻击。这里是Chrome的Blog里贴的Security Affairs的文章,介绍了这种攻击的情况。

一个典型的场景是,用户跟存在CSRF攻击的页面发生了互动,让这个页面能够对私有网络中的路由器发起请求;这个请求修改了路由器的DNS服务,导致这个网络中的其他设备在访问银行页面时使用了恶意的DNS记录,导向了钓鱼网站。

什么时候改的?

需要注意的是,中文网络下查到的Chrome浏览器引入变更的时间表,跟我在Chrome团队的Blog看到的时间表都不同。建议以Chrome的博客为准。下面引用自Rollout plan - Private Network Access: introducing preflights

Rollout plan

Chrome will roll this change out in two phases to give websites time to notice the change and adjust accordingly.

  1. In Chrome 98:
    • Chrome sends preflight requests ahead of private network subresource requests.
    • Preflight failures only display warnings in DevTools, without otherwise affecting the private network requests.
    • Chrome gathers compatibility data and reaches out to the largest affected websites.
    • We expect this to be broadly compatible with existing websites.
  2. In Chrome 101 at the earliest:
    • This will begin only if and when compatibility data indicates that the change is safe enough and we’ve outreached directly when necessary.
    • Chrome enforces that preflight requests must succeed, otherwise failing the requests.
    • A deprecation trial starts at the same time to allow for websites affected by this phase to request a time extension. The trial will last for at least 6 months.

简单翻译一下就是:

Chrome会分两个阶段来推出这个私有网络访问(Private Network Access,后面简写成PNA)的限制;

  1. Chrome 98 开始会在PNA请求之前发送预检请求,预检失败会在开发者工具中展示一个警告,不影响私有网络请求。Chrome会收集兼容性数据并联系受影响最严重的网站。我们希望这个可以和大部分现有网站兼容。
  2. 最早在Chrome 101版本(需要兼容性数据显示更改足够安全才会做出更改),需要预检成功才能发送私有网络请求,否则请求会失败。网站如果需要更多时间来做迁移或变更,可以找谷歌申请。

如何解决?——使用预检

预检请求是跨域资源共享 (CORS) 标准引入的一种机制,用于在向目标网站发送可能有副作用的 HTTP 请求之前向其请求许可。这确保了目标服务器理解 CORS 协议并显着降低了 CSRF 攻击的风险。

预检请求引入了一对新的请求头和响应头:

  • Access-Control-Request-Private-Network: true 这个请求头会出现在PNA预检请求的Header中
  • Access-Control-Allow-Private-Network: true 这个响应头用于PNA预检请求的响应,表示服务接受PNA请求。

无论请求方法和模式如何,都会为所有专用网络请求发送 PNA 的预检请求。它们在 cors 模式以及 no-cors 和所有其他模式下的请求之前发送。这是因为所有私网请求都可以用于 CSRF 攻击,无论请求模式如何,以及响应内容是否对发起者可用。

简单来说,你需要在原来的本地服务端实现OPTIONS预检请求的处理,并在响应中加上Access-Control-Allow-Private-Network: true的响应头,这样后面的请求就能正常进行了。

参考资料

Chrome团队的博客 - Private Network Access Preflight/

WICG对Private Network Access的提案