同源策略:
是由NetScape提出的著名的安全策略,所有支持javaScript的瀏覽器都使用這個策略。同源策略限制了一個源中加載文本或腳本與來自其它源中資源的交互方式。
IE特例:
授信范圍(Trust Zones):兩個相互之間高度互信的域名,如公司域名(corporate domains),不遵守同源策略的限制。
端口:IE未將端口號加入到同源策略的組成部分之中,因此 http://company.com:81/index.html 和http://company.com/index.html 屬于同源并且不受任何限制。
以上參考自MDN。
所謂同源是指:域名、協議、端口都相同。同源策略是瀏覽器最核心也最基本的安全功能
。這是知乎上關于同源策略的一篇文章。。但是瀏覽器在安全性和實用性上做出了讓步,img/script/style/iframe等有src屬性的都可以跨域引用資源。
方案一——通過JSONP跨域
jsonp是JSON with padding的簡寫,看起來與json差不多,但是包含在函數調用中的json,利用動態script元素來使用(具有src屬性的如img,iframe,srcipt都不受同源策略的影響)。該協議的一個要點就是允許用戶傳遞一個callback參數給服務端,然后服務端返回數據時會將這個callback參數作為函數名來包裹住JSON數據,這樣客戶端就可以隨意定制自己的函數來自動處理返回數據了。
如果使用jquery,可以在type為get的時候dataType設為jsonp,就可以了。
$.ajax({
url: 'http://www.qdaily.com/get_user_and_radar.json?winWidth=1280&winHeight=800',
type: 'get',
dataType: 'jsonp',
data: {},
})
.done(function(data) {
console.log(data.status);
})
然后,很幸運的就可以看到這個:
但是可以看出其實是請求到數據的,只是沒有執行我們的callBack函數。
因為前面有提到:服務端返回數據時會將這個callback參數作為函數名來包裹住JSON數據,也就是服務端的返回需要拼接成這樣(比如函數名叫做haddleData):
haddleData({"status":true,subscribes:["12","23"]})
另外:jquery的ajax方法可以傳入兩個參數
jsonp : "callback"http://設置這個會替換瀏覽器發送請求時地址后面自動添加的?callback=xxx中的callback這個字,一般情況下不用傳這個參數
jsonpCallback: "success_jsonpCallback"http://這個值將用來取代jQuery自動生成的隨機函數名,也就是上句話中的'xxx'。
【這兒遺留了一個問題,還請各位解決】如果jsonpCallback不手動設置的話,jquery是會自動生成個隨機的字符串的,但是服務器那邊的函數名要寫什么,回調函數才會正確的執行呢?
看一個運行成功的實例:
$.ajax({
url: 'http://localhost:8000/remote/remote.js',
type: 'get',
dataType: 'jsonp',
jsonp: 'back',
jsonpCallback: "haddleData",
data: {},
})
.done(function(data) {
console.log(data.status);
})
//這段程序運行在8080端口的
remote.js作為遠端其中寫了句
haddleData({"status":true,subscribes:["12","23"]})
對于大部分js初學者,大多使用jquery,而對原生不太熟悉。但是須知,ajax和jsonp其實本質上是不同的東西,ajax的核心是通過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態添加<script>標簽來調用服務器提供的js腳本,利用腳本下載下來后立即執行的事實實現callback方法的調用。
原生js的jsonp簡單實現:
<script>
function haddleData(data){
console.log(data.status);
}
</script>
<script type="text/javascript" src="http://localhost:8000/remote/remote.js"></script>
當然有多個本地函數需要處理的時候,加上回調函數名才是方便的,像這樣:
<script type="text/javascript" src="http://localhost:8000/remote/remote.js?callback=haddleData"></script>
加上callback參數,可以使得服務器端根據前端指定的方法名cb動態返回cb(data)
;而不是都寫死handleData(data)
;
【總結下jsonp】:
- 優點:
簡單,函數回調在本地處理; - 缺點:
1、安全性(存在注入漏洞,如CSRF,XSS);
2、如果出現錯誤,不會像http請求那樣有狀態碼;
3、只能使用get請求;
方案二——CORS(Cross-Origin Resource Sharing)
這是一個W3C標準(顯然比jsonp背景深厚許多),同樣需要瀏覽器和服務器同時支持,但是整個通信過程,都是瀏覽器自動完成,不需要用戶參與,就像平時寫Ajax一樣(如果使用的是jquery的話)。
下面是原生js實現CORS的代碼
function createCORSRequest(method,url){
var xhr=new XMLHttpRequest();
if("withCredentials" in xhr){
xhr.open(method,url,true);
}else if(typeof XDomainRequest != "undefined"){//IE10之前的版本使用XDmainRequest支持CORS
xhr=new XDomainRequest();
xhr.open(method,url);
}else{
xhr=null;
}
return xhr;
}
var request=createCORSRequest("get","待訪問的地址");
if(request){
request.onload=function(data){
//do sth
};
request.send();
}
適用場景:
承載的信息量大,get形式搞不定,需選用post傳輸。CORS支持所有類型的傳輸。
兼容性:
移動端全面支持(除opera mini),PC上IE8+。
CORS思想:
使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功還是失敗。(請求和響應都不包含cookie)
當然如果設置成下面這樣,所有的跨域都可以實現了,但這樣畢竟太不安全。
"Access-Control-Allow-Origin:*";//允許任何域向我們的服務器發送請求
一般情況下,瀏覽器發送一個額外的Origin頭部(由瀏覽器自動生成發送)
Origin:http://localhost:8080//本地網址
然后由服務器發送一個響應表頭:Access-Control-Allow-Origin,如果服務器接收該請求,返回值(只能是通配符或單域名。)就和請求值一樣。
Access-Control-Allow-Origin:http://localhost:8080
=>CORS方案的重點其實就在于服務器端的配置。
簡單請求
- 只使用 GET, HEAD 或者 POST 請求方法。
如果使用 POST 向服務器端傳送數據,則數據類型(Content-Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一種。
- 不會使用自定義請求頭(類似于 X-Modified 這種)。
HTTP頭部信息不超出以下{Accept,Accept-Language,Content-Language,Last-Event-ID,content-type(只限于上面提到的3種類型)}
對于簡單請求,瀏覽器直接發出CORS請求。瀏覽器會自動在頭信息(Request Headers)中,添加一個Origin 字段,來表明本次請求來自哪個域。
如果這個源不在許可范圍內,會報錯: No 'Access-Control-Allow-Origin' header is present on the requested resource.
如果Origin指定的域名在許可范圍內(必須是跨域了的),Response Headers中會多出幾個頭信息字段。
Access-Control-Allow-Credentials:true//值為true表示允許發送cookie
Access-Control-Allow-Methods:GET, POST, OPTIONS
Access-Control-Allow-Origin:http://localhost:8080
Access-Control-Max-Age:1728000
withCredentials屬性
因為CORS默認不發送cookie和http認證,如果要把Cookie發到服務器,就要指定Access-Control-Allow-Credentials:true;
另外AJAX中也要打開withCredentials屬性。
var xhr=new XMLHttpRequest();
xhr.withCredentials=true;
jquery ajax請求參數中加入
xhrFields: {
withCredentials: true
}
非簡單請求
除了上面說的簡單請求外都是非簡單請求,比如:請求方法是PUT
或DELETE,或者Content-Type字段的類型是application/json,又或者有自定義請求頭Access-Control-Request-Headers: X-Custom-Header。
比如,我添加自定義請求頭
xhr.setRequestHeader('Some-Custom-Response-Header', 'value');
就會發現連續向同一地址請求了兩次,而第一次請求什么值也沒拿到
第二次請求
這是因為瀏覽器發現,這是一個非簡單請求,就自動發出一個"預檢"請求,要求服務器確認可以這樣請求。"預檢"請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。"預檢"請求之后,瀏覽器球會進行正常CORS請求。
服務器端配置(rails為例)
1、可以參考這個方法:為 RESTful API 配置 CORS 實現跨域請求,寫的挺詳細的。然而對于rails并不算熟悉的我來說,有gem包,怎么可能放著不用呢~
2、這是Rack CORS 中間件的項目地址,按照文檔的說明1分鐘就可以基本搞定。
【問題:】
當我請求方式為put時,卻出現了404錯誤,是因為沒有為options提供路由么?
rails中使用CORS中的功能和細節還有很多,等到需要時再行挖掘吧,歡迎使用過的各位一起交流問題。
【參考】
http://mp.weixin.qq.com/s?__biz=MjM5MTA1MjAxMQ==&mid=402804565&idx=1&sn=415fc9eab30edcb66227ed5e099f9a66&scene=1&srcid=0413psVAfbsElTaWvrVas6xM#rd
http://www.ruanyifeng.com/blog/2016/04/cors.html
http://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery.html
http://www.cnblogs.com/know/archive/2011/10/09/2204005.html
http://blog.csdn.net/xiaoxian8023/article/details/27817861