当前位置: 首页 > news >正文

打破同源策略:前端跨域的全面解析与应对策略

跨域定义

跨域(Cross-Origin)指浏览器出于安全考虑,限制网页脚本向不同源(协议、域名、端口)的服务器发起 HTTP 请求。这是由浏览器的同源策略(Same-Origin Policy)引起的安全机制。

跨域原因

当以下任意一项不匹配时即产生跨域:

  • 协议不同(http vs https)

  • 域名不同(www.a.com vs www.b.com)

  • 端口不同(8080 vs 3000)

跨域现象

  • 控制台报错:Access to XMLHttpRequest at 'xxx' from origin 'yyy' has been blocked by CORS policy

    Access to XMLHttpRequest at 'http://api.example.com/data' from origin 'http://localhost:3000' 
    has been blocked by CORS policy: 
    No 'Access-Control-Allow-Origin' header is present on the requested resource.
  • 请求状态码可能显示为 200 但数据无法获取

  • 网络请求显示 (blocked:origin) 标记


主流解决方案及代码示例

方案 1:CORS(跨域资源共享) ★ 最常用

原理:服务端通过设置 HTTP 响应头告知浏览器允许跨域请求

Spring Boot 全局配置 ★ 推荐

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")      // 匹配所有路由
                .allowedOrigins("*")     // 允许所有来源(生产环境建议改为具体域名)
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的方法
                .allowedHeaders("*")    // 允许所有请求头
                .allowCredentials(false) // 是否允许发送 Cookie(设置为 true 时 allowedOrigins 不能为 *)
                .maxAge(3600);           // 预检请求缓存时间(秒)
    }
}

Node.js服务端代码

// 服务端代码(Node.js示例)
const http = require('http');
const server = http.createServer((req, res) => {
  // 设置允许跨域的域名,* 代表允许任意域名
  res.setHeader('Access-Control-Allow-Origin', '*');
  
  // 允许的请求方法(根据需求调整)
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  
  // 允许携带的请求头(根据需求调整)
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
  
  // 处理预检请求(OPTIONS)
  if (req.method === 'OPTIONS') {
    res.writeHead(204); // 空响应
    res.end();
    return;
  }
  
  // 正常业务处理
  res.end('Hello CORS!');
});

server.listen(3000);

关键配置项说明

响应头作用说明
Access-Control-Allow-Origin允许的源(* 表示全部,生产环境建议指定具体域名)
Access-Control-Allow-Methods允许的 HTTP 方法(GET/POST 等)
Access-Control-Allow-Headers允许携带的请求头(如 Content-Type、Authorization 等自定义头)
Access-Control-Allow-Credentials是否允许发送 Cookie(需与前端 withCredentials 配置配合使用)
Access-Control-Max-Age预检请求的缓存时间(减少 OPTIONS 请求次数)

注意事项

  1. 生产环境安全:不要长期使用 allowedOrigins("*")应替换为具体的前端域名

  2. 携带 Cookie:若需要携带 Cookie,需同时设置:

    .allowCredentials(true)
    .allowedOrigins("http://your-frontend-domain.com") // 必须明确指定域名
  3. 复杂请求:对于 PUT/DELETE 或自定义请求头的请求,浏览器会先发送 OPTIONS 预检请求

  4. Spring Security:若使用安全框架,需要在安全配置中放行 OPTIONS 请求

完整请求流程示例 

浏览器 → 发送 GET 请求 → 服务器
       ← 返回 CORS 头 ←

方案 2:JSONP(仅限 GET 请求,历史解决方案,可忽略)

原理:利用 <script> 标签没有跨域限制的特性

// 前端代码
function jsonpRequest(url, callbackName) {
  // 创建 script 标签
  const script = document.createElement('script');
  
  // 构造带回调函数名的请求 URL
  script.src = `${url}?callback=${callbackName}`;
  
  // 定义全局回调函数
  window[callbackName] = (data) => {
    console.log('Received data:', data);
    // 清理工作
    document.body.removeChild(script);
    delete window[callbackName];
  };
  
  // 发起请求
  document.body.appendChild(script);
}

