瀏覽器的同源策略及跨域解決方案
同源策略
一個源的定義
如果兩個頁面的協議,埠(如果有指定)和域名都相同,則兩個頁面具有相同的源。
舉個例子:
下表給出了相對http://a.xyz.com/dir/page.html同源檢測的示例:
URL | 結果 | 原因 |
---|---|---|
http://a.xyz.com/dir2/other.html |
成功 | |
http://a.xyz.com/dir/inner/another.html |
成功 | |
https://a.xyz.com/secure.html |
失敗 | 不同協議 ( https和http ) |
http://a.xyz.com:81/dir/etc.html |
失敗 | 不同埠 ( 81和80) |
http://a.opq.com/dir/other.html |
失敗 | 不同域名 ( xyz和opq) |
同源策略是什麼
同源策略是瀏覽器的一個安全功能,不同源的客戶端指令碼在沒有明確授權的情況下,不能讀寫對方資源。所以xyz.com下的js指令碼採用ajax讀取abc.com裡面的檔案資料是會被拒絕的。
同源策略限制了從同一個源載入的文件或指令碼如何與來自另一個源的資源進行互動。這是一個用於隔離潛在惡意檔案的重要安全機制。
不受同源策略限制的
1. 頁面中的連結,重定向以及表單提交是不會受到同源策略限制的。
2. 跨域資源的引入是可以的。但是js不能讀寫載入的內容。如嵌入到頁面中的<script src="..."></script>,<img>,<link>,<iframe>等。
JSONP解決跨域問題
1、JSONP原理分析
利用 瀏覽器載入靜態資源的時候不限制跨域
<script src=""></script>
我們使用cdn方式引用的jQuery檔案也是跨域的,它就可以使用。
同樣是從其他的站點拿東西,script標籤就可以。那我們能不能利用這一點搞點事情呢?
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>xyz</title> </head> <body> <button id="b1">點我</button> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script> <script> function rion() { console.log("選我不後悔!"); } </script> <script src="http://127.0.0.1:8002/abc/"></script> </body> </html>
後端返回響應
def abc(request): return HttpResponse("rion()")
重新整理頁面,可以看到下面的結果。
這樣就利用了瀏覽器載入靜態資源的時候不限制跨域的特性,實現了跨域請求拿資料。
2.jQuery中的getJSON方法
jQuery中封裝了專門的方法實現jsonp。
示例:
點選b1按鈕傳送jsonp請求到後端,然後後端返回資料。
html :
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <button id="b1">點我傳送請求</button> <script src="jquery.min.js"></script> <script> // 點選b1按鈕傳送jsonp請求 $('#b1').click(function () { // 使用jquery提供的getJSON方法請求獲取資料, // callback=? 是固定格式,會建立一個隨機的字串作為callback的value。 $.getJSON('http://127.0.0.1:8000/index/?callback=?',function (data) { console.log(data) }) }) </script> </body> </html>
後端專案函式:
def index(request): data = {"name": "zwq", "age": 18} func_name = request.GET.get('callback') print(func_name) # jQuery3310589826002995957_1547112594072 return HttpResponse('{}({})'.format(func_name,json.dumps(data)))
但JSONP有一定缺陷:
1.前後端都要支援
2.只能發GET請求
CORS解決跨域問題
CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源伺服器發出XMLHttpRequest請求,從而解決AJAX只能同源使用的限制。
CORS需要瀏覽器和伺服器同時支援。目前基本上主流的瀏覽器都支援CORS。所以只要後端服務支援CORS,就能夠實現跨域。
1.簡單請求和非簡單請求介紹
瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
一個請求需要同時滿足以下兩大條件才屬於簡單請求。
(1) 請求方法是以下三種方法之一: HEAD GET POST (2)HTTP的頭資訊不超出以下幾種欄位: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
2.簡單請求的處理方式
在跨域場景下,當瀏覽器傳送簡單請求時,瀏覽器會自動在請求頭中新增表明請求來源的 Origin 欄位。
我們的後端程式只需要在返回的響應頭中加上 Access-Control-Allow-Origin 欄位,並且把該欄位的值設定為 跨域請求的來源地址或簡單的設定為 * 就可以了。
例如:我們可以在Django中介軟體中的process_response方法來給相應物件新增該欄位。
from django.utils.deprecation import MiddlewareMixin class CORSMiddleware(MiddlewareMixin): # 處理簡單請求的跨域請求 def process_response(self, request, response): # 告訴瀏覽器來自這個源的請求是我允許的跨域請求 # response['Access-Control-Allow-Origin'] = 'http://localhost:63342' # 給響應頭加上 Access-Control-Allow-Origin 欄位 並簡單的設定為 *,表示所有跨域簡單請求都放行 response['Access-Control-Allow-Origin'] = '*' return response
非簡單請求的處理方式
我們開發中常用到的那些請求方法是PUT或DELETE,或者Content-Type欄位的型別是application/json的都是非簡單請求。
對於非簡單請求,瀏覽器通常都會在請求之前傳送一次 OPTIONS 預檢 請求。該請求會像後端服務詢問是否允許從當前源傳送請求並且詢問允許的 請求方法 和 請求頭欄位。
舉個例子:
我們前端使用axios向後端傳送PUT請求,結果:
看看傳送的具體請求:
解決辦法也很簡單,我們可以在後端簡單的給響應物件新增上 常用請求方法(PUT、DELETE)的支援就可以了。
在上面Django的中介軟體中新增如下程式碼:
class CORSMiddleware(MiddlewareMixin): """對所有跨域請求做放行處理""" def process_response(self, request, response): # 所有跨域簡單請求都放行 response['Access-Control-Allow-Origin'] = '*' if request.method == 'OPTIONS': # 告訴瀏覽器你發的非簡單請求(修改了Content-type)我允許了 response['Access-Control-Allow-Headers'] = 'Content-type' # 告訴瀏覽器 你傳送PUT請求我也支援 response['Access-Control-Allow-Methods'] = 'PUT, DELETE' return response
使用django-cors-headers
我們這個中介軟體確實能解決目前的CORS跨域問題,但是我們的方法肯定是不夠嚴謹的,django有一個第三方包django-cors-headers。
安裝:
pip install django-cors-headers
註冊APP:
INSTALLED_APPS = [ ... 'app01.apps.App01Config', 'corsheaders', # 將 corsheaders 這個APP註冊 ]
新增中介軟體:
必須放在最前面,因為要先解決跨域的問題。只有允許跨域請求,後續的中介軟體才會正常執行。
MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # 新增中介軟體 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
配置
可以選擇不限制跨域訪問
CORS_ORIGIN_ALLOW_ALL = True
或者可以選擇設定允許訪問的白名單
CORS_ORIGIN_ALLOW_ALL = False CORS_ORIGIN_WHITELIST = ( # '<YOUR_DOMAIN>[:PORT]', '127.0.0.1:8080' )
更多詳細配置詳細請檢視django-cors-headers專案
response