如果你使用76+版本的chrome瀏覽器,通過開發者面板查看每個網絡請求,會發現都有幾個Sec-Fetch開頭的請求頭,例如訪問百度首頁https://www.baidu.com/的請求:
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
這是用來干嘛的呢,簡單來說,就是網絡請求的元數據描述,服務端根據這些補充數據進行細粒度的控制響應,換句話說,服務端可以精確判斷請求的合法性,杜絕非法請求和攻擊,提高web服務的安全性。
Fetch Metadata Request Headers
Sec-Fetch開頭的請求頭都屬於Fetch Metadata Request Headers,於2019年發布的新草案,目前處於Editor's Draft階段,支持度還不是很高,還需要注意的是,這些請求頭都是Forbidden header,也就是不能被篡改的,是瀏覽器自動加上的請求頭,這樣也保證了數據的准確性,還需要注意的是如果資源是本地緩存加載,那么就不會添加這些請求頭了,這也容易理解,就不多說了。
規范的意義
近些年web領域發展迅速,但是安全問題也十分突出,從最初瀏覽器的同源模型到CSP,再到Fetch Metadata Request Headers,都是對web安全不斷的完善和加強,以往很多安全策略側重於客戶端的防護,服務端需要識別非法請求往往比較困難,因為缺乏判斷請求的依據,控制比較粗線條,而Fetch Metadata Request Headers的出現就為服務端過濾非法請求提供了元數據,避免csrf,xssi等攻擊就很容易了。
接下來探究一下這四個請求頭的含義;
Sec-Fetch-Dest
含義:
表示請求的目的地,即如何使用獲取的數據;
取值范圍:

說明:
Dest是destination的縮寫,根據上面的取值范圍可很容易理解了,這個請求頭指明客戶端請求的目的,期望需要什么樣的資源;
Sec-Fetch-Mode
含義
該請求頭表明了一個請求的模式;
取值范圍:
cors:跨域請求;
no-cors:限制請求只能使用請求方法(get/post/put)和請求頭(accept/accept-language/content-language/content-type);
same-origin:如果使用此模式向另外一個源發送請求,顯而易見,結果會是一個錯誤。你可以設置該模式以確保請求總是向當前的源發起的;
navigate:表示這是一個瀏覽器的頁面切換請求(request)。 navigate請求僅在瀏覽器切換頁面時創建,該請求應該返回HTML;
websocket:建立websocket連接;
說明:
cors表示跨域請求,且要求后端需要設置cors響應頭;no-cors並不是代表請求不跨域,而是服務端不設置cors響應頭,什么情況下會是這種模式呢,圖片/腳本/樣式表這些請求是容許跨域且不用設置跨域響應頭的,而no-cors也是默認的模式;same-origin表示同源請求,這就限制了不能跨域,前面說的cors和no-cors是容許跨域的,只是要求服務端的設置不同而已,熟悉fetch接口的同學對mode屬性應該不陌生,其實跟這里的含義是一樣的,只是fetch的mode大家可以手動設置,而Sec-Fetch-Mode不能干預而已;
Sec-Fetch-Site
含義:
表示一個請求發起者的來源與目標資源來源之間的關系;
取值范圍:
cross-site:跨域請求;
same-origin:發起和目標站點源完全一致;
same-site:有幾種判定情況,詳見說明;
none:如果用戶直接觸發頁面導航,例如在瀏覽器地址欄中輸入地址,點擊書簽跳轉等,就會設置none;
說明:
same-site有幾種情況(A->B):
| A | B | same site |
|---|---|---|
(" https ", " example.com ") |
(" https ", " sub.example.com ") |
true |
(" https ", " example.com ") |
(" https ", " sub.other.example.com ") |
true |
(" https ", " example.com ") |
(" http ", " non-secure.example.com ") |
false |
(" https ", " r.wildlife.museum ") |
(" https ", " sub.r.wildlife.museum ") |
true |
(" https ", " r.wildlife.museum ") |
(" https ", " sub.other.r.wildlife.museum ") |
true |
(" https ", " r.wildlife.museum ") |
(" https ", " other.wildlife.museum ") |
false |
(" https ", " r.wildlife.museum ") |
(" https ", " wildlife.museum ") |
false |
(" https ", " wildlife.museum ") |
(" https ", " wildlife.museum ") |
true |
在地址有重定向的情況下,Sec-Fetch-Site取值稍微復雜一點,直接參考一下示例:
1.https://example.com/ 請求https://example.com/redirect,此時的Sec-Fetch-Site 是same-origin;
2.https://example.com/redirect重定向到https://subdomain.example.com/redirect,此時的Sec-Fetch-Site 是same-site (因為是一級請求二級域名);
3.https://subdomain.example.com/redirect重定向到https://example.net/redirect,此時的Sec-Fetch-Site 是cross-site (因為https://example.net/和https://example.com&https://subdomain.example.com/是不同站點);
4.https://example.net/redirect重定向到https://example.com/,此時的Sec-Fetch-Site 是cross-site(因為重定向地址鏈里包含了https://example.net/);
Sec-Fetch-User
含義:
取值是一個Boolean類型的值,true(?1)表示導航請求由用戶激活觸發(鼠標點擊/鍵盤),false(?0)表示導航請求由用戶激活以外的原因觸發;
取值范圍:
?0
?1
說明:
請求頭只會在導航請求情況下攜帶,導航請求包括document , embed , frame , iframe , or object ;
安全策略
了解了上面是個請求頭的含義之后,我們就可以根據項目實際情況來制定安全策略了,例如google I/O提供的一個示例:
# Reject cross-origin requests to protect from CSRF, XSSI & other bugs
def allow_request(req):
# Allow requests from browsers which don't send Fetch Metadata
if not req['sec-fetch-site']:
return True
# Allow same-site and browser-initiated requests
if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
return True
# Allow simple top-level navigations from anywhere
if req['sec-fetch-mode'] == 'navigate' and req.method == 'GET':
return True
return False
1.瀏覽器不支持Sec-Fetch-*請求頭,則不做處理;
2.容許sec-fetch-site為same-origin, same-site, none三種之一的請求;
3.容許sec-fetch-mode為navigate且get請求的方法;
4.容許部分跨域請求,可設置白名單進行匹配;
5.禁止其他非導航的跨域請求,確保由用戶直接發起;
在使用Fetch Metadata Request Headers時,還需要注意Vary響應頭的正確設置,Vary這個響應頭是干嘛的呢,其實就是緩存的版本控制,當客戶端請求頭中的值包含在Vary中時,就會去匹配對應的緩存版本(如果失效就會同步資源),因此針對不同的請求,能提供不同的緩存數據,可以理解為差異化服務,說明白了Vary響應頭之后,就明白了Fetch Metadata Request Headers與Vary的影響關系了,因為要確保緩存能正確處理攜帶Sec-Fetch-*請求頭的客戶端響應,例如Vary: Accept-Encoding, Sec-Fetch-Site,因此有沒有攜帶Sec-Fetch-Site將會對應兩個緩存版本。
參考資料:
https://developer.mozilla.org/zh-CN/docs/Web/API/Request/mode
https://fetch.spec.whatwg.org/#concept-request-mode
https://web.dev/fetch-metadata/
https://w3c.github.io/webappsec-fetch-metadata/#intro