// 使用示例
jsonpRequest('http://api.example.com/data', 'handleData');

方案 3:代理服务器 ★ 重要

原理:前端请求同源服务器,由服务器转发请求

3.1 Vue 开发环境代理(基于 vue-cli 或 vite

1. vue.config.js 配置(Webpack)

// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {  // 匹配所有以 /api 开头的请求
        target: 'http://backend-server.com',  // 目标服务器地址
        changeOrigin: true,  // 修改请求头中的 Origin 为目标地址
        pathRewrite: {
          '^/api': ''  // 将 /api 前缀替换为空字符串(可选)
        },
        secure: false,  // 如果目标使用 HTTPS 且证书无效,需设置为 false
        ws: true  // 代理 WebSocket
      }
    }
  }
}

2. Vite 配置(vite.config.js

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://backend-server.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')  // 路径重写
      }
    }
  }
})

3.2 React 开发环境代理(基于 create-react-app

1. 快速配置(无需 eject)

在 package.json 中添加:

{
  "proxy": "http://backend-server.com"  // 简单代理所有非静态资源请求
}

 2. 高级配置(自定义 setupProxy.js

// src/setupProxy.js(需安装 http-proxy-middleware)
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://backend-server.com',
      changeOrigin: true,
      pathRewrite: { '^/api': '' },  // 路径重写
      onProxyRes: (proxyRes) => {  // 可监听代理响应
        console.log('Proxying request to:', proxyRes.req.path);
      }
    })
  );
};

3.3 Nginx 生产环境代理配置

1. 基础反向代理配置 ★ 重要

