什么是同源策略?
同源策略(Same origin policy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。
注意:跨域請求被拒絕,其實是瀏覽器已經拿到了不同源服務器響應的數據,瀏覽器對非同源請求返回的結果做了攔截,而不是服務器拒絕瀏覽器的請求。
ajax跨域
<script> $("button").click(function(){ $.ajax({ url:"http://127.0.0.1:7766/order/", //假設網頁的服務時http://127.0.0.1:8000,此時ajax去請求7766端口的服務 type:"POST", success:function(data){ alert(123); alert(data) } }) }) </script>
上面這種請求方式設計跨域請求,會報如下錯誤:
已攔截跨源請求:同源策略禁止讀取位於 http://127.0.0.1:7766/order/ 的遠程資源。(原因:CORS 頭缺少 'Access-Control-Allow-Origin')。
解決辦法下文會深入討論。
什么是CORS?
CORS需要瀏覽器和服務器同時支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低於IE10。
整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。
因此,實現CORS通信的關鍵是服務器。只要服務器實現了CORS接口,就可以跨源通信。
CORS兩種請求
瀏覽器將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
凡是不同時滿足上面兩個條件,就屬於非簡單請求。
瀏覽器對這兩種請求的處理,是不一樣的。
* 簡單請求和非簡單請求的區別? 簡單請求:一次請求 非簡單請求:兩次請求,在發送數據之前會先發一次請求用於做“預檢”,只有“預檢”通過后才再發送一次請求用於數據傳輸。
* 關於“預檢” - 請求方式:OPTIONS - “預檢”其實做檢查,檢查如果通過則允許傳輸數據,檢查不通過則不再發送真正想要發送的消息 - 如何“預檢” => 如果復雜請求是PUT等請求,則服務端需要設置允許某請求,否則“預檢”不通過 Access-Control-Request-Method => 如果復雜請求設置了請求頭,則服務端需要設置允許某請求頭,否則“預檢”不通過 Access-Control-Request-Headers
支持跨域,簡單請求
服務器設置響應頭即可:Access-Control-Allow-Origin = '域名' 或 '*'
支持跨域,復雜請求
由於復雜請求時,首先會發送“預檢”請求,如果“預檢”成功,則發送真實數據。
- “預檢”請求時,允許請求方式則需服務器設置響應頭:Access-Control-Request-Method
- “預檢”請求時,允許請求頭則需服務器設置響應頭:Access-Control-Request-Headers
復雜請求下,django的響應頭設置,如下:
在設置了rest_framework情況下,必須要有的是options請求的方法
from django.shortcuts import render, HttpResponse from rest_framework.views import APIView import json class Order(APIView): def options(self, request, *args, **kwargs): response = HttpResponse("ok") response['Access-Control-Allow-Origin'] = '*' # 允許所有的域名地址 response["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS,PATCH,PUT" # 允許的請求方式 response["Access-Control-Allow-Headers"] = "Content-Type" # 允許的headers return response
因為復雜請求都會請求多次,第一次一定是OPTIONS請求,也就是預檢。如果預檢通過就會攜帶數據進行再次請求,這時需要根據請求方式增加對應的處理方法。
比如,預檢通過之后進行GET請求:
from django.shortcuts import render, HttpResponse from rest_framework.views import APIView import json class Order(APIView): def options(self, request, *args, **kwargs): response = HttpResponse("ok") response['Access-Control-Allow-Origin'] = '*' # 允許所有的域名地址 response["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS,PATCH,PUT" # 允許的請求方式 response["Access-Control-Allow-Headers"] = "Content-Type" # 允許的headers return response def get(self, request, *args, **kwargs): response = HttpResponse("ok") response['Access-Control-Allow-Origin'] = '*' # 允許所有的域名地址 response["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS,PATCH,PUT" # 允許的請求方式 response["Access-Control-Allow-Headers"] = "Content-Type" # 允許的headers return response
ajax自定義headers
<script> $("button").click(function(){ $.ajax({ url:"http://127.0.0.1:7766/order/", //假設網頁的服務時http://127.0.0.1:8000,此時ajax去請求7766端口的服務 type:"POST",
headers: {k1: "v1"}, // 自定義headers,這里設置k1,服務端就要允許k1
contentType: "application/json" success:function(data){ alert(123); alert(data) } }) }) </script>
服務端django設置headers
from django.shortcuts import render, HttpResponse from rest_framework.views import APIView import json class Order(APIView): def options(self, request, *args, **kwargs): response = HttpResponse("ok") response['Access-Control-Allow-Origin'] = '*' # 允許所有的域名地址 response["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS,PATCH,PUT" # 允許的請求方式 response["Access-Control-Allow-Headers"] = "Content-Type, k1" # 允許的headers,添加k1 return response def get(self, request, *args, **kwargs): response = HttpResponse("ok") response['Access-Control-Allow-Origin'] = '*' # 允許所有的域名地址 response["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS,PATCH,PUT" # 允許的請求方式 response["Access-Control-Allow-Headers"] = "Content-Type, k1" # 允許的headers,添加k1 return response
axios跨域
import axios from 'axios' import { Message, MessageBox } from 'element-ui' import store from '../store' import { getToken } from '@/utils/auth' axios.defaults.baseURL= '/api' // 創建axios實例 const service = axios.create({ baseURL: process.env.BASE_API, // api的base_url, base_url="http://127.0.0.1:80001" timeout: 15000 // 請求超時時間 }) // request攔截器 service.interceptors.request.use(config => { if (store.getters.token) { config.headers['Authorization'] = getToken() // 讓每個請求攜帶自定義token 請根據實際情況自行修改 } return config }, error => { // Do something with request error console.log(error) // for debug Promise.reject(error) }) // respone攔截器 service.interceptors.response.use( response => { /** * code為非200是拋錯 可結合自己業務進行修改 */ const res = response.data if (res.code !== 200) { Message({ message: res.message, type: 'error', duration: 3 * 1000 }) // 401:未登錄; if (res.code === 401) { MessageBox.confirm('你已被登出,可以取消繼續留在該頁面,或者重新登錄', '確定登出', { confirmButtonText: '重新登錄', cancelButtonText: '取消', type: 'warning' }).then(() => { store.dispatch('FedLogOut').then(() => { location.reload()// 為了重新實例化vue-router對象 避免bug }) }) } return Promise.reject('error') } else { return response.data } }, error => { console.log('err' + error)// for debug Message({ message: error.message, type: 'error', duration: 3 * 1000 }) return Promise.reject(error) } ) export default service
axios在前端服務器如果沒有設置特殊全局headers,對應的后端服務器設置headers同上文。
結束!