Luffy - 解決跨域問題

躍上青空發表於2020-12-24

Luffy - 解決跨域問題

一:跨域請求

0. 跨域問題的出現

問題出現:前後端來自同一個IP的不同埠
在這裡插入圖片描述

一種奇葩的解決方法:開發的時候前後端分離,部署的時候不分離
在這裡插入圖片描述

1.同源策略

① 簡介

同源策略,是瀏覽器為了保護使用者資訊保安的一種安全機制

所謂的同源就是指代通訊的兩個地址(例如服務端介面地址與瀏覽器客戶端頁面地址)之間比較,是否協議、域名(IP)和埠相同

不同源的客戶端指令碼[javascript]在沒有得到服務端的明確授權的情況下,瀏覽器會拒絕顯示服務端資訊提供給前端ajax/axios

前端地址:http://www.4399.com/index.html是否同源原因
http://www.4399.com/user/login.html協議、域名、埠相同
http://www.4399.com/about.html協議、域名、埠相同
https://www.4399.com/user/login.html協議不同 ( https和http )
http:/www.4399.com:5000/user/login.html埠 不同( 5000和80)
http://doc.4399.com/user/login.html域名不同 ( doc和www )
http://www.4399.top/user/login.html域名不同 ( com和top )

2. CORS(跨域資源共享)簡介

CORS是一個W3C標準,全稱是"跨域資源共享",它允許瀏覽器向跨源的後端伺服器發出ajax請求,從而克服了AJAX只能同源使用的限制。
實現CORS主要依靠後端伺服器中響應資料中設定響應頭資訊返回的

CORS需要瀏覽器和伺服器同時支援。目前,所有瀏覽器都支援該功能,IE瀏覽器不能低於IE10

整個CORS通訊過程,都是瀏覽器自動完成,不需要使用者參與。對於開發者來說,CORS通訊與同源的AJAX通訊沒有差別,程式碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動新增一些附加的頭資訊,有時還會多出一次附加的請求,但使用者不會有感覺。

因此,實現CORS通訊的關鍵是伺服器。只要伺服器實現了CORS介面,就可以跨源通訊

容易混淆的點:

  • CORS:跨域資源共享
  • CSRF:跨站請求偽造
  • XSS:跨站指令碼攻擊

3. CORS基本流程

瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request

  • 瀏覽器發出CORS簡單請求,只需要在頭資訊之中增加一個Origin欄位
  • 瀏覽器發出CORS非簡單請求,會在正式通訊之前,增加一次HTTP查詢請求,稱為”預檢”請求(preflight
  • 覽器先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP動詞和頭資訊欄位
  • 只有得到肯定答覆,瀏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。

4. 解決跨域問題的3種方法

  • 前端解決(通過代理解決)
  • 自己解決(自己寫程式碼)
  • 藉助第三方模組(django-cors-headers

5. CORS兩種請求詳解

只要同時滿足以下兩大條件,就屬於簡單請求

  • ① 請求方法是以下三種方法之一

    • HEAD
    • GET
    • POST
  • HTTP的頭資訊不超出以下幾種欄位

    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限於三個值application/x-www-form-urlencodedmultipart/form-datatext/plain
  • 凡是不同時滿足上面兩個條件,就屬於非簡單請求

6. 瀏覽器對這兩種請求的處理,是不一樣的

簡單請求和非簡單請求的區別

  • 簡單請求:發1次請求
  • 非簡單請求:發2次請求,在傳送資料之前會先發1次請求用於做“預檢”看後端是否允許,只有“預檢”通過後才再傳送1次請求用於資料傳輸。
    請求方式:OPTIONS

預檢:

  • 檢查如果通過則允許傳輸資料,檢查不通過則不再傳送真正想要傳送的訊息

如何預檢:

  • 如果複雜請求是PUT等請求,則服務端需要設定允許某請求,否則“預檢”不通過
    Access-Control-Request-Method
  • 如果複雜請求設定了請求頭,則服務端需要設定允許某請求頭,否則“預檢”不通過
    Access-Control-Request-Headers

二:解決跨域問題(服務端)

【簡單請求】

1. 原Django專案:apps/user/views.py

from django.http import JsonResponse

def test(request):
    obj = JsonResponse({'name': 'Darker', 'age': '18'})
    # 值針對簡單請求
    obj['Access-Control-Allow-Origin'] = '*'    # 允許所有IP訪問
    return obj

2. 原Django專案:apps/user/urls.py

from django.urls import path
from user import views

urlpatterns = [
    path('test/', views.test),
]

3. 原Django專案:dev.py中註釋掉CSRF

4. 再建立1個Django專案(用另外的埠)

templates中建立index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<button id="btn">點我</button>

</body>
<script>
    $('#btn').click(function () {
        $.ajax({
            url: 'http://127.0.0.1:8000/user/test/',
            method: 'get',
            success: function (data) {
                console.log(data)
            }
        })
    })
</script>
</html>

views.py

from django.shortcuts import render

def index(request):
    return render(request, 'index.html')

urls.py

from django.urls import path
from app01 import views

urlpatterns = [
    path('test/', views.index),
]

在這裡插入圖片描述

【非簡單請求】

原Django專案apps/user/views.py

from django.http import JsonResponse

def test(request):
    obj = JsonResponse({'name': 'Darker', 'age': '18'})
    if request.method == 'OPTIONS':
        obj['Access-Control-Allow-Headers'] = 'Content-Type,authorization'  # 或者填寫 *
    obj['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8002'    # 8002埠是當前專案的
    return obj

新Django專案templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<button id="btn">點我</button>

</body>
<script>
    $('#btn').click(function () {
        let obj = {name: 'Darker'}
        $.ajax({
            url: 'http://127.0.0.1:8000/user/test/',
            method: 'post',
            contentType: 'application/json',
            headers: {authorization: 'Darker'},
            data: JSON.stringify(obj),
            success: function (data) {
                console.log(data)
            }
        })
    })
</script>
</html>

【中介軟體處理】

① 在原Django專案的根路徑建立mymiddle.py - 自定義中介軟體

from django.utils.deprecation import MiddlewareMixin

class CoreMiddle(MiddlewareMixin):
    def process_response(self, request, response):
        if request.method == 'OPTIONS':
            response['Access-Control-Allow-Headers'] = 'Content-Type, authorization'    # 如果是 * 就代表全部IP都可以訪問
        response['Access-Control-Allow-Origin'] = '*'
        return response

② 在原Django專案的dev.py的中介軟體中新增自定義中介軟體

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'mymiddle.CoreMiddle',    # 這一句
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

③ 原Django專案apps/user/views中替換成如下程式碼

from django.http import JsonResponse

def test(request):
    return JsonResponse({'name': 'Darker', 'age': '18'})

三:解決跨域問題(第三方)

1. 後端配置

① 後端安裝跨域模組

pip install django-cors-headers

② 到dev.py中進行註冊

INSTALLED_APPS = (
    ...
    'corsheaders'
)

③ 到dev.py中新增中介軟體

MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',    # 這個是原本就存在的
    ...
]

④ 到dev.py中新增如下程式碼

CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
# CORS_ORIGIN_WHITELIST = (
#     'http://127.0.0.1:8080',
# )
CORS_ALLOWED_ORIGINS_REGEXES=[
    r'^http://.*?$',
]
# CORS_ORIGIN_REGEXES_WHITELIST = (
#         r'^http://.*?$',
# )
CORS_ALLOW_METHODS = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
    'VIEW',
)

CORS_ALLOW_HEADERS = (
    'XMLHttpRequest',
    'X_FILENAME',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
    'Pragma',
)

⑤ 設定dev.pyALLOWED_HOSTS

ALLOWED_HOSTS = ['*']

2. 後端測試

① (測試 - 後臺)到apps/user/views中替換成如下程式碼

from django.http import JsonResponse


def test(request):
    return JsonResponse({'name': 'Darker', 'age': '18'})

② (測試 - 後臺)到apps/user/urls中替換成如下程式碼

from django.urls import path
from user import views

urlpatterns = [
    path('test/', views.test),
]

③ 啟動專案

python manage.py runserver 127.0.0.1:8000

④ 訪問測試

在這裡插入圖片描述

3. 前端測試

①(測試 - 前臺)App.vue 中換成如下程式碼

<template>
  <div id="app">
    <router-view/>
    {{name}}
  </div>
</template>

<script>
export default {
  data () {
    return {
      name: []
    }
  },
  mounted () {
    this.$axios.get(this.$settings.base_url + '/user/test/').then(res => {
      this.name = res.data
    })
  }
}
</script>

② 啟動專案

npm run serve

4. 測試效果

在這裡插入圖片描述

四:解決跨域問題(前端)

1. 前端App.vue

<template>
  <div id="home">
    <h1>我是主頁</h1>
    <h2>{{info}}</h2>
  </div>
</template>

<script>
export default {
  name: 'Home',
  data () {
    return {
      info: []
    }
  },
  mounted () {
    this.$axios.get('/moreClassicList?sortId=1&showType=3').then(res => {
      console.log(res.data)
    })
  }
}
</script>

<style scoped>

</style>

2. 前端專案根路徑的vue.config.js

const webpack = require("webpack");

module.exports = {
  configureWebpack: {
    plugins: [
      new webpack.ProvidePlugin({
        $: "jquery",
        jQuery: "jquery",
        "window.jQuery": "jquery",
        "window.$": "jquery",
        Popper: ["popper.js", "default"]
      })
    ]
  },
  devServer: {
    proxy: {
      '/ajax': {
        target: 'https://m.maoyan.com/',
        changeOrigin: true
      },
      '/user': {
        target: 'http://127.0.0.1:8000',
        changeOrigin: true
      }
    }
  }
};

相關文章