同源策略是瀏覽器上爲安全性考慮實施的很是重要的安全策略。javascript
指的是從一個域上加載的腳本不容許訪問另一個域的文檔屬性。 舉個例子:好比一個惡意網站的頁面經過iframe嵌入了銀行的登陸頁面(兩者不一樣源), 若是沒有同源限制,惡意網頁上的javascript腳本就能夠在用戶登陸銀行的時候獲取用戶名和密碼。html
URL由協議、域名、端口和路徑組成,若是兩個URL的協議、域名和端口相同,則表示它們同源。java
在瀏覽器中,<script>、<img>、<iframe>、<link>等標籤均可以加載跨域資源,而不受同源限制,但瀏覽器會限制腳本中發起的跨域請求。好比,使用 XMLHttpRequest 對象和Fetch發起 HTTP 請求就必須遵照同源策略。Web 應用程序經過 XMLHttpRequest 對象或Fetch能且只能向同域名的資源發起 HTTP 請求,而不能向任何其它域名發起請求。不容許跨域訪問並不是是瀏覽器限制了發起跨站請求,而是跨站請求能夠正常發起,可是返回結果被瀏覽器攔截了。
python
首先在http:\\127.0.0.1:8000\test\下有以下返回字符串‘ok’的視圖:jquery
from django.http import HttpResponse
def test(request):
return HttpResponse('ok')
而後在http:\\127.0.0.1:8001\域下直接用ajax發起一個跨域請求:ajax
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test</title> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> </head> <body> <script> $.get('http://127.0.0.1:8000/test/', function (data) { alert(data) }) </script> </body> </html>
看效果(這裏我使用的是火狐瀏覽器,提示更直觀):django
上面有提到過<script>等標籤能夠加載跨域資源,咱們試一下直接讓script標籤的src屬性指向http:\\127.0.0.1:8000\test\:json
會發現script發出的請求成功拿到響應結果:跨域
可是控制檯有一個報錯:瀏覽器
這個問題顯然是返回的內容(也就是‘ok’)被瀏覽器直接當作腳本執行,但window中並無定義名字對應爲‘ok’的變量。
也就是說此方式請求在服務端返回的內容是能夠直接調用瀏覽器端js腳本的,此時咱們想,若是服務端返回一個方法調用,並這個方法在對應瀏覽器端js腳本中有存在,經過傳參的方式,是否是能夠間接拿到想要返回的內容。修改服務端和瀏覽器端:
from django.http import HttpResponse
def test(request):
return HttpResponse('func("ok")')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test</title> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> </head> <body> <script> function func(data){ console.info(data) } </script> <script src="http:\\127.0.0.1:8000\test\"></script> </body> </html>
此時會發現,瀏覽器端函數被成功調用,而且拿到了服務端返回的內容:
在這裏上面的script標籤是硬編碼,顯然也能夠經過dom操做動態建立標籤間接發起請求,下面要介紹的jquery的ajax就幫咱們簡化了這些操做。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test</title> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> </head> <body> <script> $.ajax({ url: "http://127.0.0.1:8000/test/", dataType: "jsonp", // 指定服務器返回的數據類型。 // jsonp: "funcKey", // 指定參數名稱。
// 若是指定,請求url會帶有一組參數:funcKey=func。
// 不指定默認爲callback=func,具體視服務端狀況而定。 jsonpCallback: "func", // 指定回調函數名稱。 success: function (data) { console.info(data); } }); </script> </body> </html>
由於jquery提供的jsonp的實現方式其實就是<script>腳本請求地址的方式同樣,只是ajax的jsonp對其作了封裝,可想而知,jsonp是不支持POST方式的。
咱們從新看下以前跨域失敗的錯誤:
其實瀏覽器已經很明顯的告訴了咱們緣由:服務端響應缺乏一個‘
from django.http import HttpResponse
def test(request):
response = HttpResponse('ok')
response["Access-Control-Allow-Origin"] = "*"
return response
修改客戶端發送普通ajax請求:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test</title> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> </head> <body> <script> $.get('http://127.0.0.1:8000/test/',function(data){ console.info(data) }) </script> </body> </html>
此時咱們會發現,加上該響應頭後客戶端就能夠像訪問本源地址訪問跨域地址:
Access-Control-Allow-Origin=* //容許任何域名訪問
Access-Control-Allow-Origin=http://127.0.0.1:8000 //僅容許指定域名訪問
該請求頭必須包含在全部合法的CORS響應頭中;不然,省略該響應頭會致使CORS請求失敗。該值要麼與請求頭Origin的值同樣(如上述例子),要麼設置成星號‘*’,以匹配任意Origin。若是你想任何站點都能獲取到你的數據,那麼就使用‘*’吧。可是,若是你想有效的控制,就將該值設置爲一個實際的值。
Access-Control-Allow-Credentials=true
默認狀況下,發送CORS請求,cookies是不會附帶發送的。可是,經過使用該響應頭就可讓cookies包含在CORS請求中。注意,該響應頭只有惟一的合法值true(所有小寫)。若是你不須要cookies值,就不要包含該響應頭了,而不是將該響應頭的值設置成false。該響應頭Access-Control-Allow-Credentials須要與XMLHttpRequest2對象的withCredentials屬性配合使用。當這兩個屬性同時設置爲true時,cookies才能附帶。例如,withCredentials被設置成true,可是響應頭中不包含 Access-Control-Allow-Credentials響應頭,那麼該請求就會失敗(反之亦然)。發送CORS請求時,最好不要攜帶cookies,除非你肯定你想在請求中包含cookie。
Access-Control-Expose-Headers
XMLHttpRequest2對象有一個getResponseHeader()方法,該方法返回一個特殊響應頭值。在一個CORS請求中,getResponseHeader()方法僅能獲取到簡單的響應頭,以下:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
若是你想客戶端可以獲取到其餘的頭部信息,你必須設置Access-Control-Expose-Headers響應頭。該響應頭的值能夠爲響應頭的名稱,多個時須要利用逗號隔開,這樣客戶端就能經過getResponseHeader方法獲取到了。
1、請求方式:HEAD、GET、POST 2、請求頭信息: Accept Accept-Language Content-Language Last-Event-ID Content-Type 對應的值是如下三個中的任意一個 application/x-www-form-urlencoded multipart/form-data text/plain
注意:同時知足以上兩個條件時,則是簡單請求,不然爲複雜請求。
簡單請求:一次請求。
複雜請求:兩次請求,在發送數據以前會先發一次請求用於作「預檢」,只有「預檢」經過後纔再發送一次請求用於數據傳輸。
- 請求方式:OPTIONS - 「預檢」其實作檢查,檢查若是經過則容許傳輸數據,檢查不經過則再也不發送真正想要發送的消息 - 如何「預檢」 => 若是請求是PUT等複雜請求,則服務端須要設置容許某請求,不然「預檢」不經過 Access-Control-Request-Method => 若是複雜請求設置了請求頭,則服務端須要設置容許某請求頭,不然「預檢」不經過 Access-Control-Request-Headers
a、支持跨域,複雜請求。
1、「預檢」請求時,在服務端設置容許的請求方式:Access-Control-Request-Method 2、「預檢」請求時,在服務端設置容許的響應頭:Access-Control-Request-Headers 3、「預檢」緩存時間,服務器設置響應頭:Access-Control-Max-Age
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CorsTest</title> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> </head> <body> <script> (function JqSendRequest(){ $.ajax({ url: "http://127.0.0.1:8001/test/", type: 'PUT', dataType: 'text', headers: {'k1': 'v1'}, success: function(data, statusText, xmlHttpRequest){ console.log(data); } }) })() </script> </body> </html>
from django.shortcuts import render, HttpResponse from django.views import View class TestView(View): def options(self, request, *args, **kwargs): response = HttpResponse() response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000" response['Access-Control-Allow-Headers'] = "k1,k2" response['Access-Control-Allow-Methods'] = "PUT,DELETE" response['Access-Control-Max-Age'] = 1 return response def put(self, request, *args, **kwargs): response = HttpResponse() response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000" return HttpResponse('ok')
b、跨域獲取自定義響應頭。
默認獲取到的全部響應頭只有基本信息,若是想要獲取自定義的響應頭,則須要再服務器端設置Access-Control-Expose-Headers。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CorsTest</title> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> </head> <body> <script> ( function JqSendRequest(){ $.ajax({ url: "http://127.0.0.1:8001/test/", type: 'PUT', dataType: 'text', headers: {'k1': 'v1'}, success: function(data, statusText, xmlHttpRequest){ console.log(data); // 獲取響應頭 console.log(xmlHttpRequest.getAllResponseHeaders()); } }) })() </script> </body> </html>
from django.shortcuts import render, HttpResponse from django.views import View class TestView(View): def options(self, request, *args, **kwargs): response = HttpResponse() response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000" response['Access-Control-Allow-Headers'] = "k1,k2" response['Access-Control-Allow-Methods'] = "PUT,DELETE" response['Access-Control-Max-Age'] = 1 return response def put(self, request, *args, **kwargs): response = HttpResponse('ok') response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000" response['testkey1'] = "testval1" response['testkey2'] = "testval2" response['Access-Control-Expose-Headers'] = "testkey1,testkey2" return response
c、跨域傳輸cookie。
在跨域請求中,默認狀況下,HTTP Authentication信息,Cookie頭以及用戶的SSL證書不管在預檢請求中或是在實際請求都是不會被髮送。 若是想要發送: 瀏覽器端:XMLHttpRequest的withCredentials爲true 服務器端:Access-Control-Allow-Credentials爲true 注意:服務器端響應的 Access-Control-Allow-Origin 不能是通配符 *
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CorsTest</title> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> </head> <body> <script> ( function JqSendRequest() { $.ajax({ url: "http://127.0.0.1:8001/test/", type: 'PUT', dataType: 'text', headers: {'k1': 'v1'}, xhrFields: {withCredentials: true}, success: function (data, statusText, xmlHttpRequest) { console.log(data); } }) })() </script> </body> </html>
from django.shortcuts import render, HttpResponse from django.views import View class TestView(View): def options(self, request, *args, **kwargs): response = HttpResponse() response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000" response['Access-Control-Allow-Headers'] = "k1,k2" response['Access-Control-Allow-Methods'] = "PUT,DELETE" response['Access-Control-Max-Age'] = 1 response['Access-Control-Allow-Credentials'] = 'true' return response def put(self, request, *args, **kwargs): response = HttpResponse('ok') response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000" response['Access-Control-Allow-Credentials'] = 'true' response['testkey1'] = "testval1" response['testkey2'] = "testval2" response['Access-Control-Expose-Headers'] = "testkey1,testkey2" response.set_cookie('my_cookie', 'cookie value') return response