前端&后端解决跨域的方法

一、前端解决跨域

前端解决跨域问题有多种成熟方案,具体选择取决于你的开发环境、项目需求以及对安全性和兼容性的要求。下面这个表格汇总了主要的解决方法,帮你快速了解每种方案的核心原理和适用场景。

方法名称 核心原理 适用场景 前端工作量
CORS (跨域资源共享) 服务器设置特定的HTTP响应头(如 Access-Control-Allow-Origin),告知浏览器允许跨域请求。 前后端分离项目,后端可配合设置。生产环境的首选方案 几乎为零,主要在服务端配置。
开发服务器代理 (Proxy) 利用前端开发服务器(如webpack-dev-server, Vite)将API请求代理到同源的后端服务,绕过浏览器同源策略。 前端开发环境最常用、最方便的解决方案。 只需修改项目配置文件。
JSONP 利用 <script> 标签天然可跨域的特性,通过动态创建标签来获取数据,需服务器返回函数调用包装的数据。 仅支持GET请求,通常用于向老旧接口获取数据,现代开发中已较少使用。 需要编写特定回调逻辑。
Nginx反向代理 类似开发服务器代理,但由Nginx服务器完成请求转发。部署阶段常用。 生产环境部署,尤其是静态资源和服务由同一域名提供时。 需部署和配置Nginx服务器。
WebSocket WebSocket协议本身不受同源策略限制。 需要双向实时通信的应用(如聊天室、在线游戏)。 需改造为WebSocket通信模式。
postMessage HTML5 API,允许不同源的窗口/iframe间安全地进行消息传递-3-7 与嵌入的第三方页面或不同子域页面进行数据通信。 需在发送和接收方编写消息监听与发送代码。

🔧 开发环境首选:代理服务器

在开发阶段,你最方便的选择是使用构建工具自带的代理功能。这能让你像访问同源资源一样访问后端API,从而避免跨域问题。

以 Vue + Webpack 项目为例 ,在 vue.config.js 文件中进行配置:

java 复制代码
// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {  // 代理所有以 '/api' 开头的请求
        target: 'http://your-backend-server.com', // 后端服务器地址
        changeOrigin: true, // 修改请求头中的Origin为目标地址,确保后端能正确识别:cite[6]
        pathRewrite: {
          '^/api': '' // 重写路径,去掉请求路径中的 '/api' 前缀:cite[6]
        }
      }
    }
  }
}

配置后,你前端的请求 /api/users 会被代理到 http://your-backend-server.com/users

Vite 项目 的配置也很类似,在 vite.config.js 中设置:

javascript 复制代码
// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^/api/, ''),
      },
    },
  },
}

生产环境标准:CORS

CORS是W3C标准,也是生产环境下最规范、安全的跨域解决方案。它依赖于后端服务器设置特定的HTTP响应头。

常见的CORS响应头包括

  • Access-Control-Allow-Origin : 指定允许访问资源的源。* 表示允许任何源,但若请求需要携带凭证(如cookies),则不能使用 *,必须指定明确的域名。
  • Access-Control-Allow-Methods : 指定允许的HTTP方法,如 GET, POST, PUT, DELETE
  • Access-Control-Allow-Headers : 指定允许的请求头,如 Content-Type, Authorization
  • Access-Control-Allow-Credentials : 设置为 true 时,允许请求携带凭证信息。

Node.js (Express框架) 配置CORS示例

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

// 使用CORS中间件
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://your-frontend-domain.com'); // 允许的源,生产环境应具体指定
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.header('Access-Control-Allow-Credentials', 'true'); // 如果需要携带cookie
  next();
});

app.listen(3000);

传统方法:JSONP

JSONP是一种传统方法,主要用于GET请求。其原理是动态创建<script>标签,通过src属性发起请求,并指定一个回调函数名,服务器返回的数据会被该回调函数包装。

前端实现示例

ini 复制代码
function handleJSONP(data) {
  // 处理返回的数据
  console.log(data);
}

