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

一、前端解决跨域

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

方法名称 核心原理 适用场景 前端工作量
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 项目最推荐使用方案二(全局配置类) ,这种方式既灵活又易于维护,能够满足大多数项目的跨域需求。

相关推荐
gplitems1232 小时前
Tripfery - Travel & Tour Booking WordPress Theme Tested
前端
滴水寸金2 小时前
优雅地构建动态、复杂且安全的 SQL 查询
后端
白水清风2 小时前
【基础】关于函数式编程的知识
前端·javascript·面试
滴水寸金2 小时前
讯飞语音转文本:定位阅读进度与高亮文本的技术实现
后端
蓝莓味的口香糖2 小时前
【JS】JS基础-对象处理方法整合
开发语言·前端·javascript
karry_k2 小时前
Java的类加载器
后端
ZZHHWW3 小时前
高性能架构01 -- 开篇
后端·架构
sanx183 小时前
一站式电竞平台解决方案:数据、直播、源码,助力业务飞速启航
前端·数据库·apache·数据库开发·时序数据库
余防3 小时前
存储型XSS,反射型xss
前端·安全·web安全·网络安全·xss