You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Firefox设置Authorization头时fetch不发预检请求,Chrome却发?

Chrome与Firefox在带Authorization头的fetch请求中预检行为差异的原因

我做了一个跨域测试页面(部署在http://localhost:8081),用fetch()发送带Authorization头的请求到不同源的http://localhost:8080。发现Chrome、Safari、Edge会先发送OPTIONS预检请求,而Firefox直接发送实际的GET请求,想弄清楚二者行为差异的原因。

测试代码

<html><body>
Testing.
<script>
  // Insecure page
  fetch('http://localhost:8080/hello2')
  .then(response => {
    return response.text()
  })
  .then(content => {
    document.querySelector('#hello2').innerHTML = content
  });
  // Secure page
  fetch('http://localhost:8080/secure/hello1',
    { headers: {'Authorization': 'Bearer super_secure_token_1'} }
  )
  .then(response => {
    return response.text()
  })
  .then(content => {
    document.querySelector('#hello1').innerHTML = content
  });
</script>
<div id='hello1'>HELLO1</div>
<div id='hello2'>HELLO2</div>

</body>
</html>

服务器日志对比

Firefox日志(直接发送GET请求)

{
   "timestamp":"2023-05-10 08:09:19.916516",
   "level":"DEBUG",
   "name":"Application",
   "pid":68526,
   "msg":"",
   "context":{
      "request_id":"956abd1c-ef01-11ed-8485-ae6922d47386",
      "request":{
         "method":"GET",
         "path":"/secure/hello1",
         "parameters_query":{
            
         },
         "parameters_path":[
            
         ],
         "headers":{
            "sec-fetch-site":"same-site",
            "connection":"keep-alive",
            "dnt":"1",
            "origin":"http://localhost:8081",
            "authorization":"Bearer super_secure_token_1",
            "sec-fetch-dest":"empty",
            "referer":"http://localhost:8081/",
            "accept-encoding":[
               "gzip",
               "deflate",
               "br"
            ],
            "host":"localhost:8080",
            "accept-language":[
               "en-GB",
               "en;q=0.5"
            ],
            "sec-fetch-mode":"cors",
            "accept":"*/*",
            "user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0"
         }
      }
   }
}{
   "timestamp":"2023-05-10 08:09:19.974010",
   "level":"DEBUG",
   "name":"Application",
   "pid":68526,
   "msg":"",
   "context":{
      "request_id":"956abd1c-ef01-11ed-8485-ae6922d47386",
      "response":{
         "status_code":200,
         "headers":{
            "Server":"RestRserve/1.2.1; Rserve/1.8.11",
            "Access-Control-Allow-Origin":"*"
         }
      }
   }
}

Chrome日志(先发送OPTIONS预检请求)

{
   "timestamp":"2023-05-10 08:14:44.556680",
   "level":"DEBUG",
   "name":"Application",
   "pid":68695,
   "msg":"",
   "context":{
      "request_id":"56eac068-ef02-11ed-aed3-ae6922d47386",
      "request":{
         "method":"OPTIONS",
         "path":"/secure/hello1",
         "parameters_query":{
            
         },
         "parameters_path":[
            
         ],
         "headers":{
            "accept-language":[
               "en-GB",
               "en-US;q=0.9",
               "en;q=0.8"
            ],
            "referer":"http://localhost:8081/",
            "sec-fetch-dest":"empty",
            "accept":"*/*",
            "sec-fetch-mode":"cors",
            "user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36",
            "connection":"keep-alive",
            "sec-fetch-site":"same-site",
            "access-control-request-method":"GET",
            "host":"localhost:8080",
            "origin":"http://localhost:8081",
            "access-control-request-headers":"authorization",
            "accept-encoding":[
               "gzip",
               "deflate",
               "br"
            ]
         }
      }
   }
}{
   "timestamp":"2023-05-10 08:14:44.581290",
   "level":"DEBUG",
   "name":"Application",
   "pid":68695,
   "msg":"",
   "context":{
      "request_id":"56eac068-ef02-11ed-aed3-ae6922d47386",
      "response":{
         "status_code":401,
         "headers":{
            "WWW-Authenticate":"Basic"
         }
      }
   }
}

差异原因解析

这种行为差异源于不同浏览器对CORS规范的实现细节不同:

  1. CORS规范的简单请求定义:根据标准CORS规范,「简单请求」必须满足两个核心条件:

    • 请求方法只能是GET、HEAD、POST三者之一
    • 请求中携带的自定义头只能是规范明确列出的「CORS安全首部字段」,包括Accept、Accept-Language、Content-Language、Content-Type(仅限application/x-www-form-urlencoded、multipart/form-data、text/plain三种值),以及DPR、Save-Data等少数其他字段,Authorization并不在标准的安全首部列表中。
  2. Firefox的历史兼容行为:Firefox在实现CORS时,将Authorization头纳入了允许的简单请求首部范围,因此带Authorization的GET请求会被判定为简单请求,跳过预检直接发送实际请求。这个行为是为了兼容早期一些未处理OPTIONS预检的服务端场景。

  3. Chrome等浏览器的严格实现:Chrome、Safari、Edge严格遵循CORS标准,认为Authorization不属于安全首部,因此带该头的跨域请求不属于简单请求,必须先发送OPTIONS预检请求,确认服务器允许携带Authorization头后,再发送实际的GET请求。

内容的提问来源于stack exchange,提问作者richarddmorey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 12:52:05