const script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleJSONP'; // 将回调函数名传递给服务器
document.head.appendChild(script);

服务器需要返回 handleJSONP({"key": "value"}) 格式的数据。需要注意的是,JSONP只支持GET请求,并且存在一定的安全风险(如XSS攻击)。

如何选择?

  • 开发阶段 :优先使用 开发服务器代理,配置简单,能极大提升开发效率。
  • 生产环境 :与后端协作,规范配置 CORS,这是最安全、标准的方式。
  • 特殊场景 :如需与嵌入的iframe通信,考虑 postMessage;需要实时双向通信,考虑WebSocket。

二、后端解决跨域

后端解决跨域是最推荐、最安全的方式,主要通过配置服务器来允许跨域请求。以下是各种后端框架的详细解决方案:

1. Spring Boot 解决方案

方案一:使用 @CrossOrigin 注解(方法级/类级)

less 复制代码
// 在控制器类上使用 - 该类所有接口支持跨域
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api")
public class UserController {
    
    // 或者在具体方法上使用
    @CrossOrigin(origins = "http://localhost:3000")
    @GetMapping("/users")
    public List<User> getUsers() {
        return userService.findAll();
    }
    
    // 更详细的配置
    @CrossOrigin(
        origins = {"http://localhost:3000", "https://example.com"},
        methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT},
        allowedHeaders = "*",
        allowCredentials = "true",
        maxAge = 3600
    )
    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
}

方案二:全局配置类(推荐使用

typescript 复制代码
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")  // 所有接口路径
                //.allowedOrigins("*")  // 允许所有域名(Spring Boot 2.4+ 已弃用)
                .allowedOriginPatterns("*")  // 使用模式匹配(推荐)
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")  // 允许方法
                .allowedHeaders("*")  // 允许所有头
                .allowCredentials(true)  // 允许凭证
                .maxAge(3600);  // 预检请求缓存时间
    }
}

方案三:使用 Filter 过滤器

arduino 复制代码
@Configuration
public class CorsFilterConfig {
    
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        
        // 设置允许的源
        config.addAllowedOriginPattern("*");
        // 或者指定具体域名
        // config.addAllowedOrigin("http://localhost:3000");
        // config.addAllowedOrigin("https://example.com");
        
        // 设置允许的方法
        config.addAllowedMethod("*");
        // 或者指定具体方法
        // config.addAllowedMethod("GET");
        // config.addAllowedMethod("POST");
        // config.addAllowedMethod("PUT");
        
        // 设置允许的头部
        config.addAllowedHeader("*");
        
        // 是否允许凭证
        config.setAllowCredentials(true);
        
        // 预检请求的有效期,单位秒
        config.setMaxAge(3600L);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        
        return new CorsFilter(source);
    }
}

方案四:手动处理 OPTIONS 预检请求

java 复制代码
@Component
public class SimpleCorsFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
            throws IOException, ServletException {
        
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;
        
        // 设置允许的源
        response.setHeader("Access-Control-Allow-Origin", "*");
        // 或者指定具体域名
        // response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
        
        // 设置允许的方法
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        
        // 设置允许的头部
        response.setHeader("Access-Control-Allow-Headers", 
            "Content-Type, Authorization, Content-Length, X-Requested-With");
        
        // 允许携带凭证
        response.setHeader("Access-Control-Allow-Credentials", "true");
        
        // 处理OPTIONS预检请求
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return;
        }
        
        chain.doFilter(req, res);
    }
}

2. 生产环境安全配置

安全的最佳实践配置

typescript 复制代码
@Configuration
public class ProductionCorsConfig implements WebMvcConfigurer {
    
    @Value("${cors.allowed.origins:}")
    private String[] allowedOrigins;
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")  // 只对API接口生效
                .allowedOrigins(allowedOrigins)  // 从配置文件中读取允许的域名
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("Content-Type", "Authorization", "X-Requested-With")
                .allowCredentials(true)
                .maxAge(1800);  // 30分钟
        
        // 可以对不同的路径设置不同的规则
        registry.addMapping("/public/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET")
                .allowCredentials(false);
    }
}

