跨域问题解析(上):JSONP、CORS与Node代理解决方案

什么是跨域

在Web开发中,跨域问题是一个常见的安全限制。让我们先理解什么是同源策略和跨域:

javascript 复制代码
http://192.168.31.45:8080/user

// 组成:协议号://域名:端口号/路径

同源策略:只有当协议号、域名和端口号都相同的地址,浏览器才认为是同源的。这是浏览器的一种安全机制,防止恶意网站窃取数据。

跨域:当请求的资源与当前页面的源不同时,后端返回给浏览器的数据会被浏览器的同源策略拦截下来,导致前端无法获取响应数据。

开发阶段的跨域解决方案

1. JSONP解决方案

JSONP(JSON with Padding)是一种利用<script>标签实现跨域请求的技术。

原理

  1. 借助script标签的src属性不受同源策略限制的特性
  2. 通过动态创建script标签发送请求
  3. 后端返回一个函数调用,前端预先定义该函数处理数据

前端实现

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSONP示例</title>
</head>
<body>
    <button id="btn">获取数据</button>

    <script>
        function jsonp(url, cb) {
            return new Promise((resolve, reject) => {
                const script = document.createElement('script');
                script.src = `${url}?cb=${cb}`; // 添加回调函数名参数
                document.body.appendChild(script);
                
                // 全局定义回调函数
                window[cb] = (data) => {
                    resolve(data);
                    document.body.removeChild(script);
                    delete window[cb];
                }
            });
        }

        document.getElementById('btn').addEventListener('click', () => {
            jsonp('http://localhost:3000', 'callback')
                .then((res) => {
                    console.log('后端返回结果:', res);
                });
        });
    </script>
</body>
</html>

后端实现

javascript 复制代码
const koa = require('koa');
const app = new koa();

app.use((ctx) => {
    const cb = ctx.query.cb; // 获取前端传递的回调函数名
    const data = '给前端的数据';
    ctx.body = `${cb}('${data}')`; // 返回函数调用字符串
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

JSONP的优缺点

  • 优点:
    • 兼容性好,支持老式浏览器
    • 实现简单
  • 缺点:
    • 只能发送GET请求
    • 需要后端配合特定格式返回
    • 安全性较低,容易受到XSS攻击

JSONP 工作原理详解

JSONP(JSON with Padding)是一种巧妙利用浏览器特性的跨域解决方案,其核心原理基于以下几个关键点:

首先,浏览器对<script>标签的src属性加载资源时不受同源策略限制。当我们在页面中动态创建<script>元素并设置其src属性时,浏览器会向目标URL发起GET请求,这与直接使用AJAX请求不同,不会触发跨域限制。

其次,JSONP利用了JavaScript的函数调用机制。前端在发起请求前,会在全局作用域(通常是window对象)上预先定义一个唯一的回调函数。这个函数名通过查询参数(通常命名为callback或cb)传递给后端,例如:http://api.example.com/data?callback=handleResponse

后端接收到请求后,会进行特殊处理:不是返回普通的JSON数据,而是返回一段JavaScript代码,这段代码是对前端预定义回调函数的调用,并将数据作为参数传入。例如返回的内容可能是:handleResponse({"data": "value"})

当浏览器接收到这个响应时,由于是通过<script>标签加载的内容,会立即执行这段JavaScript代码,从而触发预先定义的回调函数,实现了数据的传递。整个过程完全绕过了浏览器的同源策略限制,因为从浏览器的角度看,这只是一个普通的脚本加载过程。

2. CORS跨域资源共享

CORS(Cross-Origin Resource Sharing)是现代浏览器支持的标准跨域解决方案。

原理

  • 后端通过设置响应头告诉浏览器允许跨域请求
  • 浏览器根据响应头决定是否允许前端获取响应

前端实现

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>CORS示例</title>
</head>
<body>
    <button id="btn">获取数据</button>
    
    <script>
        document.getElementById('btn').addEventListener('click', () => {
            fetch('http://localhost:3000')
                .then(res => res.json())
                .then(data => console.log(data))
                .catch(err => console.error(err));
        });
    </script>
</body>
</html>

后端实现

javascript 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
    // 设置CORS响应头
    res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500');
    res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    
    const data = { msg: 'Hello CORS' };
    res.end(JSON.stringify(data));
});

server.listen(3000, () => {
    console.log('Server is running on port 3000');
});

CORS的注意事项

  1. 简单请求与非简单请求:

    • 简单请求:GET/HEAD/POST,且Content-Type为text/plain、multipart/form-data或application/x-www-form-urlencoded
    • 非简单请求会先发送OPTIONS预检请求
  2. 常用响应头:

    • Access-Control-Allow-Origin: 允许的源
    • Access-Control-Allow-Methods: 允许的HTTP方法
    • Access-Control-Allow-Headers: 允许的请求头
    • Access-Control-Allow-Credentials: 是否允许发送Cookie

CORS 工作机制深入分析

跨域资源共享(CORS)是现代浏览器提供的标准化跨域解决方案,其工作原理可以分为几个层次:

浏览器在发送跨域请求时,会根据请求类型自动添加Origin头,标明请求来源。对于简单请求(如GET/HEAD/POST且使用特定Content-Type),浏览器会直接发送请求,但会检查响应中的Access-Control-Allow-Origin头。如果该头不包含当前源或通配符,浏览器会阻止前端JavaScript访问响应内容。

对于非简单请求(如PUT/DELETE方法或使用自定义头),浏览器会先发送一个OPTIONS预检请求。这个预检请求包含:

  • Access-Control-Request-Method:声明实际请求将使用的方法
  • Access-Control-Request-Headers:声明实际请求将携带的自定义头
  • Origin:请求来源

服务器需要正确响应这个预检请求,在响应中包含:

  • Access-Control-Allow-Origin:允许的源
  • Access-Control-Allow-Methods:允许的方法
  • Access-Control-Allow-Headers:允许的头
  • Access-Control-Max-Age:预检响应缓存时间

只有预检请求通过后,浏览器才会发送实际请求。整个过程由浏览器自动处理,对开发者透明,但要求后端必须正确配置CORS头。

3. Node代理解决方案

在开发环境下,我们可以利用构建工具提供的代理功能解决跨域问题。

Vite配置示例

javascript 复制代码
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000', // 后端地址
        changeOrigin: true, // 修改请求头中的host为目标地址
        rewrite: (path) => path.replace(/^\/api/, '') // 重写路径
      }
    }
  }
});

前端调用

javascript 复制代码
// 实际请求的是 /api,但会被代理到 http://localhost:3000
fetch('/api/data')
  .then(res => res.json())
  .then(data => console.log(data));

后端实现

javascript 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
    if (req.url === '/data') {
        const data = { msg: 'Hello Node Proxy' };
        res.end(JSON.stringify(data));
    }
});

server.listen(3000, () => {
    console.log('Server is running on port 3000');
});

代理工作原理

  1. 前端请求开发服务器的/api路径
  2. 开发服务器(Node)作为中间代理将请求转发到目标服务器
  3. 目标服务器返回响应给开发服务器
  4. 开发服务器将响应返回给前端

优点

  • 前端代码无需修改,保持生产环境相同的请求路径
  • 只在开发环境使用,不影响生产环境配置
  • 支持所有HTTP方法

Node代理的核心原理

Node代理解决跨域问题的本质是"中间人"模式,其工作原理如下:

在开发环境下,前端应用通常运行在开发服务器(如Vite/Webpack Dev Server)上,这些工具内置了Node.js代理功能。当前端代码发起向/api/xxx的请求时,请求首先被发送到开发服务器。

开发服务器收到请求后,会根据配置的代理规则,将请求转发到实际的后端服务器。这个转发过程发生在服务器之间,而服务器间的HTTP通信不受浏览器同源策略的限制。代理服务器会:

  1. 修改请求头中的Host为目标服务器地址
  2. 可选地重写请求路径(如去掉/api前缀)
  3. 转发请求到目标服务器

目标服务器处理请求后,将响应返回给代理服务器,代理服务器再原样返回给前端浏览器。对浏览器而言,它只与同源的开发服务器通信,完全感知不到背后的跨域请求,从而完美规避了同源策略限制。

这种方案的另一个优势是保持开发环境和生产环境的API路径一致。在生产环境,可以通过Nginx等Web服务器实现相同的代理功能,而前端代码无需任何修改。

小结

本文介绍了三种开发阶段常用的跨域解决方案:

  1. JSONP:利用script标签实现跨域,兼容性好但功能有限
  2. CORS:通过HTTP头实现跨域,是现代Web应用的首选方案
  3. Node代理:开发环境下通过中间层转发请求,无缝对接前端开发

下一篇文章我们将继续探讨nginx代理、domain修改和postMessage这三种跨域解决方案,以及它们在生产环境中的应用场景和实现方式。

相关推荐
zhangxingchao1 小时前
Flutter中的页面跳转
前端
烛阴1 小时前
Puppeteer入门指南:掌控浏览器,开启自动化新时代
前端·javascript
全宝2 小时前
🖲️一行代码实现鼠标换肤
前端·css·html
小小小小宇3 小时前
前端模拟一个setTimeout
前端
萌萌哒草头将军3 小时前
🚀🚀🚀 不要只知道 Vite 了,可以看看 Farm ,Rust 编写的快速且一致的打包工具
前端·vue.js·react.js
芝士加4 小时前
Playwright vs MidScene:自动化工具“双雄”谁更适合你?
前端·javascript
Carlos_sam5 小时前
OpenLayers:封装一个自定义罗盘控件
前端·javascript
前端南玖5 小时前
深入Vue3响应式:手写实现reactive与ref
前端·javascript·vue.js
wordbaby5 小时前
React Router 双重加载器机制:服务端 loader 与客户端 clientLoader 完整解析
前端·react.js
itslife5 小时前
Fiber 架构
前端·react.js