目录
方法一:后端设置响应头,告诉浏览器不进行拦截(主流方法,适用于开发,生产环境)
2.2 反向代理: 使用express + http-proxy-middleware 增加转发功能
2.3 设置反向代理服务器 使用http-proxy-middleware 实现反向代理服务器,也可直接ngnix设置代理转发
跨域产生原因:
浏览器基于浏览器的同源策略 - Web 安全 | MDN在 一个源的文档或者它加载的脚本在与另一个源(非同源)的资源进行交互 的时候经行了限制。
同源的定义:
如果两个 URL 的协议、端口(如果有指定的话)和主机都相同的话,则这两个 URL 是同源的
下表给出了与 URL http://example.com/v1/page.html
的源进行对比的示例:
URL | 结果 | 原因 | |
---|---|---|---|
示例1 |
| 同源 | 只有路径不同 |
示例2 | https://example.com/v1/page.html | 失败 | 协议不同 |
示例3 | http://example.com:81/v1/page.html | 失败 | 端口不同(http:// 默认端口是 80) |
示例4 | http://sub.example.com/v1/page.html | 失败 | 主机不同 |
tips: 二级域名不同仍旧会跨域
在示例4中,虽然官方文档(Same-origin policy - Security on the web | MDN)是说主机(host),但是指完整的:域名/主机(Domain/Host)+ 子域名(Subdomain),而不仅仅指狭义的主域名。
故:协议,端口,主域名,相同,但二级(3...4...)域名不相同仍会触发浏览器的同源策略
【已弃用】:通过主域名与子域名设置相同的 document.domain ,可以绕过同源策略浏览器的同源策略 - 源的更改 | MDN
// http://example.com/v1/page.html 页面和 // http://sub.example.com/v2/login.html 页面设置相同的 document.domain document.domain = "example.com"; // 此处设置为其当前域或其当前域的父域 example.com ,要是其他例如 test.com 也不能绕过同源策略 // 警告: 这里描述的方法(使用 document.domain setter)已被弃用,因为它破坏了同源策略所提供的安全保护,并使浏览器中的源模型复杂化,导致互操作性问题和安全漏洞。
tips:cookie 是有域限制的,但二级域访问访问顶级域是可以的,而二级域名之间的访问是存在跨域的。
解决办法
CORS:可以使用 CORS 来允许跨源访问。CORS 是 HTTP 的一部分,它允许服务端来指定哪些主机可以从这个服务端加载资源。(Cross-Origin Resource Sharing (CORS) - HTTP | MDN)
方法一:后端设置响应头,告诉浏览器不进行拦截(主流方法,适用于开发,生产环境)
Header | value | 是否必填 | 说明 |
---|---|---|---|
Access-Control-Allow-Origin | ip/* | 必填 | *表示允许所有源访问;或者设置具体的ip,仅该ip可访问 |
Access-Control-Allow-Headers | 'Content-Type, client, devicesid, registersign' | 否 | 允许的自定义请求头 |
Access-Control-Allow-Methods | 'GET, POST, OPTIONS' | 否 | 允许的请求方式 |
Access-Control-Allow-Credentials | 'true' | 否 | 唯一值 true(如果不是true,删掉该响应头):允许带Cookie;如果用了allowCredentials(true),allowedOrigins不能为*(必须明确指定域名)。 |
1.1后端nodeJs 设置中间件统一为所有请求添加响应头
npm install express
const express = require('express');
const app = express();
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
app.get('/data', (req, res) => {
res.json({ message: 'This is data from example.com' });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
2.2 反向代理: 使用express + http-proxy-middleware 增加转发功能
npm install express http-proxy-middleware
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
// 创建Express应用
const app = express();
// 自定义CORS处理逻辑
app.use((req, res, next) => {
// 设置允许的来源
res.setHeader('Access-Control-Allow-Origin', 'http://10.1.15.6:3000');
// 设置允许的请求头
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, client, devicesid, registersign');
// 设置允许的请求方法
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
if (req.method === 'OPTIONS') {
// 处理预检请求
res.sendStatus(200);
} else {
// 继续处理实际请求
next();
}
});
// 创建代理中间件
// 使用代理中间件
app.use('/jk_flv', createProxyMiddleware({
target: 'https://sample-videos.com',
// target: 'https://sample-videos.com/video321/flv/720/big_buck_bunny_720p_1mb.flv',
changeOrigin: true,
}));
//'https://apis.map.qq.com/ws/geocoder/v1/?key=111&address=广东省深圳市' //真实地址
// http://localhost:8080/ditu/ws/geocoder/v1/?key=111&address=广东省深圳市 // 请求地址
app.use('/ditu', createProxyMiddleware({
target: 'https://apis.map.qq.com',
changeOrigin: true, // true 时 真实路径将失去 一个 /ditu false 不知道什么意思😂猜测是服务器返回给浏览器的
// pathRewrite: { // 此处写不写都是一样的
// '^/ditu': ''
// }
onProxyReq(proxyReq, req, res) {
// 打印转发请求的实际地址
// 这里不打印
console.log(`Request forwarded to:${proxyReq.protocol}//${proxyReq.host}${proxyReq.path}`);
}
}));
// 启动开发服务器
const port = 8080;
app.listen(port, () => {
console.log(`开发服务器已启动,监听端口 ${port}`)
});
2.3 设置反向代理服务器 使用http-proxy-middleware 实现反向代理服务器,也可直接ngnix设置代理转发
npm install http-proxy
创建一个名为 proxy-server.js 的文件,并添加以下代码:
const http = require('http');
const httpProxy = require('http-proxy');
// 创建一个代理服务器实例
const proxy = httpProxy.createProxyServer({});
// 创建一个 HTTP 服务器
const server = http.createServer((req, res) => {
// 打印每次请求的真实地址
console.log(`Received request for: ${req.url}`);
// 目标服务器地址,这里假设目标服务器运行在本地的 3000 端口
const target = 'http://localhost:3000';
// 将请求代理到目标服务器
proxy.web(req, res, { target });
});
// 监听错误事件
proxy.on('error', (err, req, res) => {
console.error('Proxy error:', err);
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Proxy error');
});
// 启动服务器,监听 8080 端口
const port = 8080;
server.listen(port, () => {
console.log(`Proxy server is running on port ${port}`);
});
node proxy-server.js 启动服务
方法二:配置viter/webpack (仅开发环境有效)
vue3 vite 代理 @/vite.config.js 核心代码:server.proxy
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
import path from 'path'
// import CONFIG from './src/utils/config.js'
// defineConfig 工具函数获取类型提示
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [VantResolver()],
}),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
// vite 相关配置
server: {
port: 3000,
host: true,
open: true,
proxy: {
// https://cn.vitejs.dev/config/#server-proxy
'/wen': {
target: "http://192.168.10.38:8099/api",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/wen/, '')
}, // 请求地址 http://localhost:3000/wen/login => http://192.168.10.38:8099/api/login
'/map': {
target: "https://apis.map.qq.com/",
// target: "https://10.9.31.176/",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/map/, '')
},
}
},
// 打包配置
base: './',//引入路径相当于webpack中的 baseUrl 或 publicPath
build: {
// sourcemap: false,
// outDir: 'dist', // 指定输出路径,要和库的包区分开
// assetsDir: 'static/img/', // 指定生成静态资源的存放路径
// rollupOptions: {
// output: {
// chunkFileNames: 'static/js/[name]-[hash].js',
// entryFileNames: 'static/js/[name]-[hash].js',
// assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
// },
// }
}
})
vue2 webpack @/vue.config.js 核心代码:devServer.proxy
/**
* 配置参考: https://cli.vuejs.org/zh/config/
*/
const MonacoEditorPlugin = require('monaco-editor-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const path = require('path')
function resolve (dir) {
return path.join(__dirname, dir)
}
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
chainWebpack: config => {
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
},
// 默认打开eslint效验,如果需要关闭,设置成false即可
lintOnSave: false,
productionSourceMap: false,
devServer: {
open: true,
port: 8001,
overlay: {
errors: true,
warnings: true
},
proxy: {
'/dev': {
// http://localhost:8001/dev/login => http://192.168.10.126:8050/asset-admin/login
target: 'http://192.168.10.126:8050/asset-admin/', // 跨域请求的地址
changeOrigin: true, // 开启跨域
logLevel: 'debug',
// 路径重写
pathRewrite: {
'^/dev': ''
}
}
}
},
// configureWebpack: { // webpack 配置
// plugins: [
// new MonacoEditorPlugin({
// // https://github.com/Microsoft/monaco-editor-webpack-plugin#options
// languages: ['javascript', 'typescript', 'sql', 'java']
// })
// ]
// }
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
// 自定义TerserPlugin压缩配置
return {
optimization: {
minimize: true, // 启用压缩
minimizer: [
new TerserPlugin({
test: /\.js(\?.*)?$/i, // 匹配参与压缩的文件
terserOptions: {
compress: {
drop_console: true, // 移除console
drop_debugger: true, // 移除debugger
pure_funcs: ['console.log'] // 移除指定函数
}
}
})
]
}
}
}
}
}
方法3:JSONP
本质是利用script标签不受同源策略限制,需要注意只能get请求,长度有限制,然后就是回调函数名前后端保持一致
补充:跨域时,简单请求,和非简单请求的区别
简单请求的定义:
两个条件:
- 请求方法是:HEAD,GET,POST
- header中只能包含以下请求字段:
- Accpet
- Accpet-Language
- Conten-Language
- Content-Type(所指的媒体类型值仅仅限于下列三者之一)
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
浏览器不同的处理方式
对于简单请求来说,如果请求跨域,那么浏览器会放行让请求发出。浏览器会发出cors请求,并携带origin。然后浏览器会拦截响应,并检查返回的response的header中有没有Access-Control-Allow-Origin,要是其的值为*或者为请求的Origin值,表示该资源是共享的,浏览器将放行。
对于非简单请求来说,浏览器先发送OPTIONS 预检请求(preflight request)(对于OPTIONS请求浏览器将按照简单请求的方式处理)并附加如下两个请求头
- Access-Control-Request-Method:告诉服务器实际发送的HTTP请求方法。
- Access-Control-Request-Header:告诉服务器实际请求所携带的自定义的头部信息。
目的:保护客户端的安全,防止不受信任的网站利用用户的浏览器向其他网站发送恶意请求。
由服务器决定,该请求是否被允许。
详细请参考文档 HTTP之跨域_access-control-allow-credentials-CSDN博客
参考资料
Same-origin policy - Security on the web | MDN
Cross-Origin Resource Sharing (CORS) - HTTP | MDN
HTTP之跨域_access-control-allow-credentials-CSDN博客
浏览器跨域 Access-Control-Allow-Headers 问题当浏览器发送接口请求出现跨域问题时,目前的做法 - 掘金
node后端与Vue前端跨域处理方法详解_node.js_脚本之家
前端那些事(一)跨域浏览器基于 同源策略 做出的限制。 同源策略 限制了从同一个源加载的文档或脚本如何与来自另一个源的资 - 掘金