配置文件(application.yml)

yaml 复制代码
# 生产环境配置
cors:
  allowed:
    origins: 
      - "https://www.example.com"
      - "https://api.example.com"

# 开发环境配置
---
spring:
  profiles: dev
cors:
  allowed:
    origins: 
      - "http://localhost:3000"
      - "http://127.0.0.1:3000"
      - "http://localhost:8080"

3. 其他后端框架解决方案

Node.js (Express)

php 复制代码
const express = require('express');
const cors = require('cors');

const app = express();

// 简单的CORS配置
app.use(cors());

// 详细的CORS配置
app.use(cors({
    origin: ['http://localhost:3000', 'https://example.com'],
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
    maxAge: 3600
}));

// 或者手动设置响应头
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    res.header('Access-Control-Allow-Credentials', 'true');
    
    if (req.method === 'OPTIONS') {
        res.sendStatus(200);
    } else {
        next();
    }
});

Python (Django)

ini 复制代码
# settings.py
INSTALLED_APPS = [
    # ...
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # 尽量放在最前面
    # ...
]

# CORS配置
CORS_ALLOW_ALL_ORIGINS = True  # 开发环境使用

# 生产环境配置
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "https://example.com",
]

CORS_ALLOW_METHODS = [
    'GET',
    'POST',
    'PUT',
    'DELETE',
]

CORS_ALLOW_HEADERS = [
    'content-type',
    'authorization',
]

CORS_ALLOW_CREDENTIALS = True

PHP

php 复制代码
<?php
header("Access-Control-Allow-Origin: http://localhost:3000");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Max-Age: 3600");

// 处理OPTIONS预检请求
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
    http_response_code(200);
    exit();
}

// 你的业务逻辑...
?>

4. Nginx 反向代理解决方案

ini 复制代码
server {
    listen 80;
    server_name api.example.com;
    
    # 跨域配置
    location /api/ {
        # 设置跨域头
        add_header Access-Control-Allow-Origin 'http://localhost:3000' always;
        add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always;
        add_header Access-Control-Allow-Headers 'Content-Type, Authorization' always;
        add_header Access-Control-Allow-Credentials 'true' always;
        add_header Access-Control-Max-Age 3600 always;
        
        # 处理OPTIONS请求
        if ($request_method = 'OPTIONS') {
            return 204;
        }
        
        # 代理到实际的后端服务
        proxy_pass http://backend-server;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

5. 常见问题解决方案

typescript 复制代码
// 当需要携带Cookie时,不能使用通配符 *
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                // .allowedOrigins("*")  // 错误!与allowCredentials(true)冲突
                .allowedOriginPatterns("*")  // 或者指定具体域名
                .allowCredentials(true)  // 允许携带凭证
                // ... 其他配置
    }
}

问题2:自定义头部字段

arduino 复制代码
// 如果前端使用了自定义头部字段
registry.addMapping("/**")
        .allowedHeaders("Content-Type", "Authorization", "X-Custom-Header")
        .exposedHeaders("X-Custom-Header");  // 允许前端访问的自定义响应头

6. 最佳实践总结

  1. 开发环境:使用宽松配置,方便调试
  2. 生产环境:严格限制允许的域名和方法
  3. 推荐使用全局配置:统一管理,避免遗漏
  4. 安全考虑 :不要随意使用 allowedOrigins("*")
  5. 性能优化 :合理设置 maxAge 减少预检请求

Spring Boot 项目最推荐使用方案二(全局配置类) ,这种方式既灵活又易于维护,能够满足大多数项目的跨域需求。

相关推荐
GetcharZp21 分钟前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑1 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯2 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan4 小时前
多Agent之间的区别
后端
kyriewen6 小时前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试
杨充6 小时前
1.面向对象设计思想
后端
IT_陈寒6 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro7 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
小林攻城狮7 小时前
使用 Transport 节流解决 Vercel AI SDK 流式渲染卡死问题
前端·react.js
要阿尔卑斯吗7 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端