同源策略
在說跨域之前,我們需要先了解下 同源策略。它是一個規范(Netscape 1995年提出),並沒有指定具體的使用范圍和實現方式。
為了保證使用者信息的安全,防止惡意網站篡改用戶數據,一些常見的Web技術都默認采用了同源策略(如Silverlight, Flash, XMLHttpRequest, Dom等)。
那如何判斷同源呢?
- 相同的協議
- 相同的域名
- 相同的的端口號
我們用一個表格來展示同源的判斷:
| URL1 | URL2 | 是否同源 | 分析 |
|---|---|---|---|
| http://www.a.com | https://www.a.com | 不同源 | 協議不一致 |
| http://www.a.com | http://www.b.com | 不同源 | 域名不一致 |
| http://www.a.com | http://www.a.com:8080 | 不同源 | 端口不一致 |
| http://www.a.com | http://test.a.com | 不同源 | 域名不一致 |
| http://www.a.com | http://www.a.com/test | 同源 | 判斷同源和path無關 |
| http://www.a.com | http://www.a.com:80 | 同源 | 不帶端口訪問時,默認是80 |
如果不是同源會有哪些使用限制呢?
-
Cookie,WebStorage(LocalStorage, SessionStorage),Cache(Application Cache, CacheStorage),Web DB(WebSql IndexDB)等都無法共享
-
無法彼此操作各自的DOM(Iframe)
-
無法發送Ajax請求
-
其他
注意:如果兩個站點,具有相同的一級域名(如 www.a.com, test.a.com,一級域名都是a.com),那么可以通過各自設置document.domain='a.com' 來共享Cookie。
注意2:如果是iframe非同源,雖然不能操作dom,但是能操作location.href。
什么是跨域?
通過以上內容,我們了解到了什么是同源策略,以及怎么判斷同源。那么與之相反,如果不滿足同源,則就是跨域。
在瀏覽器上,如果訪問跨域資源,將會有諸多限制(為了安全),參考上面的同源限制。
注意:跨域限制是瀏覽器的機制,如果直接在服務端請求,是不會觸發跨域限制的。
那些我們遇到的跨域
1、圖片跨域
對於圖片來說,大部分場景是不需要處理跨域限制的,因為一般來說,圖片沒有跨域限制。
在也有例外,如果在 Canvas 中操作跨域的圖片,那么就會觸發跨域限制。解決辦法是在返回圖片的時候,添加 Access-Control-Allow-Origin: orign | '*' 來允許跨域。
2、Iframe跨域
這個也不太常見,如果網站本身和iframe嵌入的站點都是我們自己可以控制的,那么應直接使用 postMessage 來通信。如果瀏覽器較舊,不支持 postMessage ,可以考慮通過window.name來傳遞數據。
window.name 傳遞數據原理
首先在iframe訪問跨域的站點,這個站點,將數據寫入到window.name中。
然后主站點,修改iframe的location.href='about:blank' 或其他不跨域的站點。
最后通過window.name獲取數據
這是因為同一個iframe的window.name是相互共享的。在現代瀏覽器中,該方式可能會失效,此時請使用 postMessage。
3、字體跨域
跨域使用字體文件,也會觸發攔截。這個的解決辦法和圖片跨域一致,后端設置CORS頭部即可。
4、Ajax跨域
這是我們經常會遇到的跨域問題,由於現在流行的開發模式,很多時候我們都需要處理這類型的跨域。
如何判斷Ajax跨域
當我們在訪問一個Ajax請求,控制台出現如下錯誤時,我們基本可以判斷,是被跨域攔截了:
XMLHttpRequest cannot load xxxxx. No 'Access-Control-Allow-Origin' header is present on the requested resource.
很多時候,我們的API和Web並不在一個站點上(多個域名),而我們又必須要跨域訪問。這個時候我們就需要想辦法實現跨域資源訪問。
以下,我們就來看看如何實現跨域資源訪問:
CORS(跨域資源訪問)- 標准做法,強烈推薦
開發模式的演進,導致我們很多的應用都是跨域訪問。這個時候CORS規范也就應運而生了。使用它,我們可以直接對跨域資源進行訪問,了解更多,請參考CORS詳解。
該方式的核心是通過和后端API協商,看是否允許跨域訪問。對於滿足某些條件的請求,會先發送一個預請求。簡單請求,也需要服務器允許跨域訪問。
最關鍵的的幾個響應頭如下:
- Access-Control-Allow-Origin: origin | '*' 允許某個指定的域訪問,*表示不限制域。
- Access-Control-Allow-Methods: 'GET,POST,PUT,DELETE' 允許哪些類型的請求
- Access-Control-Allow-Headers: 'x-token' 允許的自定義Header。
注意:該方式由服務端設置,前端無需設置,也無法設置。只要服務端處理好了,前端不需要做任何處理即可使用。
反向代理(將跨域代理為同域,繞過)
既然跨域有限制,那么我們可以考慮將跨域變成同域,這樣不就沒有限制了么?
以 Nginx 為例,我們只需要將特定路徑的請求轉發給真正的后端API即可:
server {
listen 8101;
root /dist;
index index.html;
location ~* \.(eot|ttf|woff|woff2)$ {
add_header x-server $server_addr;
add_header Access-Control-Allow-Origin '*';
}
location ^~ /api/v1 {
proxy_pass http://apis.xxx.com/api/v1;
}
}
注意:該方式需要在部署的時候做處理,前端需要修改請求api的地址為同域。
服務端轉發(通過不跨域的請求跨域API,繞過)
該方式,通過請求不跨域的api,然后在api中再呼叫真實的跨域api,由於是服務端請求,所以也就避開了跨域問題。整體看來,這種方式有點多此一舉,不過如果把這個轉發由統一的程序進行處理,還是挺不錯的。
注意:該方式在后端API中處理,前端需要修改請求api地址為同域。
JSONP(利用script無跨域限制,繞過)
該方式利用Script請求資源不會觸發跨域限制這個特點來實現。JSON原理,請參考JSONP詳解。
注意:該方式需要前后端搭配,后端需要支持JSONP請求,前端需要采用JSONP的方式去請求數據。
注意2:該方式由於實現原理限制,只能處理GET請求。
綜上,遇到跨域請求,就先去找后端啊。前端真的獨自搞不定啊。
總結
跨域是項目開發中,非常常見的問題。就算是前端開發,也一定要理解跨域,了解跨域的處理方案。以便於能夠真正的處理好開發任務(或許,這樣和后端交(Si)流(Bi)也更有底氣)。
