若前後端分離,且同時在本機開發 Vue 前端與 Node 後端時,很容易遇到違反 Same-Origin Policy 問題,本文介紹多種方式解決。
Version
macOS 10.15.4
WebStorm 2020.1.1
Vue 2.6.11
Vue CLI 4.3.1
Node 12.4.0
Chrome 83.0.4103.61
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 domain 的 API 存取違反 same-orgin policy,會被 browser 擋下而無法執行。
Node
index.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。
使用 Postman 可正確打到 http://localhost:3000/hello-world
並回傳 Hello World!
Vue
App.vue
<template>
<div>
{{ message }}
</div>
</template>
<script>
import axios from 'axios'
let mounted = function() {
axios.get('http://localhost:3000/hello-world')
.then(x => x.data)
.then(x => this.message = x)
}
export default {
name: 'app',
data: () => ({
message: ''
}),
mounted
}
</script>
11 行
axios.get('http://localhost:3000/hello-world')
.then(x => x.data)
.then(x => this.message = x)
在 Vue 使用 Axios 直接打 http://localhost:3000/hello-world
。
Browser 會顯示因為 header 沒有 Access-Control-Allow-Origin
,所以打 http://localhost:3000/hello-world
違反 same-origin policy 而失敗。
CORS
$ yarn add cors --dev
若 API 也自己開發,可安裝 cors
middleware。
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 api
啟動 Node。
Vue 可正確收到 API 回傳值。
Reverse Proxy
vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {'^/api' : ''}
}
}
},
}
若 API 不是由自己開發,可暫時由前端 Webpack DevServer 的 reverse proxy 解決。
在專案目錄下新增 vue.config.js
,當 yarn serve
發現 vue.config.js
,就會採用這個檔案。
第 4 行
'/api': {
target: 'http://localhost:3000',
pathRewrite: { '^/api': '' }
}
當 Vue 打 /api/hello-world
時,DevServer 的 reverse proxy 會將 /api
以 http://localhost:3000
rewrite 成 http://localhost:3000/hello-world
。
也由於 Vue 是打 /api/hello-world
,browser 視為相同 domain,因此沒有違反 same-origin policy。
location /api/ {
proxy_pass 'http://localhost:3000';
}
在此特別要強調的是同樣是 reverse proxy,但 rule 寫法 DevServer 與 Nginx 不一樣!
Nginx 在 location
最後要加上 /
,也就是 /api/
而不是 /api
,server 最後也要加上 /
,也就是 http://localhost:3000
而不是 http://localhost:3000/
,且最後也不需 rewrite
。
App.vue
<template>
<div>
{{ message }}
</div>
</template>
<script>
import axios from 'axios'
let mounted = function() {
axios.get('api/hello-world')
.then(x => x.data)
.then(x => this.message = x)
}
export default {
name: 'app',
data: () => ({
message: ''
}),
mounted
}
</script>
11 行
axios.get('api/hello-world')
.then(x => x.data)
.then(x => this.message = x)
Vue 從原本的 http://localhost:3000/hello-world
改打 /api/hello-world
。
其他部分不用改變,Node 也不用改變,重新 yarn start
啟動 DevServer 即可。
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。
警告可暫時忽略。
Moesif Origin & CORS Changer
也可安裝 Chrome extension 解決。
在 extension 為 on
狀態下,Vue 與 Node 都不必修改程式就可避開 same-origin policy 檢查。
Conclusion
- Same-origin policy 理論上該由後端以 CORS 處理,若暫時無法由後端解決,前端可先 workaround 用 reverse proxy,或乾脆暫時讓 Chrome 不要執行 same-origin policy
Sample Code
完整的範例可以在我的 GitHub 上找到