點燈坊

學而時習之,不亦悅乎

如何解決 Vue 開發環境違反 Same-Origin Policy ?

Sam Xiao's Avatar 2019-09-02

若前後端分離,且同時在本機開發 Vue 前端與 Node 後端時,很容易遇到違反 Same-Origin Policy 問題,此時可用 CORS 或 Reverse Proxy 解決。

Version

macOS 10.14.6
WebStorm 2019.2.1
Vue 2.6.10
Vue CLI 3.11.0
Node 12.9.1
Express 4.17.1
cors 2.8.5

Same-Origin Policy

Browser 的 same-origin policy 不允許存取不同 domain 的 API,舉例來說,若在本機同時開發 Vue 與 Node,Vue CLI 啟動在 http://localhost:8080,而 Node 啟動在 https://localhost:3000,對於 browser 而言,儘管 host 相同,port 不同視為不同 domain,這種 cross domian 的 API 存取,因違反 same-orgin policy,會被 browser 擋下而無法執行。

Node

app.js

let express = require('express');

let app = express();
let port = 3000;

app.get('/hello-world', (req, res) => res.send('Hello World!'));

app.listen(port, () => console.log(`Node listening on port ${port}!`));

使用 Node + Express 建立 /hello-world GET,啟動在 3000 port。

cors000

使用 Postman 可正確打到 http://localhost:3000/hello-world 並回傳 Hello World!

Vue

App.vue

<template>
  <div>
    Vue CORS demo
  </div>
</template>

<script>
import axios from 'axios';

let mounted = async () => {
  let res = await axios.get('http://localhost:3000/hello-world');
  console.log(res.data);
};

export default {
  name: 'app',
  mounted,
}
</script>

11 行

let res = await axios.get('http://localhost:3000/hello-world');
console.log(res.data);

在 Vue 使用 Axios 直接打 http://localhost:3000/hello-world

cors001

Browser 會顯示因為 header 沒有 Access-Control-Allow-Origin,所以打 http://localhost:3000/hello-world 違反 same-origin policy 而失敗。

CORS

若 Node 也自己開發,可使用 CORS 解決:

$ yarn add cors --dev

安裝 cors middleware。

cors002

app.js

let express = require('express');
let cors = require('cors');

let app = express().use(cors());
let port = 3000;

app.get('/hello-world', (req, res) => res.send('Hello World!'));

app.listen(port, () => console.log(`Node listening on port ${port}!`));

第 2 行

let cors = require('cors');

cors module require 進來。

第 4 行

let app = express().use(cors());

使用 cors middleware。

其他部分不用改變,Vue 也不用改變,重新 yarn start 啟動 Node server。

cors003

Vue 可正確收到 API 回傳值。

Reverse Proxy

若 Node 不是由自己開發,也可暫時由前端 Webpack devServer 的 reverse proxy 解決。

vue.config.js

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

在專案目錄下新增 vue.config.js,當 yarn serve 發現 vue.config.js,就會採用這個檔案。

第 4 行

'/api': {
  target: 'http://localhost:3000',
  pathRewrite: { '^/api': '' }
}

當 Vue 打 /api/hello-world 時,reverse proxy 會 rewrite 成 http://localhost:3000/hello-world

也由於 Vue 是打 /api/hello-world,browser 視為相同 domain,因此沒有違反 same-origin policy。

App.vue

<template>
  <div>
    Vue CORS demo
  </div>
</template>

<script>
import axios from 'axios';

let mounted = async () => {
  let res = await axios.get('/api/hello-world');
  console.log(res.data);
};

export default {
  name: 'app',
  mounted,
}
</script>

11 行

let res = await axios.get('/api/hello-world');
console.log(res.data);

Vue 從原本的 http://localhost:3000/hello-world 改打 /api/hello-world

其他部分不用改變,Node 也不用改變,重新 yarn start 啟動 devServer。

cors003

Vue 可正確收到 API 回傳值。

Chrome

$ open -n -a /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security

若不想改 Node 也不想改 Vue,也可暫時啟動沒有執行 same-origin policy 的 Chrome。

cors004

Conclusion

  • Same-origin policy 理論上該由後端以 CORS 處理,若暫時無法由後端處理,前端可先 workaround 用 reverse proxy,或乾脆暫時讓 Chrome 不要執行 same-origin policy

Sample Code

完整的範例可以在我的 GitHub 上找到

Reference

Vue CLI, devServer.proxy
Chimurai, http-proxy-middleware