CORS為什么能保障安全?為什么只對復(fù)雜請求做預(yù)檢?
大家好,我是年年!提起CORS,大部分的文章都在寫什么是簡單請求、什么是復(fù)雜請求,復(fù)雜請求預(yù)檢的流程又是怎樣。
但如果問你:
- CORS為什么要帶上源,這是為了保障當(dāng)前站點的安全還是目的服務(wù)器的安全?
- 為什么區(qū)分簡單請求和復(fù)雜請求,只對復(fù)雜請求做預(yù)檢?
這篇文章會圍繞CORS是如何保障安全的的,講清這幾個問題。讀完可以對CORS知其然,并知其所以然。
什么是CORS
相信每個前端的控制臺都中都被打印過這樣一段話,告訴你:你的跨域請求策略攔截啦!
首先要明確的一點,CORS的目的不是攔截請求,反倒是為了讓其能正常請求。
CORS誕生的背景是「同源策略」。這是一個相當(dāng)嚴(yán)苛的規(guī)定,它禁止了跨域的AJAX請求。但實際的開發(fā)中又有這樣的需求,于是開一個口子——只要配置了CORS的對應(yīng)規(guī)則,跨域請求就能正常進(jìn)行。這也正和CORS的名字對應(yīng)起來了——「跨域資源共享」,就是為了能讓跨域請求在「同源策略」的大背景下進(jìn)行。
回到上面提到控制臺報錯,這不是阻止你做跨域請求,而是提示你:因為沒有按照CORS要求做配置,不得不暫時攔截。
怎樣配置CORS
上文講清了,只要按照CORS要求做配置,就能突破同源策略的限制,下面將會講述如何配置。
這部分不需要前端操心,完全后端來做:在響應(yīng)頭里面加一個字段Access-Control-Allow-Origin(允許請求的來源),這個值要把前端的源包含進(jìn)去。
舉個例子:請求的后端接口是http://fe_nian,你本地正在開發(fā)前端工程跑在8080端口。那么后端會在響應(yīng)頭里加上Access-Control-Allow-Origin:*來允許http://localhost:8080這個源去做跨域請求,因為*是所有的意思。
跨域請求的流程
CORS把請求分成簡單請求和復(fù)雜請求,劃分的依據(jù)是“是否會產(chǎn)生副作用”。
簡單貼一下定義,同時滿足下面這兩個條件的是簡單請求
- 請求方法是HEAD/GET/POST。
- 請求體的文件類型只能是form-urlencoded、form-data、text/plain(這類文章很多,不再贅述,可以看阮一峰-跨域資源共享)。
對于簡單請求,流程如下:
- 瀏覽器發(fā)起請求,并且自動加上請求的來源origin給服務(wù)器檢查。
- 服務(wù)器返回數(shù)據(jù),并返回檢查結(jié)果,配置CORS響應(yīng)頭。
- 瀏覽器檢查CORS響應(yīng)頭,如果包含了當(dāng)前的源則放行,反之?dāng)r截。
這里需要注意,瀏覽器是攔截響應(yīng),而不是攔截請求,跨域請求是發(fā)出去的,并且服務(wù)端做了響應(yīng),只是瀏覽器攔截了下來。
對于復(fù)雜請求,整個流程如下:
- 瀏覽器發(fā)起預(yù)檢請求,帶上請求的來源origin,不包含請求體。
- 服務(wù)器返回檢查結(jié)果,配置CORS頭。
- 瀏覽器發(fā)起真正請求。
- 瀏覽器返回數(shù)據(jù)。
瀏覽器會檢查第2步中拿到的CORS頭,如果沒有包含當(dāng)前的源,后續(xù)的第3、4步都不會進(jìn)行,也就是不會發(fā)起真正請求。
為什么要帶上源
CORS給開發(fā)帶來了便利,同時也帶來了安全隱患——CSRF攻擊。
它的基本流程如下:
- 用戶登錄受害網(wǎng)站,把獲取的身份憑證保存在瀏覽器的cookie中。也就是上圖流程的①②③。
- 用戶用同一瀏覽器打開黑客網(wǎng)站,黑客網(wǎng)站向受害網(wǎng)站服務(wù)器發(fā)起一個惡意請求,這時瀏覽器會自動從cookie中取出身份憑證,把它帶上。也就是上圖的④⑤。
- 受害網(wǎng)站服務(wù)端發(fā)現(xiàn)有身份憑證,惡意請求被成功受理。
如果嚴(yán)格按照同源政策,第2步的跨域請求不能進(jìn)行的,也就不會造成危害。所以CORS策略的心智模型是:所有跨域請求都是不安全的,瀏覽器要帶上來源給服務(wù)器檢驗。
如果做過服務(wù)端開發(fā),應(yīng)該知道,服務(wù)端不存在跨域一說,去獲取另一個服務(wù)器的資源是再順暢不過的事情。因為服務(wù)端不像瀏覽器一樣,作為“容器”存貯著用戶身份憑證——也就是上面的第1步發(fā)生的事情,它去做跨域請求沒有這樣的風(fēng)險。
為什么只對復(fù)雜請求做預(yù)檢
上文提到,劃分簡單請求和復(fù)雜請求的依據(jù)是“是否產(chǎn)生副作用”。這里的副作用指對數(shù)據(jù)庫做出修改:使用GET請求獲取新聞列表,數(shù)據(jù)庫中的記錄不會做出改變,而使用PUT請求去修改一條記錄,數(shù)據(jù)庫中的記錄就發(fā)生了改變。
對于簡單請求,瀏覽器只會在請求頭加上一個origin字段標(biāo)識請求來源;對于非簡單請求,瀏覽器會先發(fā)出一個預(yù)檢請求,獲得肯定回答后才會發(fā)送真正的請求,下面會講清楚為什么這么做。
可以假設(shè)網(wǎng)站被CSRF攻擊了——黑客網(wǎng)站向銀行的服務(wù)器發(fā)起跨域請求,并且這個銀行的安全意識很弱,只要有登錄憑證cookie就可以成功響應(yīng):
黑客網(wǎng)站發(fā)起一個GET請求,目的是查看受害用戶本月的賬單。銀行的服務(wù)器會返回正確的數(shù)據(jù),不過影響并不大,而且由于瀏覽器的攔截,最后黑客也沒有拿到這份數(shù)據(jù);
黑客網(wǎng)站發(fā)起一個PUT請求,目的是把受害用戶的賬戶余額清零。瀏覽器會首先做一次預(yù)檢,發(fā)現(xiàn)收到的響應(yīng)并沒有帶上CORS響應(yīng)頭,于是真正的PUT請求不會發(fā)出;
幸好有預(yù)檢機(jī)制,否則PUT請求一旦發(fā)出,黑客的攻擊就成功了。
結(jié)語
回到開頭的兩個問題,不難得出答案:
- 對于跨域請求帶上請求來源,是為了防止CSRF攻擊;瀏覽器的心智模型是:跨域請求都是不安全的,CORS的機(jī)制是為了保障請求目的服務(wù)器的安全。
- 依據(jù)是否對服務(wù)器有副作用,劃分了簡單請求和復(fù)雜請求(但由于歷史原因,表單POST請求也被劃分成了簡單請求),預(yù)檢機(jī)制會把不安全的復(fù)雜請求攔截下來,避免對服務(wù)器造成危害,而簡單請求通常不會對服務(wù)器的資源作出修改,即使發(fā)出危害不大。