什麼是同源政策?
同源政策(Same origin policy)是一種約定,它是浏覽器最核心也最基本的安全功能,如果缺少了同源政策,則浏覽器的正常功能可能都會受到影響。可以說Web是建構在同源政策基礎之上的,浏覽器隻是針對同源政策的一種實作。
同源政策,它是由Netscape提出的一個著名的安全政策。現在所有支援JavaScript 的浏覽器都會使用這個政策。所謂同源是指,域名,協定,端口相同。當一個浏覽器的兩個tab頁中分别打開來 百度和谷歌的頁面當浏覽器的百度tab頁執行一個腳本的時候會檢查這個腳本是屬于哪個頁面的,即檢查是否同源,隻有和百度同源的腳本才會被執行。如果非同源,那麼在請求資料時,浏覽器會在控制台中報一個異常,提示拒絕通路。
注意:跨域請求被拒絕,其實是浏覽器已經拿到了不同源伺服器響應的資料,浏覽器對非同源請求傳回的結果做了攔截,而不是伺服器拒絕浏覽器的請求。
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同上文。
結束!