1. 程式人生 > >使用cors完成跨域請求處理

使用cors完成跨域請求處理

# 跨域的含義 ## 同源策略以及其限制內容 同源策略是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到XSS、CSFR等攻擊。所謂**同源是指"協議+域名+埠"**三者相同,即便兩個不同的域名指向同一個ip地址,也非同源。 ![img](https://image.fundebug.com/2019-01-26-01.png) **同源策略限制內容有:** - Cookie、LocalStorage、IndexedDB 等儲存性內容 - DOM 節點 - AJAX 請求不能傳送 但是有三個標籤允許跨域載入資源: - ` ` - `` - `` ## 常見跨域場景 **當協議、子域名、主域名、埠號中任意一個不相同時,都算作不同域**。不同域之間相互請求資源,就算作“跨域”。常見跨域場景如下圖所示: ![img](https://image.fundebug.com/2019-01-26-02.png) 特別說明兩點: **第一:如果是協議和埠造成的跨域問題“前端”是處理不了的。** **第二:在跨域問題上,僅僅是通過“URL的首部”來識別而不會根據域名對應的IP地址是否相同來判斷。“URL的首部”可以理解為“協議, 域名和埠必須匹配”**。 這裡你或許有個疑問:**請求跨域了,那麼請求到底發出去沒有?** **跨域並不是請求發不出去,請求能發出去,服務端能收到請求並正常返回響應,只是響應被瀏覽器攔截了**。你可能會疑問明明通過表單的方式可以發起跨域請求,為什麼 Ajax 就不行?因為歸根結底,跨域是為了阻止使用者讀取到另一個域名下的內容,Ajax 可以獲取響應,瀏覽器認為這不安全,所以攔截了響應。但是表單並不會獲取新的內容,所以可以發起跨域請求。同時也說明了**跨域並不能完全阻止 CSRF**,因為請求畢竟是發出去了。 # 實現跨域的方式之一:CORS - CORS 全稱是**跨域資源共享(Cross-Origin Resource Sharing)**,是一種 AJAX 跨域請求資源的方式,支援現代瀏覽器,IE支援10以上。 - CORS與JSONP的使用目的相同,但是比JSONP更強大。**JSONP只支援GET請求,CORS支援所有型別的HTTP請求**。JSONP的優勢在於支援老式瀏覽器,以及可以向不支援CORS的網站請求資料。 - 瀏覽器會自動進行 CORS 通訊,實現 CORS 通訊的關鍵是後端。只要後端實現了 CORS,就實現了跨域。 - **服務端設定 Access-Control-Allow-Origin 就可以開啟 CORS**。 該屬性表示**哪些域名可以訪問資源,如果設定萬用字元(*)則表示所有網站都可以訪問資源**。 - `response = HttpResponse('向非同源的網址返回其請求所對應的響應資料')` - `response["Access-Control-Allow-origin"] = "*"` ## 前端傳送請求型別 雖然設定 CORS 和前端沒什麼關係,但是通過這種方式解決跨域問題的話,會在傳送請求時出現兩種情況,分別為**簡單請求**和**複雜請求**。 ### 簡單請求 只要同時滿足以下兩大條件,就屬於簡單請求 條件1:使用下列方法之一: - GET - HEAD - POST 條件2:Content-Type 的值僅限於下列三者之一: - text/plain - multipart/form-data - application/x-www-form-urlencoded 請求中的任意 XMLHttpRequestUpload 物件均沒有註冊任何事件監聽器; XMLHttpRequestUpload 物件可以使用 XMLHttpRequest.upload 屬性訪問。 ### 複雜請求 不符合以上條件的請求就是複雜請求。 複雜請求的CORS請求,會在正式通訊之前,增加一次HTTP查詢請求,稱為"預檢"請求,該請求是 option 方法的,通過該請求來知道服務端是否允許跨域請求。 我們用`PUT`向後臺請求時,屬於複雜請求,後臺需做如下配置: ```javascript // 允許哪個方法訪問我 res.setHeader('Access-Control-Allow-Methods', 'PUT') // 預檢的存活時間 res.setHeader('Access-Control-Max-Age', 6) // OPTIONS請求不做任何處理 if (req.method === 'OPTIONS') { res.end() } // 定義後臺返回的內容 app.put('/getData', function(req, res) { console.log(req.headers) res.end('****') }) ``` 接下來我們看下一個完整複雜請求的例子,並且介紹下CORS請求相關的欄位 ```javascript // index.html let xhr = new XMLHttpRequest() document.cookie = 'name=xiamen' // cookie不能跨域 xhr.withCredentials = true // 前端設定是否帶cookie xhr.open('PUT', 'http://localhost:4000/getData', true) xhr.setRequestHeader('name', 'xiamen') xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { console.log(xhr.response) //得到響應頭,後臺需設定Access-Control-Expose-Headers console.log(xhr.getResponseHeader('name')) } } } xhr.send() //server1.js let express = require('express'); let app = express(); app.use(express.static(__dirname)); app.listen(3000); //server2.js let express = require('express') let app = express() let whitList = ['http://localhost:3000'] //設定白名單 app.use(function(req, res, next) { let origin = req.headers.origin if (whitList.includes(origin)) { // 設定哪個源可以訪問我 res.setHeader('Access-Control-Allow-Origin', origin) // 允許攜帶哪個頭訪問我 res.setHeader('Access-Control-Allow-Headers', 'name') // 允許哪個方法訪問我 res.setHeader('Access-Control-Allow-Methods', 'PUT') // 允許攜帶cookie res.setHeader('Access-Control-Allow-Credentials', true) // 預檢的存活時間 res.setHeader('Access-Control-Max-Age', 6) // 允許返回的頭 res.setHeader('Access-Control-Expose-Headers', 'name') if (req.method === 'OPTIONS') { res.end() // OPTIONS請求不做任何處理 } } next() }) app.put('/getData', function(req, res) { console.log(req.headers) res.setHeader('name', 'jw') //返回一個響應頭,後臺需設定 res.end('****') }) app.get('/getData', function(req, res) { console.log(req.headers) res.end('****') }) app.use(express.static(__dirname)) app.listen(4000) ``` 上述程式碼由`http://localhost:3000/index.html`向`http://localhost:4000/`跨域請求,正如我們上面所說的,後端是實現 CORS 通訊的關鍵。 # 應用django-cors-headers ## drf後端+vue前端的專案 在做以drf框架作為後端支撐以及以vue前端框架作為頁面展示的前後端分離的web專案中,前端會向後端請求資料,也會涉及到非同源跨域的問題,以下操作將做詳細處理 ```bash # vue前端域名 www.aliang.cn # drf後端域名 api.aliang.com ``` ### 修改vue專案的配置資訊 - 通過瀏覽器訪問前端vue專案,會出現nginx的歡迎頁面,主要因為我們當前操作中已經有一個nginx監聽了80埠,所以訪問`www.aliang.cn`網址時,會自動被轉發到127.0.0.1本機,因為沒有網址預設埠是80埠,所以被nginx進行處理了當前請求,因此我們暫時先把nginx關閉。 ```bash # 查詢nginx的程序 ps -ef|grep nginx # 關閉程序 sudo kill -9 nginx程序號 ``` - 修改vue專案的配置檔案config/index.js ```javascript host: 'www.aliang.cn', // can be overwritten by process.env.HOST port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined autoOpenBrowser: true, ``` - 儲存修改資訊,並重啟專案 - 通過瀏覽器訪問drf專案,會出現以下錯誤資訊 - `You may need to add 'api.aliang.com' to ALLOWED_HOSTS` - 可以通過設定drf專案中的配置檔案,`settings/dev.py的ALLOWED_HOSTS`,設定允許訪問 ```python # 設定哪些客戶端可以通過地址訪問到後端 ALLOWED_HOSTS = [ 'api.aliang.cn', ] ``` - 現在,前端與後端分處不同的域名,我們需要為後端新增跨域訪問的支援否則前端使用axios則無法請求到後端提供的api資料,故使用CORS來解決後端對跨域訪問的支援。 ### 使用django-cors-headers擴充套件 - 安裝 ```python pip install django-cors-headers -i https://pypi.douban.com/simple/ ``` - drf後端專案配置檔案中新增應用 ```python INSTALLED_APPS = ( ... 'corsheaders', ... ) ``` - drf後端專案配置檔案中設定中介軟體【必須寫在第一個位置】 ```python MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', ... ] ``` - drf後端專案配置檔案中新增白名單 ```python # CORS組的配置資訊 CORS_ORIGIN_WHITELIST = ( 'www.aliang.cn:8080', ) CORS_ALLOW_CREDENTIALS = True # 允許ajax跨域請求時攜帶cookie ``` 完成了上面的步驟,我們就可以通過後端提供資料給前端使用ajax訪問了。 - 前端使用 axios就可以訪問到後端提供給的資料介面,但是如果要附帶cookie資訊,前端還要設定一下。 - 需要注意的是,如果要傳送Cookie,`Access-Control-Allow-Origin`就不能設為星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用伺服器域名設定的Cookie才會上傳,其他域名的Cookie並不會上傳,且(跨源)原網頁程式碼中的`document.cookie`也無法讀取伺服器域名下的Cookie。 - 前端引入axios外掛並配置允許axios傳送cookie資訊[axios本身也不允許ajax傳送cookie到後端] ``` npm i axios -S ``` - 在main.js中引用 axios外掛 ```javascript import axios from 'axios'; // 從node_modules目錄中匯入包 // 允許ajax傳送請求時附帶cookie axios.defaults.withCredentials = true; Vue.prototype.$axios = axios; // 把物件掛載vue中 ``` >
如果你拷貝前端vue-cli專案到咱們指定目錄下,如果執行起來有問題,一些不知名的錯誤,那麼就刪除node_modules檔案件,然後在專案目錄下執行`npm install` 這個指令,重新按照package.json資料夾中的包進行node_modules裡面包