一、问题描述

在前后端分离的项目中,前端应用(运行在 http://localhost:8174)向后端API(https://modal.sum.ait/api/)发起请求时,浏览器控制台出现以下CORS错误:

Access to XMLHttpRequest at 'https://modal.sum.ait/api/multiCreate/text2image?prompt=asf&style=&resolution='
from origin 'http://localhost:8174' has been blocked by CORS policy:
Request header field x-request-id is not allowed by Access-Control-Allow-Headers in preflight response.

关键错误信息Request header field x-request-id is not allowed by Access-Control-Allow-Headers in preflight response

二、问题原因分析

2.1 CORS 机制概述

CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种基于HTTP头的机制,它允许服务器声明哪些源站有权限访问服务器的资源。当浏览器检测到跨域请求时,会自动触发CORS机制。

2.2 预检请求(Preflight Request)

对于某些跨域请求,浏览器会先发送一个 OPTIONS 预检请求,以确认服务器是否允许该跨域请求。触发预检请求的条件包括:

  1. 使用了非简单请求方法:如 PUTDELETE 等(GETPOSTHEAD 为简单方法)
  2. 使用了非简单请求头:除了浏览器自动设置的头部和以下简单头部外的其他头部
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(仅限于 application/x-www-form-urlencodedmultipait/form-datatext/plain

2.3 本案例的问题根源

通过错误信息分析,问题的核心在于:

  1. 前端请求包含自定义头部:前端代码在发送请求时添加了 x-request-id 这个自定义请求头
  2. 服务器CORS配置不匹配:Nginx 的 CORS 配置中 Access-Control-Allow-Headers 字段没有包含 x-request-id
  3. 预检请求被拒绝:浏览器发送 OPTIONS 预检请求时,服务器返回的允许头列表中没有 x-request-id,导致预检失败,真正的请求无法发送

三、详细排查过程

3.1 网络架构分析

根据提供的配置,整体架构如下:

前端(localhost:8174)
    ↓
反向代理服务器(45.248.133.102) - modal.sum.ait:443
    ↓
后端应用服务器(45.248.133.103:8080)
    ↓
后端服务(localhost:8082)

3.2 请求流程追踪

  1. 前端发起请求

    // 请求包含自定义头 x-request-id
    axios.get('/api/multiCreate/text2image', {
      headers: {
        'x-request-id': 'some-uuid'
      }
    })
    
  2. 浏览器发送 OPTIONS 预检请求

    OPTIONS /api/multiCreate/text2image HTTP/1.1
    Host: modal.sum.ait
    Origin: http://localhost:8174
    Access-Control-Request-Method: GET
    Access-Control-Request-Headers: x-request-id
    
  3. Nginx 返回 CORS 响应头(45.248.133.103 服务器配置):

    Access-Control-Allow-Origin: http://localhost:8174
    Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE
    Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
    
  4. 问题发现

    • 浏览器请求的头部:x-request-id
    • 服务器允许的头部:Content-Type, Authorization, X-Requested-With
    • x-request-id 不在允许列表中 → 预检失败 → 真实请求被浏览器阻止

3.3 配置对比分析

当前配置(45.248.133.103 服务器 /api/ location):

add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;

问题:缺少 x-request-id 头部的允许声明

四、解决方案

4.1 修改 Nginx 配置

45.248.133.103 服务器的 Nginx 配置文件中,找到多模态后端的 location /api/ 块,修改 Access-Control-Allow-Headers 配置。

方案一:添加 x-request-id(最小修改)

location /api/ {
    set $cors_origin "";
    if ($http_origin ~* "^http://localhost:[0-9]+$|^https://modal\.sum\.ait$|^https?://124\.71\.115\.96(:[0-9]+)?$|^https?://124\.71\.115\.99(:[0-9]+)?$") {
        set $cors_origin $http_origin;
    }

    add_header 'Access-Control-Allow-Origin' $cors_origin always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
    # 添加 x-request-id 到允许的请求头列表
    add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, x-request-id' always;

    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' $cors_origin;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
        # OPTIONS 预检请求也需要添加
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, x-request-id';
        add_header 'Access-Control-Max-Age' '3600';
        add_header 'Content-Length' '0';
        add_header 'Content-Type' 'text/plain';
        return 204;
    }

    proxy_pass http://localhost:8082/;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

方案二:添加更多常见请求头(推荐)

考虑到未来可能添加其他自定义头部,建议添加更全面的头部支持:

location /api/ {
    set $cors_origin "";
    if ($http_origin ~* "^http://localhost:[0-9]+$|^https://modal\.sum\.ait$|^https?://124\.71\.115\.96(:[0-9]+)?$|^https?://124\.71\.115\.99(:[0-9]+)?$") {
        set $cors_origin $http_origin;
    }

    add_header 'Access-Control-Allow-Origin' $cors_origin always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH' always;
    # 添加更全面的请求头支持
    add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, x-request-id, Accept, Origin, Cache-Control, X-File-Name' always;
    # 允许浏览器读取的响应头
    add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range, X-Request-Id' always;
    # 允许携带认证信息(如需要)
    # add_header 'Access-Control-Allow-Credentials' 'true' always;

    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' $cors_origin;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, x-request-id, Accept, Origin, Cache-Control, X-File-Name';
        add_header 'Access-Control-Max-Age' '86400';  # 预检请求缓存24小时
        add_header 'Content-Length' '0';
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        return 204;
    }

    proxy_pass http://localhost:8082/;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

4.2 配置变更步骤

  1. 备份当前配置

    sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup.$(date +%Y%m%d_%H%M%S)
    
  2. 编辑配置文件

    sudo vim /etc/nginx/nginx.conf
    # 或使用其他编辑器
    sudo nano /etc/nginx/nginx.conf
    
  3. 验证配置语法

    sudo nginx -t
    

    期望输出:

    nginx: configuration file /etc/nginx/nginx.conf test is successful
    
  4. 重载 Nginx 配置

    sudo systemctl reload nginx
    # 或
    sudo nginx -s reload
    
  5. 验证服务状态

    sudo systemctl status nginx
    

4.3 验证修复效果

修改配置并重载 Nginx 后,可以通过以下方式验证:

  1. 浏览器开发者工具

    • 打开 Network 标签
    • 查找 OPTIONS 预检请求
    • 检查响应头中的 Access-Control-Allow-Headers 是否包含 x-request-id
  2. curl 命令测试

    curl -X OPTIONS https://modal.sum.ait/api/multiCreate/text2image \
      -H "Origin: http://localhost:8174" \
      -H "Access-Control-Request-Method: GET" \
      -H "Access-Control-Request-Headers: x-request-id" \
      -v
    

    检查响应头中是否包含:

    Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, x-request-id, ...
    
  3. 前端应用测试

    • 清除浏览器缓存
    • 重新发起请求
    • 确认请求成功,无 CORS 错误

五、最佳实践建议

5.1 CORS 配置原则

  1. 最小权限原则:只允许必要的源、方法和头部
  2. 显式声明:明确列出允许的头部,避免使用 * 通配符(除非开发环境)
  3. 安全考虑
    • 生产环境不要使用 Access-Control-Allow-Origin: *
    • 如需凭证(Cookie),必须设置具体的 Origin,不能使用 *
    • 合理设置 Access-Control-Max-Age 减少预检请求

5.2 调试技巧

  1. 查看完整的请求和响应头:使用浏览器 DevTools 的 Network 面板
  2. 区分简单请求和预检请求
    • 简单请求直接发送,只检查响应头
    • 复杂请求先发 OPTIONS,再发实际请求
  3. 检查 Nginx 日志
    sudo tail -f /var/log/nginx/error.log
    sudo tail -f /var/log/nginx/access.log
    

5.3 前端配置建议

如果可以修改前端代码,建议统一管理自定义请求头:

// axios 配置示例
import axios from 'axios';
import { v4 as uuidv4 } from 'uuid';

const apiClient = axios.create({
  baseURL: 'https://modal.sum.ait/api',
  timeout: 30000,
});

// 请求拦截器:统一添加自定义头部
apiClient.interceptors.request.use(config => {
  config.headers['x-request-id'] = uuidv4();
  return config;
});

export default apiClient;

5.4 多层代理的注意事项

本案例中存在两层 Nginx 代理:

  • 45.248.133.102(外层代理)→ 45.248.133.103(内层代理 + 应用服务器)

关键点

  1. CORS 配置应该在最终响应的服务器上设置(本例中是 45.248.133.103)
  2. 外层代理不应再添加 CORS 头,避免重复头部导致的问题
  3. 使用 proxy_pass 时,确保 Host 头正确传递

六、总结

6.1 问题核心

CORS 错误的本质是浏览器的安全机制阻止了不符合服务器 CORS 策略的跨域请求。本案例中,前端请求包含的 x-request-id 自定义头部未在服务器的 Access-Control-Allow-Headers 白名单中,导致预检请求失败。

6.2 解决关键

在 Nginx 的 CORS 配置中添加 x-request-idAccess-Control-Allow-Headers 列表,同时确保 OPTIONS 预检请求的响应头配置一致。

6.3 举一反三

遇到类似 CORS 错误时,排查步骤:

  1. 查看错误信息:确定是哪个头部、方法或源被拒绝
  2. 检查预检请求:在 Network 中找到 OPTIONS 请求,对比请求头和响应头
  3. 定位配置位置:找到处理该路径的服务器配置
  4. 修改并验证:添加缺失的配置项,重载服务,测试验证
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