CORS跨域問題梳理

Sherioc發表於2024-07-16

什麼是跨域

瀏覽器的同源策略:瀏覽器為確保資源安全,而遵循的一種策略,該策略對訪問資源進行了一些限制
https://www.w3.org/Security/wiki/Same_Origin_Policy
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS

1、發生跨域後會出現的問題:

1、限制DOM訪問
<!-- <iframe id="framePage" src="./demo.html"></iframe> -->
<iframe id="framePage" src="https://www.baidu.com"></iframe>

<script type="text/javascript" >
  function showDOM(){
    const framePage = document.getElementById('framePage')
    console.log(framePage.contentWindow.document) //同源的可以獲取,非同源的無法獲取
  }
</script>
2、限制cookie訪問(實際上dom無法訪問,cookie也自然無法訪問了)
<iframe id="baidu" src="http://www.baidu.com" width="500" height="300"></iframe>

<script type="text/javascript" >
  // 訪問的是當前源的cookie,並不是baidu的cookie
  console.log(document.cookie)
</script>
3、限制Ajax獲取資料(請求可以發出,但是無法獲取源B的響應資料)
const url = 'https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc'
let result = await fetch(url)
let data = await result.json();
console.log(data)

2、注意點

1、跨域限制僅存在瀏覽器端,服務端不存在跨域限制
2、即使跨域了,Ajax 請求也可以正常發出,但響應資料不會交給開發者
image

3、<link><script>、CORS跨域問題梳理...... 這些標籤發出的請求也可能跨域,只不過瀏覽器對標籤跨域不做嚴格限制,對開發幾乎無影響

跨域的解決方案

1、解決方案一:CORS

CORS 全稱:Cross-Origin Resource Sharing(跨域資源共享),是用於控制瀏覽器校驗跨域請求的一套規範,伺服器依照 CORS 規範,新增特定響應頭來控制瀏覽器校驗,大致規則如下:
● 伺服器明確表示拒絕跨域請求,或沒有表示,則瀏覽器校驗不透過。
● 伺服器明確表示允許跨域請求,則瀏覽器校驗透過

(1)處理簡單請求

簡單請求

  • 請求方法為:GET、HEAD、POST
  • 請求頭的Content-Type的值只能是以下三種:
    ● text/plain
    ● multipart/form-data
    ● application/x-www-form-urlencoded

伺服器響應時候,新增Access-Control-Allow-Origin響應頭,宣告允許某個源發起跨域請求,瀏覽器校驗透過
image

服務端核心程式碼(以express框架為例)
// 處理跨域中介軟體
function corsMiddleWare(req,res,next){
  // 允許 http://127.0.0.1:5500 這個源發起跨域請求
  // res.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:5500')
  
  // 允許所有源發起跨域請求
  res.setHeader('Access-Control-Allow-Origin','*')
  next()
}

// 配置路由並使用中介軟體
app.get('/',corsMiddleWare,(req,res)=>{
  res.send('hello!')
})

(2)處理複雜請求

複雜請求:不是簡單請求的請求就是複雜請求,比如application/json
複雜請求會自動傳送預檢請求

image

解決方案

第一步:伺服器先透過瀏覽器的預檢請求,伺服器需要返回如下響應頭:

image

第二步:處理實際的跨域請求(與處理簡單請求跨域的方式相同)
image

服務端核心程式碼
// 處理預檢請求
app.options('/students', (req, res) => {
  // 設定允許的跨域請求源
  res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
  // 設定允許的請求方法
  res.setHeader('Access-Control-Allow-Methods', 'GET')
  // 設定允許的請求頭
  res.setHeader('Access-Control-Allow-Headers', 'school')
  // 設定預檢請求的快取時間(可選)
  res.setHeader('Access-Control-Max-Age', 7200)
  // 傳送響應
  res.send()
})

// 處理實際請求
app.get('/students', (req, res) => {
  // 設定允許的跨域請求源
  res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
  // 隨便設定一個自定義響應頭
  res.setHeader('abc',123)
  // 設定允許暴露給客戶端的響應頭
  res.setHeader('Access-Control-Expose-Headers', 'abc')
  // 列印請求日誌
  console.log('有人請求/students了')
  // 傳送響應資料
  res.send(students)
})

2、解決方案二:使用cors庫等

nodejs
// 配置cors庫
app.use(cors())
// cors中介軟體配置
const corsOptions = {
  origin: 'http://127.0.0.1:5500', // 允許的源
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'], // 允許的方法
  allowedHeaders: ['school'], // 允許的自定義頭
  exposedHeaders: ['abc'], // 要暴露的響應頭
  optionsSuccessStatus: 200 // 預檢請求成功的狀態碼
};

app.use(cors(corsOptions)); // 使用cors中介軟體
django
INSTALLED_APPS = [
    'django.contrib.admin',
	...
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 處理跨域
    'corsheaders',
]

MIDDLEWARE = [
	...
    'django.contrib.sessions.middleware.SessionMiddleware',
    # 處理跨域
    "corsheaders.middleware.CorsMiddleware",
	...
]

3、解決方案三、JSONP

  1. JSONP 概述: JSONP 是利用了<script>標籤可以跨域載入指令碼,且不受嚴格限制的特性,可以說是程式設計師智慧的結晶,早期一些瀏覽器不支援 CORS 的時,可以靠 JSONP 解決跨域。
  2. 基本流程:
    ○ 第一步:客戶端建立一個<script>標籤,並將其src屬性設定為包含跨域請求的 URL,同時準備一個回撥函式,這個回撥函式用於處理返回的資料。
    ○ 第二步:服務端接收到請求後,將資料封裝在回撥函式中並返回。
    ○ 第三步:客戶端的回撥函式被呼叫,資料以引數的形勢傳入回撥函式。
    image
JavaScript核心程式碼
<button onclick="getTeachers()">獲取資料</button>

<script type="text/javascript" >
  function callback(data){
    console.log(data)
  }

  function getTeachers(url){
    // 建立script元素
    const script = document.createElement('script')
    // 指定script的src屬性
    script.src= 'http://127.0.0.1:8081/teachers'
    // 將script元素新增到body中觸發指令碼載入
    document.body.appendChild(script)
    // script標籤載入完畢後移除該標籤
    script.onload = ()=>{
      script.remove()
    }
  }
</script>
jQuery 封裝的 jsonp
$.getJSON('http://127.0.0.1:8081/teachers?callback=?',(data)=>{
  console.log(data)
})

4、配置代理解決跨域

4.1 自己配置代理伺服器

藉助http-proxy-middleware配置代理
const { createProxyMiddleware } = require('http-proxy-middleware');

app.use('/api',createProxyMiddleware({
  target:'https://www.toutiao.com',
  changeOrigin:true,
  pathRewrite:{
    '^/api':''
  }
}))

4.2

基於nginx搭建代理伺服器,基於Vue等腳手架搭建代理伺服器(本質上是對4.1的封裝)

相關文章