ajax、axios請求之同源策略與CORS


什么是同源策略?

同源策略(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同上文。

結束!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM