Skip to content

浏览器/网络问题

🌌跨域问题(Cross-Origin)

什么是跨域?

跨域(Cross-Origin)是指在浏览器中,当一个网页试图访问与自己不同源(协议、域名、端口号)的资源时,就会发生跨域请求。这是浏览器的一种安全机制,用于防止恶意网站窃取用户数据。

一、同源策略(Same-Origin Policy)

同源策略是浏览器最核心的安全机制之一。当两个 URL 的以下三个部分完全相同时,才被认为是同源:

  • 协议(Protocol):如 httphttps
  • 域名(Domain):如 example.com
  • 端口(Port):如 80443

示例

以下 URL 与 https://example.com 的比较:

URL是否同源原因
https://example.com/api✅ 是完全匹配
http://example.com❌ 否协议不同
https://api.example.com❌ 否域名不同
https://example.com:8080❌ 否端口不同

二、什么场景中会发生跨域?

跨域请求在以下场景中经常发生

  1. 前后端分离开发
  2. 调用第三方 API
  3. 使用 CDN 资源
  4. 微服务架构

常见的跨域场景

javascript
// 前端代码运行在(本地) http://localhost:3000
fetch('https://api.example.com/data') // 调用后端的API时,发生跨域请求
  .then((response) => response.json())
  .catch((error) => console.error('跨域请求失败:', error))

三、解决跨域的方案有哪些?

0. 禁用浏览器安全策略(不推荐)

警告

此方案仅建议在本地开发环境临时使用,禁止在生产环境使用,会带来严重的安全风险!

通过创建批处理文件(.bat)来启动浏览器并禁用安全策略:

batch
# 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

使用说明

  1. 将上述命令保存为 .bat 文件(如 disable-cors.bat
  2. 双击运行该文件启动浏览器
  3. 新启动的浏览器将禁用同源策略
  4. 建议使用独立的浏览器配置文件,避免影响正常浏览

注意事项

  • 此方法仅适用于本地开发调试
  • 会禁用浏览器的所有安全特性
  • 可能导致敏感信息泄露
  • 仅建议在隔离的开发环境中使用

1. CORS(跨域资源共享)

CORS 是最推荐的解决方案

CORS 是一种基于 HTTP 头的机制,允许服务器声明哪些源可以访问其资源。

javascript
// 服务器端设置 CORS 头
app.use(
  cors({
    origin: 'https://your-frontend-domain.com',
    methods: ['GET', 'POST'],
    allowedHeaders: ['Content-Type', 'Authorization'],
  }),
)

2. 代理服务器

开发环境常用方案

在开发环境中,可以通过配置代理服务器来转发请求,避免跨域问题。

javascript
// 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_HOST

3. JSONP

不推荐使用

JSONP 是一种过时的解决方案,仅支持 GET 请求,且存在安全风险。

4. postMessage

适用于跨窗口通信

适用于不同窗口/iframe 之间的通信场景。

javascript
// 发送消息
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 网关

五、注意事项

注意事项

  1. 安全性考虑

    • 生产环境不要使用 * 作为 CORS 的 origin
    • 敏感接口需要额外的安全措施,如使用HTTPS
    • 注意 CSRF 防护
  2. 性能优化

    • 合理使用 axios 配置请求和响应拦截器
    • 合理配置请求和响应超时的断开和重试机制
    • 合理使用请求缓存减少跨域请求,减少预检请求

六、总结回顾

思考问题

  1. 什么是跨域?为什么会出现跨域问题?
  2. 同源策略的三个要素是什么?
  3. 开发环境和生产环境分别推荐使用什么方案解决跨域?
  4. 为什么说 CORS 是最推荐的跨域解决方案?
  5. 跨域请求中需要注意哪些安全问题?
展开答案
  1. 什么是跨域?为什么会出现跨域问题?

    • 跨域是指浏览器出于安全考虑,限制不同源(协议、域名、端口)之间的资源访问
    • 出现跨域是为了防止恶意网站窃取用户数据,是浏览器的一种安全机制
  2. 同源策略的三个要素是什么?

    • 协议(Protocol):如 http、https
    • 域名(Domain):如 example.com
    • 端口(Port):如 80、443
    • 只有当这三个要素完全相同时,才被认为是同源
  3. 开发环境和生产环境分别推荐使用什么方案解决跨域?

    • 开发环境:使用代理服务器(如 webpack-dev-server 的 proxy)
    • 生产环境:使用 CORS 或 Nginx 反向代理
  4. 为什么说 CORS 是最推荐的跨域解决方案?

    • 是 W3C 标准,浏览器原生支持
    • 配置灵活,可以精确控制允许的源、方法和头部
    • 支持各种 HTTP 方法,不仅限于 GET
    • 可以携带认证信息(如 cookies)
  5. 跨域请求中需要注意哪些安全问题?

    • 生产环境不要使用 * 作为 CORS 的 origin
    • 敏感接口必须使用 HTTPS
    • 注意 CSRF 防护
    • 合理配置 CORS 策略,避免过度开放
    • 注意预检请求(OPTIONS)的性能影响

最佳实践总结

  1. 优先使用 CORS,这是最标准和安全的解决方案
  2. 开发环境使用代理服务器提高开发效率
  3. 避免使用 JSONP 等过时方案
  4. 始终注意安全性,合理配置 CORS 策略
  5. 考虑使用现代浏览器 API(如 Fetch API)的特性

七、推荐阅读

🌐 5 种 HTTP 数据传输方式

数据传输方式

对于前端来说,后端主要是提供 http 接口来传输数据,而这种数据传输的方式主要有 5 种:

  • url param
  • query
  • form-urlencoded
  • form-data
  • json

一、 url param

可以把参数写在 url

bash
http://test.lightmes.cn/person/123

这里的 123 就是路径中的参数(url param),服务端框架或者单页应用的路由都支持从 url 中取出参数。

二、 query

通过 url 中 ? 后面的用 & 分隔的字符串传递数据。

bash
http://test.lightmes.cn/person?name=xiaoming&age=20

这里的 name age 以及对应的值 age 就是 query 传递的数据。

其中非英文的字符和一些特殊字符要经过编码,可以使用 encodeURIComponent 的 api 来编码:

javascript
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 库来处理。

javascript
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 typeapplication/x-www-form-urlencoded

javascript
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。

javascript
const form = new FormData()
form.append('name', '小明')
form.append('age', 20)

fetch('http://test.lightmes.cn/person', {
  method: 'POST',
  body: form,
})

form-data 需要指定 content typemultipart/form-data,然后指定 boundary 也就是分割线。

body 里面就是用 boundary 分隔符分割的内容。

很明显,这种方式适合传输文件,而且可以传输多个文件

但是毕竟多了一些只是用来分隔的 boundary,所以请求体会增大。

五、 json

form-urlencoded 需要对内容做 url encode,而 form data 则需要加很长的 boundary,两种方式都有一些缺点。如果只是传输 json 数据的话,不需要用这两种。

可以直接指定content typeapplication/json,然后把 json 数据 stringify 就行:

javascript
fetch('http://test.lightmes.cn/person', {
  method: 'POST',
  body: JSON.stringify({
    name: '小明',
    age: 20,
  }),
  headers: {
    'Content-Type': 'application/json',
  },
})

我们平时传输 json 数据基本用的是这种。

树字标品MES 让智造简单些!