浏览器/网络问题
🌌跨域问题(Cross-Origin)
什么是跨域?
跨域(Cross-Origin)是指在浏览器中,当一个网页试图访问与自己不同源(协议、域名、端口号)的资源时,就会发生跨域请求。这是浏览器的一种安全机制,用于防止恶意网站窃取用户数据。
一、同源策略(Same-Origin Policy)
同源策略是浏览器最核心的安全机制之一。当两个 URL 的以下三个部分完全相同时,才被认为是同源:
- 协议(Protocol):如
http、https - 域名(Domain):如
example.com - 端口(Port):如
80、443
示例
以下 URL 与 https://example.com 的比较:
| URL | 是否同源 | 原因 |
|---|---|---|
https://example.com/api | ✅ 是 | 完全匹配 |
http://example.com | ❌ 否 | 协议不同 |
https://api.example.com | ❌ 否 | 域名不同 |
https://example.com:8080 | ❌ 否 | 端口不同 |
二、什么场景中会发生跨域?
跨域请求在以下场景中经常发生:
- 前后端分离开发
- 调用第三方 API
- 使用 CDN 资源
- 微服务架构
常见的跨域场景
// 前端代码运行在(本地) http://localhost:3000
fetch('https://api.example.com/data') // 调用后端的API时,发生跨域请求
.then((response) => response.json())
.catch((error) => console.error('跨域请求失败:', error))三、解决跨域的方案有哪些?
0. 禁用浏览器安全策略(不推荐)
警告
此方案仅建议在本地开发环境临时使用,禁止在生产环境使用,会带来严重的安全风险!
通过创建批处理文件(.bat)来启动浏览器并禁用安全策略:
# Chrome 浏览器(Windows)
@echo off
start chrome.exe --disable-web-security --user-data-dir="C:/ChromeDevSession"
# Edge 浏览器(Windows)
@echo off
start msedge.exe --disable-web-security --user-data-dir="C:/EdgeDevSession"
# Firefox 浏览器(Windows)
@echo off
start firefox.exe -P "dev" --disable-web-security使用说明
- 将上述命令保存为
.bat文件(如disable-cors.bat) - 双击运行该文件启动浏览器
- 新启动的浏览器将禁用同源策略
- 建议使用独立的浏览器配置文件,避免影响正常浏览
注意事项
- 此方法仅适用于本地开发调试
- 会禁用浏览器的所有安全特性
- 可能导致敏感信息泄露
- 仅建议在隔离的开发环境中使用
1. CORS(跨域资源共享)
CORS 是最推荐的解决方案
CORS 是一种基于 HTTP 头的机制,允许服务器声明哪些源可以访问其资源。
// 服务器端设置 CORS 头
app.use(
cors({
origin: 'https://your-frontend-domain.com',
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
}),
)2. 代理服务器
开发环境常用方案
在开发环境中,可以通过配置代理服务器来转发请求,避免跨域问题。
// 1.Webpack/Vue CLI 配置示例
module.exports = {
devServer: {
proxy: {
// 匹配所有以 '/dev-api' 开头的请求路径
'/dev-api': {
target: 'https://example.com', // 接口的域名(服务器)
pathRewrite: { '/dev-api': '' }, // 重写地址,将前缀 '/dev-api' 转为 '/'。
changeOrigin: true, // 加了这个属性,那后端收到的请求头中的host是目标地址 target
secure: false, // 如果是https接口,需要配置这个参数
},
},
},
}
// 2.修改Axios配置文件的请求
axios.defaults.baseURL =
process.env.NODE_ENV == 'production' ? '' : '/dev-api' + process.env.API_HOST3. JSONP
不推荐使用
JSONP 是一种过时的解决方案,仅支持 GET 请求,且存在安全风险。
4. postMessage
适用于跨窗口通信
适用于不同窗口/iframe 之间的通信场景。
// 发送消息
window.postMessage(
{
type: 'data',
payload: {
/* 数据 */
},
},
'https://target-domain.com',
)
// 接收消息
window.addEventListener('message', (event) => {
if (event.origin !== 'https://trusted-domain.com') return
// 处理消息
})四、前端项目跨域最佳实践
开发环境配置
- 使用
vue.config.js配置代理,避免跨域问题 - 利用 webpack-dev-server 的 proxy 功能
- 配置多个代理规则,适应不同环境
- 使用环境变量管理 API 地址
生产环境部署
- 前后端部署在同一域名下
- 使用 Nginx 反向代理
- 合理配置 CORS 策略,配置 CORS 响应头
- 使用 API 网关
五、注意事项
注意事项
安全性考虑
- 生产环境不要使用
*作为 CORS 的 origin - 敏感接口需要额外的安全措施,如使用HTTPS
- 注意 CSRF 防护
- 生产环境不要使用
性能优化
- 合理使用 axios 配置请求和响应拦截器
- 合理配置请求和响应超时的断开和重试机制
- 合理使用请求缓存减少跨域请求,减少预检请求
六、总结回顾
思考问题
- 什么是跨域?为什么会出现跨域问题?
- 同源策略的三个要素是什么?
- 开发环境和生产环境分别推荐使用什么方案解决跨域?
- 为什么说 CORS 是最推荐的跨域解决方案?
- 跨域请求中需要注意哪些安全问题?
展开答案
什么是跨域?为什么会出现跨域问题?
- 跨域是指浏览器出于安全考虑,限制不同源(协议、域名、端口)之间的资源访问
- 出现跨域是为了防止恶意网站窃取用户数据,是浏览器的一种安全机制
同源策略的三个要素是什么?
- 协议(Protocol):如 http、https
- 域名(Domain):如 example.com
- 端口(Port):如 80、443
- 只有当这三个要素完全相同时,才被认为是同源
开发环境和生产环境分别推荐使用什么方案解决跨域?
- 开发环境:使用代理服务器(如 webpack-dev-server 的 proxy)
- 生产环境:使用 CORS 或 Nginx 反向代理
为什么说 CORS 是最推荐的跨域解决方案?
- 是 W3C 标准,浏览器原生支持
- 配置灵活,可以精确控制允许的源、方法和头部
- 支持各种 HTTP 方法,不仅限于 GET
- 可以携带认证信息(如 cookies)
跨域请求中需要注意哪些安全问题?
- 生产环境不要使用
*作为 CORS 的 origin - 敏感接口必须使用 HTTPS
- 注意 CSRF 防护
- 合理配置 CORS 策略,避免过度开放
- 注意预检请求(OPTIONS)的性能影响
- 生产环境不要使用
最佳实践总结
- 优先使用 CORS,这是最标准和安全的解决方案
- 开发环境使用代理服务器提高开发效率
- 避免使用 JSONP 等过时方案
- 始终注意安全性,合理配置 CORS 策略
- 考虑使用现代浏览器 API(如 Fetch API)的特性
七、推荐阅读
官方文档
Vue2 相关文档
🌐 5 种 HTTP 数据传输方式
数据传输方式
对于前端来说,后端主要是提供 http 接口来传输数据,而这种数据传输的方式主要有 5 种:
- url param
- query
- form-urlencoded
- form-data
- json
一、 url param
可以把参数写在 url
http://test.lightmes.cn/person/123这里的 123 就是路径中的参数(url param),服务端框架或者单页应用的路由都支持从 url 中取出参数。
二、 query
通过 url 中 ? 后面的用 & 分隔的字符串传递数据。
http://test.lightmes.cn/person?name=xiaoming&age=20这里的 name 和 age 以及对应的值 age 就是 query 传递的数据。
其中非英文的字符和一些特殊字符要经过编码,可以使用 encodeURIComponent 的 api 来编码:
const query = '?name=' + encodeURIComponent('小明') + '&age=20'
// ?name=%123%123%123&age=20
// http://test.lightmes.cn/person?name=%123%123%123&age=20或者使用封装了一层的 query-string 库来处理。
const queryString = require('query-string')
queryString.stringify({
name: '小明',
age: 20,
})
// ?name=%123%123%123&age=20
// http://test.lightmes.cn/person?name=%123%123%123&age=20三、 form-urlencoded
直接用 form 表单提交数据就是这种,它和 query 字符串的方式的区别只是放在了 body 里,然后指定下 content type 是 application/x-www-form-urlencoded。
const form = new FormData()
form.append('name', '小明')
form.append('age', 20)
fetch('http://test.lightmes.cn/person', {
method: 'POST',
body: form,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
})因为内容也是 query 字符串,所以也要用 encodeURIComponent 的 api 或者 query-string 库处理下(同query)。
这种格式也很容易理解,get 是把数据拼成 query 字符串放在 url 后面,于是表单的 post 提交方式的时候就直接用相同的方式把数据放在了 body 里。
通过 & 分隔的 form-urlencoded 的方式需要对内容做 url encode,如果传递大量的数据,比如上传文件的时候就不是很合适了,因为文件 encode 一遍的话太慢了,这时候就可以用 form-data。
四、 form-data
form data 不再是通过 & 分隔数据,而是用 --------- + 一串数字做为 boundary 分隔符。因为不是 url 的方式了,自然也不用再做 url encode。
const form = new FormData()
form.append('name', '小明')
form.append('age', 20)
fetch('http://test.lightmes.cn/person', {
method: 'POST',
body: form,
})form-data 需要指定 content type 为 multipart/form-data,然后指定 boundary 也就是分割线。
body 里面就是用 boundary 分隔符分割的内容。
很明显,这种方式适合传输文件,而且可以传输多个文件。
但是毕竟多了一些只是用来分隔的 boundary,所以请求体会增大。
五、 json
form-urlencoded 需要对内容做 url encode,而 form data 则需要加很长的 boundary,两种方式都有一些缺点。如果只是传输 json 数据的话,不需要用这两种。
可以直接指定content type 为 application/json,然后把 json 数据 stringify 就行:
fetch('http://test.lightmes.cn/person', {
method: 'POST',
body: JSON.stringify({
name: '小明',
age: 20,
}),
headers: {
'Content-Type': 'application/json',
},
})我们平时传输 json 数据基本用的是这种。
