为何Firefox设置Authorization头时fetch不发预检请求,Chrome却发?
我做了一个跨域测试页面(部署在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规范的实现细节不同:
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并不在标准的安全首部列表中。
Firefox的历史兼容行为:Firefox在实现CORS时,将Authorization头纳入了允许的简单请求首部范围,因此带Authorization的GET请求会被判定为简单请求,跳过预检直接发送实际请求。这个行为是为了兼容早期一些未处理OPTIONS预检的服务端场景。
Chrome等浏览器的严格实现:Chrome、Safari、Edge严格遵循CORS标准,认为Authorization不属于安全首部,因此带该头的跨域请求不属于简单请求,必须先发送OPTIONS预检请求,确认服务器允许携带Authorization头后,再发送实际的GET请求。
内容的提问来源于stack exchange,提问作者richarddmorey
相关产品推荐
相关产品推荐

