一篇搞定面試中的跨域問題

發表於2024-02-16

什麼是CORS(跨源資源共享)?

CORS(Cross-Origin Resource Sharing)是一種機制,允許網頁從不同的域訪問伺服器上的資源。

在同源策略下,瀏覽器限制了跨域訪問,CORS允許伺服器指定哪些源可以訪問其資源。

同源策略(Same-origin policy)

同源策略在web應用安全模型中是一個重要的概念。在這個策略下,瀏覽器允許第一個網頁中包含的指令碼可以獲取第二個網頁的資料,前提是這兩個網頁在同一個源下。

同源:需要URI、主機名、埠都相同。

這個策略可以防止一個網頁上的惡意指令碼透過DOM獲取其他網頁的敏感資料。

需要牢記的一點就是同源策略只應用於指令碼,這意味著像images、css和其他動態載入的指令碼 可以透過對應的標籤跨域訪問資源。

是否同源的規則

同源需要滿足相同的協議(scheme),相同的主機名(host),相同的埠號(port)

Compared URLOutcome Reason
http://www.example.com/dir/page2.htmlSuccess Same scheme, host and port
http://www.example.com/dir2/other.htmlSuccess Same scheme, host and port
http://username:password@www.example.com/dir2/other.htmlSuccess Same scheme, host and port
http://www.example.com:81/dir/other.htmlFailure Same scheme and host but different port
https://www.example.com/dir/other.htmlFailure Different scheme
http://en.example.com/dir/other.htmlFailure Different host
http://example.com/dir/other.htmlFailure Different host (exact match required)
http://v2.www.example.com/dir/other.htmlFailure Different host (exact match required)
http://www.example.com:80/dir/other.htmlDepends Port explicit. Depends on implementation in browser.

::: tip
scheme: http https
:::

CORS存在的意義

跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓執行在一個 origin (domain) 上的Web應用被準許訪問來自不同源伺服器上的指定的資源。

出於安全性,瀏覽器限制指令碼內發起的跨源HTTP請求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。這意味著使用這些 API 的 Web 應用程式只能從載入應用程式的同一個域請求 HTTP 資源,除非響應報文包含了正確 CORS 響應頭。

跨源域資源共享(CORS)機制允許 Web 應用伺服器進行跨源訪問控制,從而使跨源資料傳輸得以安全進行。

CORS的工作原理是什麼?

當瀏覽器發起跨域請求時,會在請求頭中新增Origin欄位,指示請求的源。伺服器接收到請求後,會在響應頭中新增Access-Control-Allow-Origin欄位,指示允許訪問的源。如果伺服器允許該源訪問資源,瀏覽器會將響應返回給客戶端。

CORS中的預檢請求是什麼?為什麼需要預檢請求?

預檢請求是瀏覽器在傳送跨域請求前傳送的一種OPTIONS請求,用於檢查伺服器是否支援跨域請求。預檢請求中包含一些額外的頭資訊,如Access-Control-Request-Method、Access-Control-Request-Headers等。

預檢請求的目的是確保伺服器支援跨域請求,避免跨域請求對伺服器造成安全風險。只有在伺服器返回允許的響應頭後,瀏覽器才會傳送實際的跨域請求。

瞭解了上面的內容,我們解決瀏覽器控制檯的跨域問題,一般有兩個方向:

  1. 後端服務設定允許跨域訪問
  2. 前端透過代理訪問資源(開發階段使用)

如何在伺服器端配置CORS?

在伺服器端配置CORS通常需要在響應頭中新增一些欄位,如Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers等。透過配置這些欄位,可以控制允許的源、請求方法和請求頭。

用 Node.js 的一個框架 koa 來舉例,解決跨域使用 koa-cors 非常簡單,如下:

var koa = require('koa');
var route = require('koa-route');
var cors = require('koa-cors');
var app = koa();

app.use(cors());

app.use(route.get('/', function() {
  this.body = { msg: 'Hello World!' };
}));

app.listen(3000);

這個中介軟體大概做了這樣的事情:

module.exports = () => {
  return async function(ctx, next) {
    ctx.set('Content-Type', 'application/json');
    ctx.set('Access-Control-Allow-Origin', '*');
    ctx.set('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE, PUT');
    ctx.set('Access-Control-Allow-Headers', 'X-Requested-With, content-type, X-Authorization, X-uuid');
    ctx.json = json.bind(ctx);
    ctx.halt = halt.bind(ctx);
    try {
        await next();
    } catch (e) {
        return ctx.halt(e.code, e.message);
    }
  };
};

這樣前端收到的響應會是下面的樣子:

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

本例中,服務端返回的 Access-Control-Allow-Origin: * 表明,該資源可以被 任意 外域訪問。

前端代理

如果使用了webpack 那麼配置一個代理就很容易,透過代理模擬出和服務端同源的請求。

//webpack.config.js
module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        pathRewrite: { '^/api': '' },
      },
    },
  },
};

這樣前端發起的請求包含/api的路徑就會被代理到 http://localhost:3000,並且會把/api替換為空。如果你的介面地址本來就包括/api,那隻要把pathRewrite: { '^/api': '' }去掉即可。

這個devServer代理使用功能強大的 http-proxy-middleware 軟體包。

http-proxy-middleware 內部又使用了 node-http-proxy

服務端代理

服務端代理是一種解決跨域訪問的方法,透過在服務端發起請求並將結果返回給客戶端,繞過了瀏覽器的同源策略限制。通常可以使用Node.js、Java、Python等後端語言來實現服務端代理。

以下是一個使用Node.js實現服務端代理的示例程式碼:

const express = require('express');
const request = require('request');

const app = express();

app.get('/proxy', (req, res) => {
  const url = req.query.url;
  request(url, (error, response, body) => {
    if (!error && response.statusCode === 200) {
      res.send(body);
    } else {
      res.status(500).send('Error');
    }
  });
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

在上面的示例中,當客戶端傳送請求到/proxy介面時,服務端會將請求轉發到指定的URL,並將結果返回給客戶端。

總結

總的來說,跨域資源共享是一個常見的Web開發問題,瞭解跨域資源共享的原理和解決方法對於開發安全可靠的Web應用程式非常重要。

參考連結

跨源資源共享(CORS)

相關文章