跨域解决方案梳理

从浏览器的同源策略说起

浏览器为确保资源安全,而遵循的一种策略,它限制从一个源加载的网页如何与另一个源的资源进行交互,以防止恶意网站窃取用户的敏感数据。

若没有同源策略,浏览器很容易受到XSS、CSRF等攻击。

其核心规则是: "同源"需满足以下三个条件

  1. 协议相同 (如 httphttps 不同源);
  2. 域名相同 (如 example.comapi.example.com 不同源);
  3. 端口相同 (如 example.com:80example.com:8080 不同源)。

受限制的操作


什么是跨域?

当请求的目标与当前页面不满足同源条件时,即发生跨域。

常见跨域场景

当前页面URL 目标URL 是否跨域 原因
http://example.com/a http://example.com/b 同协议、域名、端口
https://example.com http://example.com 协议不同(https vs http
http://example.com http://api.example.com 域名不同(子域名差异)
http://example.com:80 http://example.com:8080 端口不同

子域名不同是否算跨域?

是的,子域名不同属于跨域。例如:

  • www.example.comapi.example.com 是不同源(主域名相同但子域名不同);
  • blog.example.comshop.example.com 也是跨域。

特别说明:

1.跨域限制仅存在浏览器端,服务端不存在跨域限制。 2.即使跨域,Ajax请求也可以正常发出,但响应数据不会交给开发者。


CORS

CORS概述

CORS 全称:Cross-Origin Resource Sharing (跨域资源共享),是用于控制浏览器校验跨域请求 的一套规范,服务器依照CORS规范,添加特定响应头来控制浏览器校验,大致规则如下:

  • 服务器明确表示拒绝跨域 请求,或没有表示 ,则浏览器校验不通过
  • 服务器明确表示允许跨域 请求,则浏览器校验通过

备注说明:使用CORS解决跨域是最正统的方式,且要求服务器是"自己人"

常见的响应头设置:

  1. Access-Control-Allow-Origin : 这个头部指定允许访问资源的域。它可以设置为特定的域名,如https://example.com,也可以使用通配符*表示允许任何域访问。

  2. Access-Control-Allow-Methods: 该头部用于预检请求中,告知客户端允许的实际请求方法(如GET、POST、PUT等)。这在服务器响应OPTIONS请求时非常有用。

  3. Access-Control-Allow-Headers: 这个头部指定了除了简单的默认头部之外,哪些HTTP头部可以用于实际的请求。这对于非简单请求特别重要,因为它们可能包含自定义头部。

  4. Access-Control-Max-Age: 预检请求的结果可以被缓存多长时间,以秒为单位。这样可以避免频繁发送OPTIONS预检请求。

  5. Access-Control-Allow-Credentials : 表明是否允许发送凭据(包括Cookies和HTTP认证信息)作为请求的一部分。注意,如果设置此值为true,则Access-Control-Allow-Origin不能使用通配符*

简单请求和复杂请求

CORS会把请求分为两类,分别是:

1.简单请求

  • 方法(Method) :请求方法必须是 GETPOSTHEAD

  • 头部(Headers) :请求头必须是浏览器默认的几种头部,不能包含自定义头部。具体来说,只允许以下这些头部(除非 CORS 服务器允许):

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(仅限 text/plainmultipart/form-dataapplication/x-www-form-urlencoded

简记:只要不手动修改请求头,一般都能符合该规范。

2.复杂请求

  • 不是简单请求,就是复杂请求。
  • 复杂请求会自动发送预检请求

CORS解决简单请求

对于简单请求Access-Control-Allow-Origin 是必须的,这是跨域请求能否成功的关键。如果请求不涉及凭证,Access-Control-Allow-Origin 可以设置为 *,允许所有源进行跨域请求;如果请求涉及凭证,则必须指定明确的源。

如果只是在使用标准的跨域请求方法(GET, POST, HEAD),并且没有使用自定义的请求头部和特殊的 Content-Type,其他响应头部(如 Access-Control-Allow-MethodsAccess-Control-Allow-Headers 等)通常是可选的,浏览器会自动允许这些请求。

大致思路:

如果要发送cookie的话必须满足:

1.web 请求设置withCredentials

2.Access-Control-Allow-Credentialstrue

3.Access-Control-Allow-Origin为非 *

后面两个都是服务端配置

示例:

js 复制代码
var xhr = new XMLHttpRequest();
//简单跨域请求
xhr.open('POST', 'http://localhost:8088/data1', true);
//携带 cookie
xhr.withCredentials = true;
// 对于简单请求,如何设置了不符合简单请求的请求头,就会变成复杂请求
// xhr.setRequestHeader('abc', '123');
xhr.send(JSON.stringify({ key: 'value' }))

xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if (xhr.status >= 200 && xhr.status < 300) {
      console.log(xhr.responseText);
      console.log(xhr.status, 'CORS Preflight successful');
    } else {
      console.error('CORS Preflight failed');
    }
  }
}
js 复制代码
const http = require('http');

// 调用createServer创建http服务
const server = http.createServer((req, res) => {
    const headers = {
        'Access-Control-Allow-Origin': req.headers.origin || '*', 
        'Access-Control-Allow-Credentials': 'true',
    }
    if(req.method === 'GET' && req.url === '/data0') {
        res.writeHead(200, headers);
        res.end('GET 请求成功');
        return; // 终止请求处理
    }
    if(req.method === 'POST' && req.url === '/data1') {
        let body = []
        req
          .on('data',(chunk)=>{
            body.push(chunk) 
          })
          .on('end',()=>{
            console.log(body.toString())
            res.writeHead(201, headers);
            res.end('POST 请求成功');
          })

        return; // 终止请求处理 
    }
    res.writeHead(404, headers);
    res.end('404 Not Found');
});
server.listen(8088, () => {
  console.log('服务器启动成功!')
})

CORS解决复杂请求

对于复杂请求 ,浏览器在实际发出请求之前,会先发出 预检请求(preflight request)。服务器必须对预检请求做出正确响应,才能允许实际的跨域请求。

预检请求是一个 OPTIONS 请求,浏览器会询问服务器是否允许特定的跨域请求。预检请求会携带以下信息:

  • Access-Control-Request-Method :实际请求将使用的方法(例如,PUTDELETE)。
  • Access-Control-Request-Headers:实际请求中包含的自定义请求头。
  • Origin:发起请求的源。

服务器必须响应以下 CORS 响应头

  • Access-Control-Allow-Origin :指定允许跨域的源。如果服务器允许所有源,返回 *,或者可以指定具体的源(如 https://example.com)。

  • Access-Control-Allow-Methods :列出允许的 HTTP 方法。例如,PUTDELETEPATCH 等方法必须在此列出。

  • Access-Control-Allow-Headers:列出允许的自定义请求头。预检请求会告知服务器实际请求将携带哪些头部,服务器必须在响应中允许这些头部。

可选:

  • Access-Control-Max-Age:设置预检请求的结果可以缓存多长时间(以秒为单位)。通常设置为几小时,以减少频繁发送预检请求。

关于cookie的发送,与前面所诉一样。

示例:

js 复制代码
var xhr = new XMLHttpRequest();
// 复杂跨域请求
xhr.open('PUT', 'http://localhost:3000/data', true);
xhr.setRequestHeader('Content-Type', 'application/json');
//自定义请求头,需要在Access-Control-Allow-Headers中显示声明
xhr.setRequestHeader('abc', '123');
xhr.withCredentials = true;
xhr.send(JSON.stringify({ key: 'value' }))

xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if (xhr.status >= 200 && xhr.status < 300) {
      console.log(xhr.responseText,'11');
      console.log(xhr.status, 'CORS Preflight successful');
    } else {
      console.error('CORS Preflight failed');
    }
  }
}
js 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
  const headers = {
    'Access-Control-Allow-Credentials': 'true',
    'Access-Control-Allow-Origin': req.headers.origin || '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Custom-Header,abc',
    'Access-Control-Max-Age': '86400',
  };

  if (req.method === 'OPTIONS') {
    // 预检请求 200 201 Created 204 No Content
    res.writeHead(204, headers);
    console.log('预检请求');
    res.end();
    return; // 终止请求处理,避免继续执行后续代码
  }

  if (req.method === 'PUT' && req.url === '/data') {
    res.writeHead(201, headers);
    res.end('PUT 请求成功');
    return; // 终止请求处理
  }
  res.writeHead(404, headers);
  res.end('404 Not Found');
});

server.listen(3000, () => {
  console.log('服务器正在监听端口 3000');
});

JSONP

JSONP 概述

JSONP 是利用了<script>标签可以跨域加载脚本,且不受严格限制的特性,可以说是程序员智慧的结晶,早期一些浏览器不支持 CORS 的时,可以靠 JSONP 解决跨域。

基本流程

  • 第一步: 客户端创建一个<script>标签,并将其src属性设置为包含跨域请求的 URL,同时准备一个回调函数,这个回调函数用于处理返回的数据。

  • 第二步: 服务端接收到请求后,将数据封装在回调函数中并返回。

  • 第三步: 客户端的回调函数被调用,数据以参数的形式传入回调函数。

缺点

  1. 仅支持GET请求:JSONP只能通过GET方法发送请求,无法使用POST、PUT、DELETE等其他HTTP方法。
  2. 错误处理困难:如果请求失败,很难获取详细的错误信息,浏览器通常只会显示脚本加载失败的通用错误。
  3. 安全问题: JSONP从外部域加载并执行JavaScript代码,容易遭到XSS攻击。
  4. 性能问题 :每个JSONP请求都需要动态创建<script>标签,可能影响页面性能。

实现案例可以参考我之前写的文章

postMessage

概述

postMessageHTML5 的跨文档消息传递 API ,用于在不同窗口、iframe 或不同源的网页之间安全地传递消息 ,常用于跨域通信

iframe 跨域通信

问题 :如果一个网站在 iframe 中嵌套了一个 不同源 的网页,默认情况下,它们不能直接访问彼此的 JavaScript 对象(受同源策略限制)。

解决方案 :使用 postMessage 让主页面和 iframe 之间安全通信。

示例:主页面向 iframe 发送消息

主页面(https://parent.com):

xml 复制代码
<iframe id="myFrame" src="https://child.com"></iframe>

<script>
    const iframe = document.getElementById("myFrame");
    
    // 向 iframe 发送消息
    iframe.onload = function() {
        iframe.contentWindow.postMessage("Hello from parent", "https://child.com");
    };

    // 监听 iframe 返回的消息
    window.addEventListener("message", (event) => {
        if (event.origin === "https://child.com") {
            console.log("Received from child:", event.data);
        }
    });
</script>

iframe(https://child.com)监听消息并回复

xml 复制代码
<script>
    window.addEventListener("message", (event) => {
        if (event.origin !== "https://parent.com") return; // 只接受特定来源的消息
        console.log("Received from parent:", event.data);
        
        // 回复消息给主页面
        event.source.postMessage("Hello from child", event.origin);
    });
</script>

websocket

WebSocket 也是用于通信的,但它与 HTTP 不同:

  1. 通信方式不同

    • XMLHttpRequestFetch API 是基于 HTTP 的请求-响应模式,请求数据后服务器返回结果,连接随即关闭。
    • WebSocket 是全双工通信协议 ,可以保持连接,客户端和服务器可以 随时相互推送数据,不像 HTTP 需要客户端主动发起请求。
  2. 不受同源策略限制的原因

    • WebSocket 使用 ws://wss:// 协议(安全 WebSocket)。
    • 在建立 WebSocket 连接时,客户端会先通过 HTTP 发送一次 Upgrade 请求 ,要求服务器将连接升级为 WebSocket 连接(即 Connection: Upgrade)。
    • 一旦连接成功,WebSocket 就不会再受同源策略的约束,因为它不再是普通的 HTTP 请求,而是一个持久化的双向通道。

案例:

服务端:

js 复制代码
const WebSocket = require('ws');
const http = require('http');

// 1. 创建一个 HTTP 服务器
// 2. 创建一个 WebSocket 服务器
// 3. 建立一次http连接

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('WebSocket server is running');
})


const wss = new WebSocket.Server({ 
    server,
    path: '/ws', // 与前端保持一致
 });

wss.on('connection', (ws) => {
  console.log('client connected');
  ws.on('message', (message) => {
    console.log('received: %s', message);
    // 向客户端发送消息
    ws.send('Hello from server!');
  });
   ws.send('Hello from server!');
})


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

客户端:

js 复制代码
// websocket 不受同源策略的影响
const ws = new WebSocket('ws://localhost:8080/ws');
ws.onopen = function () {
    console.log('连接成功');
    ws.send('hello from client');
}
ws.onmessage = function (e) {
    console.log('message from server',e.data); 
}

vite反向代理

反向代理是一种 服务器代理机制,即前端开发服务器通过代理将请求转发到目标服务器。这样,浏览器只会和开发服务器通信,而不直接请求跨域的后端服务器,从而避免跨域问题。

Vite 中配置反向代理

在 Vite 项目中,可以通过 vite.config.js 文件来配置代理。

js 复制代码
import { defineConfig } from 'vite'

export default defineConfig({
  server: {
    proxy: {
      // 代理 /api 路径到后端服务
      '/api': {
        target: 'http://localhost:5000',  // 后端服务的地址
        changeOrigin: true,              // 如果是跨域请求,设置为 true
        rewrite: (path) => path.replace(/^\/api/, ''),  // 可选:重写路径,去掉 /api 前缀
      },
    },
  },
})

总结

JSONP 通过动态 <script> 标签绕过同源限制,支持 GET 请求,安全性差。CORS 通过设置 HTTP 头部,允许跨域请求,现代浏览器广泛支持,是通信双方协商后的结果,算是真正的跨域请求解决方案。postMessage 用于不同源窗口之间安全通信,避免跨域问题。WebSocket 使用 HTTP 协议握手,连接后不受同源策略限制,适用于实时通信。Vite 反向代理 通过代理请求到目标服务器,解决开发时的跨域问题,简化配置。

相关推荐
Trouvaille ~4 分钟前
【MySQL篇】内外连接:多表关联的完整指南
android·数据库·mysql·面试·后端开发·dql·内外连接
Ruihong40 分钟前
你的 Vue 3 生命周期,VuReact 会编译成什么样的 React?
vue.js·react.js·面试
未秃头的程序猿40 分钟前
💥 MyBatis 面试连环炮:从源码原理到实战避坑,彻底拿下 Offer 通关秘籍
后端·面试·mybatis
over6971 小时前
面试官视角:TypeScript Pick 工具类型深度解析与手写实现
前端·面试
San301 小时前
从浏览器到 Node.js,这一次彻底搞懂 Event Loop 与异步模型
面试·node.js·浏览器
仟里码1 小时前
linux安装mysql,超级详细,不踩坑
面试
前端摸鱼匠2 小时前
【AI大模型春招面试题20】大模型训练中优化器(AdamW、SGD、RMSProp)的选择依据?
人工智能·ai·语言模型·面试·大模型·求职招聘
人道领域2 小时前
【LeetCode刷题日记】18.四数之和
算法·leetcode·面试
一块小土坷垃3 小时前
最近发现了一款很好玩的SBTI测试的APP
面试·职场和发展·sbti
zjeweler3 小时前
“网安+护网”终极300多问题面试笔记-2共3-计算机网络相关 - 好淘云
笔记·计算机网络·web安全·面试·职场和发展·护网行动·护网面试