# 定义 HTTP 服务器监听配置
server {
    # 监听 80 端口(HTTP 协议默认端口)
    listen 80;
    # 绑定的域名(生产环境替换为实际域名,支持通配符如 *.example.com)
    server_name your-domain.com;

    # API 请求代理配置块(匹配所有以 /api/ 开头的请求)
    location /api/ {
        # 反向代理到后端服务器(注意结尾的 / 会自动去除 /api 前缀)
        # 示例:请求 /api/user → 转发到 http://backend-server.com/user
        proxy_pass http://backend-server.com/;

        # ---------- 核心代理头配置 ----------
        # 传递原始请求的 Host 头(后端可通过此头识别原始域名)
        proxy_set_header Host $host;
        # 传递客户端真实 IP(后端可获取用户真实 IP)
        proxy_set_header X-Real-IP $remote_addr;
        # 传递经过的代理服务器 IP 链(用于追踪请求路径)
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # ---------- 跨域响应头配置(若后端未处理 CORS 则必需)----------
        # 允许所有来源访问(生产环境应替换为具体前端域名,如 http://your-frontend.com)
        add_header Access-Control-Allow-Origin *;
        # 允许的 HTTP 方法(根据实际需求调整)
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        # 允许携带的请求头(需包含实际使用的自定义头,如 Authorization)
        add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

        # ---------- 处理 OPTIONS 预检请求 ----------
        # 当请求方法为 OPTIONS 时(浏览器自动发起的预检请求)
       

常见问题处理

现象解决方案
前端路由刷新 404确认 try_files 配置正确指向前端入口文件
OPTIONS 请求返回 404检查 proxy_pass 地址是否可访问,确保后端服务正确处理 OPTIONS 方法
无法获取真实客户端 IP在后端服务中检查 X-Real-IP 或 X-Forwarded-For 头
静态资源加载失败检查 root 路径权限,确认文件已正确部署到 /usr/share/nginx/html 目录
CORS 头未生效确保未在后端重复设置 CORS 头(否则可能产生冲突)

2. 带 WebSocket 代理的配置

location /ws/ {
    proxy_pass http://websocket-server.com/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;  # 关键配置
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $host;
}

不同场景对比

方案适用阶段优势注意事项
Vue/React 代理开发环境无需后端配合,热更新即时生效生产环境需部署到服务器
Nginx 代理生产环境高性能、支持负载均衡、可配置性强需要服务器运维知识
Node 中间件代理全阶段灵活处理请求(可添加鉴权等逻辑)需要额外开发中间件代码

关键配置说明

  1. changeOrigin 的作用

    • true:将请求头中的 Host 改为目标服务器地址

    • false:保持原始 Host(可能导致目标服务器拒绝)

  2. 路径重写规则

    pathRewrite: { '^/api': '' }  // 请求 /api/user → 转发到 /user
  3. 生产环境安全建议

  • 不要使用 Access-Control-Allow-Origin: *

  • 限制允许的 HTTP 方法

    add_header Access-Control-Allow-Origin https://your-frontend.com;
    add_header Access-Control-Allow-Methods 'GET, POST';

验证代理是否生效

  1. 浏览器控制台检查

    # 原始请求
    fetch('/api/data')
    
    # 实际网络请求显示
    Request URL: http://localhost:8080/api/data
    Proxied to: http://backend-server.com/data
  2. cURL 测试

    curl -I http://your-domain.com/api/ping
    # 检查返回的 Server 头是否为 Nginx

方案 4:WebSocket (了解)

原理:WebSocket 协议不受同源策略限制

// 前端代码
const socket = new WebSocket('ws://echo.websocket.org');

socket.onopen = () => {
  socket.send('Hello Server!');
};

socket.onmessage = (event) => {
  console.log('Received:', event.data);
};

方案 5:postMessage (了解)

原理:跨文档通信 API

// 发送方(例如 iframe 中的页面)
window.parent.postMessage('Hello Parent!', 'http://parent-domain.com');

// 接收方(父页面)
window.addEventListener('message', (event) => {
  // 验证来源
  if (event.origin !== 'http://child-domain.com') return;
  console.log('Received:', event.data);
});

各方案对比

方案优点缺点适用场景
CORS标准方案,安全性高需要服务端配合主流跨域解决方案
JSONP兼容性好仅支持 GET,存在安全隐患老项目兼容
代理服务器前端无需修改代码需要服务器支持开发环境/有服务器控制权
WebSocket双向通信,无跨域限制协议切换成本高实时通信场景
postMessage可跨域窗口通信只能窗口间通信iframe/多窗口交互

总结

建议优先使用 CORS 方案,开发环境可配合代理服务器,老项目考虑 JSONP,特殊场景选择其他方案

相关文章:

  • MIPI 详解:XAPP894 D-PHY Solutions
  • 深入理解Java的 JIT(即时编译器)
  • 操作系统(第三章 内存管理)
  • 计算机三级网络技术知识汇总【10】
  • AtCoderABC387题解
  • Java复习
  • 透析Vue的nextTick原理
  • tryhackme——Password Attacks
  • 考研c语言复习之栈
  • CMS网站模板定制设计与安全评估
  • 基于CAMEL 的Workforce 实现多智能体协同工作系统
  • Guava:Google开源的Java工具库,太强大了
  • ZCS的随机游走的题解
  • 用Llama 3微调私有知识库:本地部署避坑指南
  • 大屏技术汇集【目录】
  • CMake 函数和宏
  • 34-三数之和
  • 应用案例 | 核能工业:M-PM助力核工业科研项目
  • 华为网路设备学习-16 虚拟路由器冗余协议(VRRP)
  • vue设置自定义logo跟标题
  • 上海“电子支付费率成本为0”背后:金融服务不仅“快”和“省”,更有“稳”和“准”
  • 讲座预告|全球贸易不确定情况下企业创新生态构建
  • 马上评丨行人转身相撞案:走路该保持“安全距离”吗
  • 中国海警依法驱离日非法进入我钓鱼岛领海船只
  • 习近平出席俄罗斯总统举行的欢迎仪式
  • 欧派家居:一季度营收降4.8%,目前海外业务整体体量仍较小