# 同源与跨域
如果两个 URL 的协议、域名、端口都一致,则他们同源,反之是跨域。例如:
- 本地项目
http://localhost:8080
去访问本地的后端接口http://localhost:6060
—— 端口不一致 - 本地项目
http://localhost:8080
去访问部署的后端接口https://10.143.88.222:8080
—— 域名、协议不一致 - ……
# 同源策略
出现跨域的根本原因:
浏览器的同源策略不允许非同源的 URL 之间进行资源的交互。

# 一、JONSP 方式
# 1、原理
由于浏览器的同源策略,网页无法通过 ajax 请求非同源的接口数据。但是:
script 标签的 src 属性、img 标签的 src 属性、link 标签的 href 属性等是没有跨域的限制的。
# 2、优缺点
优点:
实现方式非常巧妙,通过前端写一个回调函数,并在发送请求的时候将回调函数名告诉后端,后端返回数据是通过触发回调函数的方式向前端发送数据。
缺点:
只能解决 get 请求的跨域问题。并且使用 JONSP 方法解决跨域的话,需要前后端相互配合的写一些东西,这一点就 😒 非常难受。
# 3、具体实现
动态添加一个<script>标签,通过 src 属性请求跨域的数据接口,并通过函数调用的形式接受跨域接口响应回来的数据。
# ① 前端
利用 src 属性解决跨域,前端除了使用原生 js 手动实现,还可以使用 jQuery,因为 jQuery 是支持发起 jsonp 请求的。
前端发起请求的时候,在请求地址上追加 ?callback='回调函数名'
代码演示
- index.html
<body> <h4>1、请求方式</h4> <button class="btn-origin">发起跨域请求-原生js封装方式</button> <br /><br /> <button class="btn-jquery">发起跨域请求-jQuery请求方式</button> <h4>2、服务器地址</h4> <p>原生node服务器:http://localhost:6060/data?callback=requestData</p> <p>express服务器:http://localhost:6061/data?callback=requestData</p> <script> document.querySelector('.btn-origin').onclick = function () { // JSONP请求封装 function jsonp(url, callback) { const script = document.createElement('script') script.src = url + '?callback=' + callback document.body.appendChild(script) } // 1、发起请求 jsonp('http://localhost:6060/data', 'requestData') } // 2、接收数据(回调 -- 供后端调用) // 问题: 如何将这里的回调🤔🤔🤔放到onclick里面 function requestData(data) { console.log(data) } // ---------------------------- document.querySelector('.btn-jquery').onclick = function () { $.ajax({ dataType: 'jsonp', url: 'http://localhost:6061/data', // 设置指定的回调函数名 // 【等效于上面的:'?callback=' + callback】 jsonp: 'callback', jsonpCallback: 'requestData', success: function (res) { console.log(res) } }) } </script> </body>
Copied!
# ② 后端
后端的话,可以使用原生 node.js 或者 后面的 express 快速搭建一个接口环境。
后端返回数据时,返回的是一个回调函数 res.send(`${callback}(${JSON.stringify(data)})`)
代码演示
- 原生 node 服务器 —— 搭建
const http = require('http') const url = require('url') // 创建web服务器 http .createServer((req, res) => { const data = { name: 'lencamo', age: 18 } const callback = url.parse(req.url, true).query.callback if (url.parse(req.url, true).pathname === '/data') { res.end(`${callback}(${JSON.stringify(data)})`) } }) .listen(6060, () => { console.log('原生node服务器启动:http://localhost:6060') })
Copied!
- express 服务器 —— 搭建
const express = require('express') const app = express() app.get('/data', (req, res) => { const data = { name: 'lencamo', age: 18 } const callback = req.query.callback res.send(`${callback}(${JSON.stringify(data)})`) }) app.listen(6061, () => { console.log('express服务器启动:http://localhost:6061') })
Copied!
# 二、CORS 方式(后端)
# 1、原理
通过最前面的图片介绍,我们知道了,产生跨域的根源是浏览器的同源策略。
使用 CORS 时,它可以通过向浏览器添加 HTTP 头,达到允许源站点设置哪些外部域可以访问其资源的目的。相关的 HTTP 字段有:
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
- ……
# 2、优缺点
CORS 解决跨域可以说是真正意义上的解决跨域问题,但是压力全给到了后端,并且响应头不是可以随便配置的。
# 3、具体实现
# ① 前端
在上面 JONSP 实现请求跨域中,我们使用了 jQuery 发起 ajax 请求,下面我们采用其他的几种方式发起数据请求:XHR、 fetch 和 axios 。
采用 CORS 方法解决跨域问题,就没前端什么事了,安心写请求就可以了。
代码演示
<body> <h4>1、请求方式</h4> <button class="btn-XHR">发起跨域请求-XHR请求</button> <br /><br /> <button class="btn-fetch">发起跨域请求-fetch请求</button> <br /><br /> <button class="btn-axios">发起跨域请求-axios请求</button> <h4>2、服务器地址</h4> <p>原生node服务器:http://localhost:6060/data</p> <p>express服务器:http://localhost:6061/data</p> <script> document.querySelector('.btn-XHR').onclick = function () { var xhr = new XMLHttpRequest() xhr.open('GET', 'http://localhost:6060/data') xhr.send() xhr.onload = function () { if (xhr.status === 200) { console.log(JSON.parse(xhr.responseText)) } } } document.querySelector('.btn-fetch').onclick = function () { fetch('http://localhost:6060/data') .then((res) => res.json()) .then((res) => { console.log(res) }) } document.querySelector('.btn-axios').onclick = function () { axios.get('http://localhost:6061/data').then((res) => { console.log(res.data) }) } </script> </body>
Copied!
# ② 后端
后端利用 CORS 解决跨域问题时呢,也有两种方式:
springboot 应用中
@Configuration public class GlobalCorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); // 设置你要允许的网站域名 config.addAllowedOrigin("http://localhost:8080"); //允许跨域发送cookie config.setAllowCredentials(true); //放行全部原始头信息 config.addAllowedHeader("*"); //允许所有请求方法跨域调用 config.addAllowedMethod("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }
Copied!
- 方式 1:设置响应头(需要知道 😒 前端服务运行地址)
const http = require('http') const url = require('url') http .createServer((req, res) => { // 设置响应头解决跨域 res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8081') res.setHeader('Access-Control-Allow-Methods', 'GET') res.setHeader('Access-Control-Allow-Headers', 'Content-Type') res.setHeader('Content-Type', 'application/json') const data = { name: 'lencamo', age: 18 } if (url.parse(req.url, true).pathname === '/data') { res.end(JSON.stringify(data)) } }) .listen(6060, () => { console.log('原生node服务器启动:http://localhost:6060') })
Copied!
- 方式 2:使用第三方中间件
在真正的开发中,我们往往是采用第三方中间件来简化跨域处理,例如 cors (opens new window)(HTTP 服务器)、http-proxy-middleware (opens new window)(代理服务器)等。
这些中间件会自动设置响应头和处理预检请求(OPTIONS),让我们更方便地处理跨域问题。
- cors 包
const express = require('express') const app = express() // cors包解决跨域 const cors = require('cors') app.use(cors()) app.get('/data', (req, res) => { const data = { name: 'lencamo', age: 18 } res.send(JSON.stringify(data)) }) app.listen(6061, () => { console.log('express服务器启动:http://localhost:6061') })
Copied!
# 三、代理服务器
浏览器没办法和 目标服务器沟通 解决跨域的问题,那就找一个代理人帮我们做。

作用
自己单独创建一个 node 代理服务器有如下功能:
- 作为服务端:它解决跨域问题,并接受数据请求地址
- 作为客户端:它向目标服务器发出数据请求
# 1、原生 Node 代理
略(详见 - Node 服务器 (opens new window))
# 2、第三方中间件 代理
-http-proxy-middleware (opens new window) 是一个 Node.js 中间件,它允许将 HTTP 请求代理到另一个服务器,从而避免浏览器的同源策略限制
完整实现 ✍
- 前端
<body> <h4>1、请求方式</h4> <button class="btn-axios">发起跨域请求</button> <h4>2、服务器地址</h4> <p>express-代理服务器:http://localhost:5050/ren/data</p> <p>express-http服务器:http://localhost:6061/data</p> <script> document.querySelector('.btn-axios').onclick = function () { // 向代理商发起数据请求 axios.get('http://localhost:5050/ren/data').then((res) => { console.log(res.data) }) } </script> </body>
Copied!
- 代理服务器 5050 ✍
const express = require('express') const cors = require('cors') const { createProxyMiddleware } = require('http-proxy-middleware') const app = express() // 1、解决请求跨域问题 app.use(cors()) // 注意使用时机🤔 app.use((req, res, next) => { // console.log(req) console.log( '5050代理服务器收到' + req.get('origin') + '发来的数据请求!该请求将发往' + req.get('host') ) next() }) // 2、将请求进行代理转发 // 将以/ren(ren这一类型的😄请求)开头的请求,代理到 http://localhost:6061 app.use( '/ren', createProxyMiddleware({ target: 'http://localhost:6061', // 请求头的 Host 字段将由http://localhost:5050改为http://localhost:6061(伪装) changeOrigin: true, // 将 http://localhost:6061/ren/data/ 代理到 http://localhost:6061/data/ // 【去掉/ren这个接口标识】 pathRewrite: { '^/ren': '' } }) ) app.listen(5050, () => { console.log('代理服务器启动:http://localhost:5050') })
Copied!
- 目标服务器 6061
const express = require('express') const app = express() app.use((req, res, next) => { // console.log(req) console.log( '6061-http服务器收到' + req.get('origin') + '发来的数据请求!该请求将发往' + req.get('host') ) next() }) app.get('/data', (req, res) => { const data = { name: 'lencamo', age: 18 } res.send(JSON.stringify(data)) }) app.listen(6061, () => { console.log('HTTP服务器启动:http://localhost:6061') })
Copied!
在上面这个完整实现案例中,changeOrigin: true,
这个配置是什么意思呢?
答案
首先,要先了解两个东西origin
(请求从哪里发起的) 和 host
(请求将被发送的目的地) 。
其次,changeOrigin 这个单词是意思要理解好:是将 Host 字段将更改为 Origin 字段。
要想深刻的理解一下,可以通过打印:
console.log( '5050代理服务器收到' + req.get('origin') + '发来的数据请求!该请求将发往' + req.get('host') ) console.log( '6061-http服务器收到' + req.get('origin') + '发来的数据请求!该请求将发往' + req.get('host') )
Copied!
changeOrigin: false,
时
5050代理服务器收到http://localhost:8081发来的数据请求!该请求将发往localhost:5050 6061-http服务器收到http://localhost:8081发来的数据请求!该请求将发往localhost:5050
Copied!
changeOrigin: true,
时
5050代理服务器收到http://localhost:8081发来的数据请求!该请求将发往localhost:5050 # 这就是改为true时👍的作用 6061-http服务器收到http://localhost:8081发来的数据请求!该请求将发往localhost:6061
Copied!
# 3、Nginx 代理
略(详见 - 项目部署 (opens new window))
# 四、vue-cli 代理(前端)
关于 vue-cli 项目内部的这个 8081 代理服务器,它是怎么搞的我暂时不太清楚(虚拟内存?),但目标服务器 6061 的打印信息为:
6061-http服务器收到🤔undefined发来的数据请求!该请求将发往localhost:6061
Copied!

完整实现 ✍
- App.vue
<template> <div id="app"> <h4>1、请求方式</h4> <button @click="btn_axios">发起跨域请求</button> <h4>2、服务器地址</h4> <p>express-代理服务器:http://localhost:8081/ren/data</p> <p>express-http服务器:http://localhost:6061/data</p> </div> </template> <script> import axios from 'axios' export default { name: 'App', methods: { btn_axios() { // 向代理商✍8081发起数据请求 axios.get('http://localhost:8081/ren/data').then((res) => { console.log(res.data) }) } } } </script>
Copied!
- vue.config.js
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, devServer: { // 一、项目启动配置 open: true, host: 'localhost', port: 8081, // 默认为8080 https: false, // 二、请求代理配置 proxy: { '/ren': { target: 'http://localhost:6061', ws: true, // 支持webSocket changeOrigin: true, pathRewrite: { '^/ren': '' } } } } })
Copied!
- 目标服务器 6061
const express = require('express') const app = express() app.use((req, res, next) => { // console.log(req) console.log( '6061-http服务器收到' + req.get('origin') + '发来的数据请求!该请求将发往' + req.get('host') ) next() }) app.get('/data', (req, res) => { const data = { name: 'lencamo', age: 18 } res.send(JSON.stringify(data)) }) app.listen(6061, () => { console.log('HTTP服务器启动:http://localhost:6061') })
Copied!
# 1、官方解释:
如果你的前端应用和后端 API 服务器没有运行在同一个主机上,你需要在 开发环境下 将 API 请求代理到 API 服务器。
这个问题可以通过 vue.config.js
中的 devServer.proxy 选项来配置。
# 2、本质
通过官方可以发现,它是使用了http-proxy-middleware 中间件 (opens new window),说白了还是使用了 CORS 方式解决跨域的。
知道了 vue-cli 是怎么实现代理后,后面看到它的 devServer.proxy (opens new window) 配置和 http-proxy-middleware 类似就没什么奇怪的了。
# 3、vue-cli 的使用
既然都使用 vue-cli 了,devServer.proxy 配置该写在那个文件里面就不用说了吧?
答案
在 vue.config.js 文件里面进行配置
在 vue-cli 中进行代理时,最大的体验莫过于:不用自己单独创建一台代理服务器,vue-cli 已经在项目中为我们启动了一台代理服务器,而是直接在配置文件中一键配置代理
(就有那么一瞬间感觉配置
devServer.proxy
就是代理服务器中配置createProxyMiddleware
)。
① 代理所用请求
module.exports = { devServer: { proxy: 'http://localhost:6061' } }
Copied!
② 代理指定类型的请求
module.exports = { devServer: { // 一、项目启动配置 open: true, host: 'localhost', port: 8081, // 默认为8080 https: false, // 二、请求代理配置 proxy: { '/ren': { target: 'http://localhost:6061', ws: true, // 支持webSocket changeOrigin: true, pathRewrite: { '^/ren': '' } } // '/foo': { // target: '<other_url>' // } } } }
Copied!
拓展
// [process.env.VUE_APP_BASE_API]: { // target: `http://example.com`, //要代理的域名地址 // ws: true, // changeOrigin: true, // 支持跨域请求 // pathRewrite: { // // 不保留[process.env.VUE_APP_BASE_API] // ['^' + process.env.VUE_APP_BASE_API]: '' // // 保留或重写为其他 // '^/api': '/api/oj' // } // },
Copied!
# 4、生产环境跨域
生产环境表示我们已经开发完成项目,将项目部署到了服务器上,这时已经没有了 vue-cli 脚手架的辅助了。
我们只是把打包好的 html+js+css 交付运维人员,放到 Nginx 服务器而已,所以此时需要借助 Nginx 的反向代理来进行。
# 五、webpack / vite 代理
无论是 webpack-dev-server 中的 proxy,还是 vite 中的 proxy,内部都是使用了同一个库 node-http-proxy (opens new window)。
改天 回顾 webpack 和 学完 vite 的时候再来补充 😂