瀏覽器的同源策略與跨域處理
一、 同源策略
如果兩個頁面的協議,端口(如果有指定)和域名都相同,則兩個頁面具有相同的源。
下表給出了相對http://store.company.com/dir/page.html
同源檢測的示例:
同源策略限制了從同一個源加載的文檔或腳本如何與來自另一個源的資源進行交互。
允許跨源訪問的一些例子:
<script src="..."></script>
標簽嵌入跨域腳本。語法錯誤信息只能在同源腳本中捕捉到。<link rel="stylesheet" href="...">
標簽嵌入CSS。由於CSS的松散的語法規則,CSS的跨域需要一個設置正確的Content-Type
<img>
嵌入圖片。支持的圖片格式包括PNG,JPEG,GIF,BMP,SVG,...<video>
和<audio>
嵌入多媒體資源。<object>
,<embed>
和<applet>
的插件。@font-face
引入的字體。一些瀏覽器允許跨域字體( cross-origin fonts),一些需要同源字體(same-origin fonts)。<frame>
<iframe>
載入的任何資源。站點可以使用X-Frame-Options消息頭來阻止這種形式的跨域交互。
使用 CORS 允許跨源訪問。
跨源腳本API的訪問
Javascript的APIs中,如 iframe.contentWindow
, window.parent
, window.open
和 window.opener
允許文檔間直接相互引用。當兩個文檔的源不同時,這些引用方式將對 Window 和 Location對象的訪問添加限制。
可以使用window.postMessage作不同源文檔之間的交流。
跨源數據存儲訪問
存儲在瀏覽器中的數據,如localStorage和IndexedDB,以源進行分割。每個源都擁有自己單獨的存儲空間,一個源中的Javascript腳本不能對屬於其它源的數據進行讀寫操作。
二、幾種跨域解決方案
1、JSONP
Jsonp(JSON with Padding) 是 json 的一種"使用模式",可以讓網頁從別的域名(網站)那獲取資料,即跨域讀取數據。
Jsonp 的實現原理是利用 <script> 標簽可以獲取不同源資源的特點,來達到跨域訪問某個資源的目的。假如 <script> 元素內部的代碼沒有位於某個函數中,那麽這些代碼會在頁面被加載時被立即執行。
應用實例:
服務端代碼使用jFinal框架
1 package com.controller; 2 3 import com.mazu.core.Controller; 4 5 public class JsonPController extends Controller{ 6 7 public void getUserInfo(){ 8 9 String userId = getPara("userId"); 10 String callBackFunc = getPara("callback"); 11 if(userId!=null&&userId.equals("123")){ 12 String userJson = "{\"userName\":\"Admin\",\"age\":26}"; 13 callBackFunc += "(‘success‘,"+userJson+")";//拼接為callback(a,b)的形式返回給前端 14 renderJson(callBackFunc); 15 }else{ 16 renderJson(callBackFunc+"(‘error‘)"); 17 } 18 } 19 }
客戶端代碼
1 <script> 2 function callbackFunc(status,data){ 3 if(status=="success"){ 4 console.log(data.userName+":"+data.age); 5 }else{ 6 console.log(status); 7 } 8 } 9 </script> 10 <script type="text/javascript" src="http://localhost/TestJSONP/jsonp/getUserInfo?userId=123&callback=callbackFunc"></script>
chrome中執行結果
由於是本地測試,使用的是端口不同的兩個項目,可以看到服務端響應後,返回給前端一個字符串callback(status,data),前端<script>接收到這段代碼後立即執行,成功實現了跨域資源的訪問。
在jQuery中如何通過JSONP來跨域獲取數據
第一種方法是在ajax函數中設置dataType為‘jsonp‘:
$.ajax({
dataType: ‘jsonp‘,
url: ‘http://localhost/TestJSONP/jsonp/getUserInfo?userId=123‘,
success: function(data){
console.log(data);//success
}
});
上面的方式只能返回一個參數,多參數時將無法得到第二個及之後的參數。
可以在傳遞過程中自定義函數名,使用jsonpCallback參數
jsonp:表示傳遞的參數,默認為callback,我們也可以自定義
jsonpCallback:表示傳遞的參數值,也就是回調的函數名稱,這是自定義的名稱
1 <script src="http://apps.bdimg.com/libs/jquery/1.8.3/jquery.js"></script> 2 <script> 3 function callbackFunc(status,data){ 4 if(status=="success"){ 5 // console.log(data.userName+":"+data.age); 6 console.log("callback:"+data); 7 }else{ 8 console.log("callback:"+status); 9 } 10 } 11 $.ajax({ 12 type:"get", 13 url: ‘http://localhost/TestJSONP/jsonp/getUserInfo?userId=123‘, 14 dataType: ‘jsonp‘, 15 jsonp:‘callback‘, 16 jsonpCallback:‘callbackFunc‘, 17 success: function(data){ 18 console.log("ajax:"+data); 19 } 20 }); 21 22 </script>
執行結果:
可以看到返回結果中,仍然獲取不到第二個參數值。仔細debug了一下後臺,返回的字符串也是正確的。
於是我改了下後臺的返回值和回調函數的參數,得到了正確的測試結果。
由此可見,使用jQuery做JSONP跨域調用時,與ajax的success回調函數一樣,服務端需要將傳的內容封裝在一個返回值中。
為什麽使用jsonpCallback簡單的綁定回調函數就能實現回調函數調用了呢?
仔細跟蹤chrome中的執行過程,發現Jquery首先將回調函數賦值給一個內部函數,在服務端響應成功後,執行這個內部函數,然後銷毀,起到一個臨時代理的作用。
jQuery 的 $.ajax 只支持get方式獲取跨域數據,並且它不支持出錯時的回調。
jQuery-JSONP是一個支持 JSONP 調用的 jQuery 插件,使用它是因為它支持出錯時的 ajax 回調。
1 <script src="jquery-3.2.1.js"></script> 2 <script src="jquery-jsonp.js"></script> 3 <script> 4 $.jsonp({ 5 url: ‘http://localhost/TestJSONP/jsonp/getUserInfo‘, 6 data: { userId: 123 }, 7 callbackParameter: "callback", 8 success: function (data, textStatus, xOptions) { 9 console.log(data); 10 }, 11 error: function (xOptions, textStatus) { 12 } 13 }); 14 </script>
第1個需要註意的地方是 callbackParameter,如果沒有專門的 callback 函數,一定要寫上 "callback";
第2個需要註意的地方是在 success 回調函數中,xOptions包含傳給服務器的參數,如xOptions.data={userId:123}。
2、CORS
CORS全稱是“跨域資源共享”(Cross-origin resource sharing),它是一種通過設置HTTP頭信息來獲取跨源服務器上的特定資源,主要是設置Access-Control-Allow-Origin字段的值。
服務端代碼:
1 public void getUserInfo(){ 2 3 String userId = getPara("userId"); 4 String userJson = ""; 5 if(userId!=null&&userId.equals("123")){ 6 System.out.println("userId:"+userId); 7 userJson = "{\"userName\":\"Admin\",\"age\":26}"; 8 } 9 //getResponse().setHeader("Access-Control-Allow-Origin", "*"); 10 renderJson(userJson); 11 12 }
客戶端代碼:
1 $.ajax({ 2 type:"get", 3 url: ‘http://localhost/TestJSONP/jsonp/getUserInfo?userId=123‘, 4 dataType: ‘json‘, 5 success: function(data){ 6 console.log("ajax:"+data); 7 } 8 })
當服務端回應頭信息中沒有包含Access-Control-Allow-Origin
字段時,瀏覽器報錯如下:
設置Access-Control-Allow-Origin為*,表示接受任意域名的請求,在實際開發中為了安全起見一般設為特定域名。
與JSONP的比較
CORS與JSONP的使用目的相同,但是比JSONP更強大。
JSONP只支持GET
請求,CORS支持所有類型的HTTP請求。JSONP的優勢在於支持老式瀏覽器,以及可以向不支持CORS的網站請求數據。
3、window.postMessage
HTML5跨文檔消息傳輸Cross Document Messaging。下一代瀏覽器都將支持這個功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。
用postMessage支持基於web的實時消息傳遞。
window.postMessage() 方法被調用時,會在所有頁面腳本執行完畢之後(e.g., 在該方法之後設置的事件、之前設置的timeout 事件,etc.)向目標窗口派發一個 MessageEvent
消息。
該MessageEvent
消息有四個屬性需要註意:
- message 屬性表示該message 的類型;
- data 屬性為 window.postMessage 的第一個參數;
- origin 屬性表示調用window.postMessage() 方法時調用頁面的當前狀態;
- source 屬性記錄調用 window.postMessage() 方法的窗口信息。
語法:
otherWindow.postMessage(message, targetOrigin, [transfer]);
參考:
- 瀏覽器的同源策略
- JSONP教程
- 淺談瀏覽器端JavaScript跨域解決方法
- JavaScript CORS通信
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage
瀏覽器的同源策略與跨域處理