Nginx解决CORS跨域配置的指南

 更新时间:2026年07月11日 09:17:18   作者:知远漫谈  
Nginx作为高性能的反向代理服务器和 Web 服务器,是解决CORS跨域问题的工具之一,本篇将带你从零开始,深入理解 CORS 原理,掌握 Nginx 中 CORS 的完整配置方案,并结合 Java 后端实战案例,构建一个安全、灵活、可扩展的跨域解决方案

在现代 Web 开发中,前后端分离架构已成为主流。前端应用通常部署在独立的域名或端口上(如 https://frontend.example.com),而后端 API 服务则运行在另一个域(如 https://api.example.com)。当浏览器加载前端页面并尝试通过 JavaScript 调用跨域 API 时,会触发 同源策略(Same-Origin Policy) 的限制,导致请求被拦截,控制台报错:

Access to fetch at 'https://api.example.com/users' from origin 'https://frontend.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

这就是著名的 CORS(Cross-Origin Resource Sharing) 问题。而 Nginx,作为高性能的反向代理服务器和 Web 服务器,是解决这一问题最常用、最高效的工具之一。本篇将带你从零开始,深入理解 CORS 原理,掌握 Nginx 中 CORS 的完整配置方案,并结合 Java 后端实战案例,构建一个安全、灵活、可扩展的跨域解决方案。

一、CORS 是什么?为什么需要它?

1.1 同源策略的诞生

浏览器的同源策略是 Web 安全的基石之一。它规定:只有当协议、域名、端口三者完全相同时,页面脚本才能访问另一个页面的资源

例如:

URL是否同源原因
https://example.com✅ 同源
https://example.com/api✅ 同源路径不同不影响
http://example.com❌ 不同源协议不同(http vs https)
https://api.example.com❌ 不同源子域名不同
https://example.com:8080❌ 不同源端口不同

这种限制虽然保护了用户免受 XSS、CSRF 等攻击,但也给现代应用开发带来了挑战 —— 前后端分离架构下,前端与后端必然不在同一源。

1.2 CORS 的作用

CORS 是 W3C 制定的标准,允许服务器明确声明哪些外部源可以访问其资源。它通过在 HTTP 响应头中添加特定字段,告知浏览器:“你可以信任这个来源,允许它发起跨域请求”。CORS 不是浏览器的特性,而是服务器的权限声明机制。

1.3 CORS 请求类型

CORS 请求分为两类:

简单请求(Simple Request)

满足以下条件即为简单请求:

  • 方法:GETPOSTHEAD
  • 头部:仅限 AcceptAccept-LanguageContent-LanguageContent-Type(仅限 application/x-www-form-urlencodedmultipart/form-datatext/plain

示例:

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'Alice' })
})

注意:Content-Type: application/json 不是简单请求!它是复杂请求。

复杂请求(Preflighted Request)

不满足上述条件的请求,浏览器会先发送一个 OPTIONS 预检请求(Preflight Request),询问服务器是否允许该跨域请求。只有预检通过后,才会发送真实请求。

常见触发场景:

  • 使用 PUTDELETEPATCH 方法
  • 自定义请求头(如 AuthorizationX-API-Key
  • Content-Type: application/jsonapplication/xml

预检请求示例:

OPTIONS /users HTTP/1.1
Host: api.example.com
Origin: https://frontend.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Authorization, X-API-Key

服务器响应必须包含:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, X-API-Key
Access-Control-Max-Age: 86400

只有当这些头信息匹配时,浏览器才允许后续的 PUT 请求。

二、Nginx 作为 CORS 网关:为什么选它?

在微服务架构中,前端通常通过 Nginx 作为统一入口,代理多个后端服务。Nginx 拥有以下优势:

优势说明
性能极高基于事件驱动,单进程处理高并发,内存占用低
配置灵活支持基于 location、if、map 等精细控制
反向代理可统一代理多个后端服务,集中处理 CORS
安全可控可在入口层统一拦截非法请求,减少后端负担
无依赖不需修改后端代码,实现零侵入式跨域管理

最佳实践:将 CORS 配置放在 Nginx 层,而非后端(如 Spring Boot)。这样可以避免每个服务重复配置,提升维护性。

三、Nginx CORS 配置核心指令详解

Nginx 通过 add_header 指令设置响应头,从而实现 CORS 控制。以下是关键指令的详细说明:

指令作用示例
add_header Access-Control-Allow-Origin允许的源add_header Access-Control-Allow-Origin "https://frontend.example.com";
add_header Access-Control-Allow-Credentials是否允许携带凭证(Cookie、Authorization)add_header Access-Control-Allow-Credentials "true";
add_header Access-Control-Allow-Methods允许的 HTTP 方法add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers允许的请求头add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-API-Key";
add_header Access-Control-Expose-Headers允许前端 JS 访问的响应头add_header Access-Control-Expose-Headers "X-Total-Count, X-Request-ID";
add_header Access-Control-Max-Age预检请求缓存时间(秒)add_header Access-Control-Max-Age 86400;
if ($request_method = 'OPTIONS')处理预检请求,避免重复处理if ($request_method = 'OPTIONS') { ... }

基础配置模板(推荐)

location /api/ {
    # 代理到后端 Java 服务
    proxy_pass http://localhost:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # CORS 配置
    add_header Access-Control-Allow-Origin "https://frontend.example.com";
    add_header Access-Control-Allow-Credentials "true";
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
    add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-API-Key";

    # 预检请求直接返回 204
    if ($request_method = 'OPTIONS') {
        add_header Access-Control-Allow-Origin "https://frontend.example.com";
        add_header Access-Control-Allow-Credentials "true";
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
        add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-API-Key";
        add_header Access-Control-Max-Age 86400;
        add_header Content-Type 'text/plain; charset=utf-8';
        add_header Content-Length 0;
        return 204;
    }
}

为什么 OPTIONS 请求要单独处理?

因为如果不处理,Nginx 会将 OPTIONS 请求也转发给后端,而大多数 Java 服务(如 Spring Boot)默认不处理 OPTIONS,导致返回 405 或 500,前端报错。

四、实战场景一:单域名前端 + 单后端服务

假设我们有:

  • 前端:https://app.mycompany.com(Nginx 静态站点)
  • 后端:http://localhost:8080(Java Spring Boot 应用)
  • Nginx 监听 443,代理 /api/* 到后端

4.1 Nginx 配置文件(/etc/nginx/sites-available/api-proxy)

server {
    listen 443 ssl http2;
    server_name app.mycompany.com;

    ssl_certificate /etc/ssl/certs/app.mycompany.com.crt;
    ssl_certificate_key /etc/ssl/private/app.mycompany.com.key;

    # 静态资源
    location / {
        root /var/www/frontend;
        try_files $uri $uri/ /index.html;
    }

    # API 代理
    location /api/ {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # CORS 头
        add_header Access-Control-Allow-Origin "https://app.mycompany.com";
        add_header Access-Control-Allow-Credentials "true";
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
        add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-API-Key";

        # 预检请求处理
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin "https://app.mycompany.com";
            add_header Access-Control-Allow-Credentials "true";
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
            add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-API-Key";
            add_header Access-Control-Max-Age 86400;
            add_header Content-Type 'text/plain; charset=utf-8';
            add_header Content-Length 0;
            return 204;
        }
    }

    # 错误页面
    error_page 404 /404.html;
    location = /404.html {
        internal;
        root /var/www/frontend;
    }
}

4.2 Java 后端:Spring Boot API 示例

package com.example.controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.ArrayList;
@RestController
@RequestMapping("/api/users")
public class UserController {
    @GetMapping
    public List<User> getAllUsers() {
        List<User> users = new ArrayList<>();
        users.add(new User(1L, "Alice", "alice@example.com"));
        users.add(new User(2L, "Bob", "bob@example.com"));
        return users;
    }
    @PostMapping
    public User createUser(@RequestBody User user) {
        // 模拟保存
        user.setId(System.currentTimeMillis());
        return user;
    }
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        // 模拟更新
        user.setId(id);
        return user;
    }
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        // 模拟删除
        System.out.println("Deleted user: " + id);
    }
    // 内部类:用户模型
    static class User {
        private Long id;
        private String name;
        private String email;
        public User() {}
        public User(Long id, String name, String email) {
            this.id = id;
            this.name = name;
            this.email = email;
        }
        // Getters & Setters
        public Long getId() { return id; }
        public void setId(Long id) { this.id = id; }
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public String getEmail() { return email; }
        public void setEmail(String email) { this.email = email; }
    }
}

4.3 前端 JavaScript 调用示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CORS Demo</title>
</head>
<body>
    <h1>前端调用跨域 API</h1>
    <button onclick="fetchUsers()">获取用户列表</button>
    <button onclick="createUser()">创建用户</button>
    <div id="result"></div>
    <script>
        const API_BASE = 'https://app.mycompany.com/api';
        async function fetchUsers() {
            try {
                const response = await fetch(`${API_BASE}/users`, {
                    method: 'GET',
                    credentials: 'include', // 携带 Cookie
                    headers: {
                        'Content-Type': 'application/json'
                    }
                });
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                const users = await response.json();
                document.getElementById('result').innerHTML = 
                    `<pre>${JSON.stringify(users, null, 2)}</pre>`;
            } catch (error) {
                document.getElementById('result').innerHTML = 
                    `<p style="color:red">❌ 错误: ${error.message}</p>`;
            }
        }
        async function createUser() {
            try {
                const response = await fetch(`${API_BASE}/users`, {
                    method: 'POST',
                    credentials: 'include',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        name: 'Charlie',
                        email: 'charlie@example.com'
                    })
                });
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                const user = await response.json();
                document.getElementById('result').innerHTML = 
                    `<p>✅ 创建成功: ${user.name} (${user.email})</p>`;
            } catch (error) {
                document.getElementById('result').innerHTML = 
                    `<p style="color:red">❌ 创建失败: ${error.message}</p>`;
            }
        }
    </script>
</body>
</html>

关键点:前端必须设置 credentials: 'include',否则浏览器不会发送 Cookie 或 Authorization 头。

五、实战场景二:多前端域名授权(白名单机制)

在企业级应用中,可能有多个前端项目(如管理后台、移动端 H5、第三方合作伙伴)需要访问同一个 API。

5.1 需求

  • 允许 https://admin.mycompany.com
  • 允许 https://mobile.mycompany.com
  • 允许 https://partner.example.org(第三方合作伙伴)
  • 禁止所有其他来源

5.2 Nginx 实现方案:使用 map + 变量

# 定义允许的来源白名单
map $http_origin $allowed_origin {
    default "";
    "https://admin.mycompany.com" "https://admin.mycompany.com";
    "https://mobile.mycompany.com" "https://mobile.mycompany.com";
    "https://partner.example.org" "https://partner.example.org";
}

server {
    listen 443 ssl http2;
    server_name api.mycompany.com;

    ssl_certificate /etc/ssl/certs/api.mycompany.com.crt;
    ssl_certificate_key /etc/ssl/private/api.mycompany.com.key;

    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 动态设置 Allow-Origin
        add_header Access-Control-Allow-Origin $allowed_origin;
        add_header Access-Control-Allow-Credentials "true";
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
        add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-API-Key";

        # 预检请求处理
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin $allowed_origin;
            add_header Access-Control-Allow-Credentials "true";
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
            add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-API-Key";
            add_header Access-Control-Max-Age 86400;
            add_header Content-Type 'text/plain; charset=utf-8';
            add_header Content-Length 0;
            return 204;
        }
    }
}

为什么用 map

map 指令在 Nginx 启动时编译,性能极高,避免了 if 的重复判断。同时,它能精确匹配,防止恶意伪造 Origin。

5.3 测试验证

使用 curl 模拟不同来源请求:

curl -H "Origin: https://admin.mycompany.com" \
     -X OPTIONS \
     -H "Access-Control-Request-Method: GET" \
     https://api.mycompany.com/users

# ✅ 应返回:
# Access-Control-Allow-Origin: https://admin.mycompany.com

curl -H "Origin: https://evil-site.com" \
     -X OPTIONS \
     -H "Access-Control-Request-Method: GET" \
     https://api.mycompany.com/users

# ❌ 应返回:
# Access-Control-Allow-Origin: (空)
# 浏览器将拦截真实请求

六、安全最佳实践:避免常见陷阱

错误做法 1:通配符*+credentials: true

add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Credentials "true";

浏览器会拒绝这种组合!

Access-Control-Allow-Credentials: true 时,Access-Control-Allow-Origin 不能为 *,必须是明确的源。

正确做法:动态匹配 Origin

map $http_origin $cors_origin {
    default "";
    "~^https://(admin|mobile)\.mycompany\.com$" $http_origin;
    "~^https://partner\.example\.org$" $http_origin;
}

add_header Access-Control-Allow-Origin $cors_origin;
add_header Access-Control-Allow-Credentials "true";

使用正则匹配 ~^,可灵活支持子域名,如 admin-dev.mycompany.com

错误做法 2:不处理 OPTIONS 请求

location /api/ {
    proxy_pass http://localhost:8080;
    # 没有处理 OPTIONS
}

前端发送 PUT 请求时,浏览器先发 OPTIONS,但 Nginx 未响应,后端返回 405,前端报错。

正确做法:显式处理 OPTIONS

if ($request_method = 'OPTIONS') {
    add_header Access-Control-Allow-Origin $cors_origin;
    add_header Access-Control-Allow-Credentials "true";
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
    add_header Access-Control-Allow-Headers "...";
    add_header Access-Control-Max-Age 86400;
    return 204;
}

错误做法 3:暴露敏感响应头

add_header Access-Control-Expose-Headers "Set-Cookie, Authorization";

绝对不要暴露 Set-CookieAuthorizationX-Admin-Token 等敏感头!

推荐暴露头(仅限前端需要的

add_header Access-Control-Expose-Headers "X-Total-Count, X-Request-ID, X-RateLimit-Remaining";

七、CORS 请求流程图(Mermaid)

下面是一个完整的 CORS 请求流程图,涵盖简单请求和预检请求两种路径:

注意: 该流程图是浏览器行为,Nginx 只是响应者。Nginx 的任务是“正确地响应预检请求”和“正确地添加响应头”。

八、Java 后端配合:是否需要 CORS 配置?

很多开发者会在 Spring Boot 中使用 @CrossOrigin 注解:

@RestController
@CrossOrigin(origins = "https://frontend.example.com")
@RequestMapping("/api/users")
public class UserController { ... }

不推荐!

为什么?

问题说明
重复配置每个 Controller 都要加,维护成本高
无法统一管理无法动态切换白名单
紧耦合前端域名变更,需重新编译部署 Java 服务
容易遗漏新增接口忘记加注解
不支持预检@CrossOrigin 无法处理 OPTIONS 请求的复杂逻辑

正确做法:只在 Nginx 层统一处理 CORS

Java 后端保持“干净”,只关注业务逻辑。CORS 由边缘网关(Nginx)统一管理,这才是微服务架构的正确姿势。

最佳实践:Java 后端禁用所有 CORS,全部交给 Nginx 处理。

九、Nginx + Java 实战:完整部署流程

Step 1:准备 Java 后端(Spring Boot)

@SpringBootApplication
public class ApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiApplication.class, args);
    }
}
@RestController
@RequestMapping("/api/users")
public class UserController {
    @GetMapping
    public List<User> list() {
        return List.of(
            new User(1L, "Alice", "alice@example.com"),
            new User(2L, "Bob", "bob@example.com")
        );
    }
    @PostMapping
    public User create(@RequestBody User user) {
        user.setId(System.currentTimeMillis());
        return user;
    }
    static class User {
        private Long id;
        private String name;
        private String email;
        // 构造器、getter、setter 省略
    }
}

application.properties

server.port=8080
logging.level.org.springframework=INFO

Step 2:编译并运行 Java 服务

mvn clean package
java -jar target/api-0.0.1-SNAPSHOT.jar

Step 3:配置 Nginx

编辑 /etc/nginx/sites-available/api-gateway

server {
    listen 443 ssl http2;
    server_name api.mycompany.com;

    ssl_certificate /etc/ssl/certs/api.mycompany.com.crt;
    ssl_certificate_key /etc/ssl/private/api.mycompany.com.key;

    # 静态资源(可选)
    location /static/ {
        alias /var/www/static;
    }

    # API 代理
    location /api/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 白名单映射
        map $http_origin $allowed_origin {
            default "";
            "https://admin.mycompany.com" "https://admin.mycompany.com";
            "https://mobile.mycompany.com" "https://mobile.mycompany.com";
            "https://partner.example.org" "https://partner.example.org";
        }

        # 响应头
        add_header Access-Control-Allow-Origin $allowed_origin;
        add_header Access-Control-Allow-Credentials "true";
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
        add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-API-Key";
        add_header Access-Control-Expose-Headers "X-Total-Count,X-Request-ID";

        # 预检请求
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin $allowed_origin;
            add_header Access-Control-Allow-Credentials "true";
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
            add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-API-Key";
            add_header Access-Control-Max-Age 86400;
            add_header Content-Type 'text/plain; charset=utf-8';
            add_header Content-Length 0;
            return 204;
        }
    }

    # 错误页
    error_page 404 /404.html;
    location = /404.html {
        internal;
        root /var/www/html;
    }
}

Step 4:启用站点并重启 Nginx

sudo ln -s /etc/nginx/sites-available/api-gateway /etc/nginx/sites-enabled/
sudo nginx -t  # 检查语法
sudo systemctl reload nginx

Step 5:前端测试页面(部署在 admin.mycompany.com)

<!DOCTYPE html>
<html>
<head>
    <title>CORS 测试</title>
</head>
<body>
    <h2>测试跨域请求</h2>
    <button onclick="testGet()">获取用户列表</button>
    <button onclick="testPost()">创建用户</button>
    <div id="output"></div>
    <script>
        const API = 'https://api.mycompany.com/api/users';
        async function testGet() {
            try {
                const res = await fetch(API, {
                    method: 'GET',
                    credentials: 'include'
                });
                const data = await res.json();
                document.getElementById('output').innerHTML = 
                    `<pre>${JSON.stringify(data, null, 2)}</pre>`;
            } catch (err) {
                document.getElementById('output').innerHTML = 
                    `<p style="color:red">❌ ${err.message}</p>`;
            }
        }
        async function testPost() {
            try {
                const res = await fetch(API, {
                    method: 'POST',
                    credentials: 'include',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ name: 'David', email: 'david@example.com' })
                });
                const user = await res.json();
                document.getElementById('output').innerHTML = 
                    `<p>✅ 创建成功: ${user.name} (${user.email})</p>`;
            } catch (err) {
                document.getElementById('output').innerHTML = 
                    `<p style="color:red">❌ ${err.message}</p>`;
            }
        }
    </script>
</body>
</html>

打开 https://admin.mycompany.com/test.html,点击按钮,应能成功调用 API!

十、调试技巧:如何验证 CORS 是否生效?

方法 1:Chrome DevTools

  1. 打开 Network 面板
  2. 发起请求
  3. 查看响应头是否包含:
    • Access-Control-Allow-Origin
    • Access-Control-Allow-Credentials
    • Access-Control-Allow-Methods

如果看到 Access-Control-Allow-Origin: *credentials: include浏览器会报错!

方法 2:curl 模拟 OPTIONS 预检

curl -v \
  -H "Origin: https://admin.mycompany.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Authorization, Content-Type" \
  -X OPTIONS \
  https://api.mycompany.com/api/users

查看响应头是否包含:

Access-Control-Allow-Origin: https://admin.mycompany.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400

十一、进阶:使用 Lua + OpenResty 实现动态 CORS(可选)

如果你的架构更复杂(如多租户、动态租户域名),可以使用 OpenResty(Nginx + Lua)实现动态白名单从数据库加载:

-- nginx.conf
location /api/ {
    access_by_lua_block {
        local origin = ngx.var.http_origin
        local allowed = require("cors").is_allowed(origin)
        if allowed then
            ngx.header["Access-Control-Allow-Origin"] = origin
            ngx.header["Access-Control-Allow-Credentials"] = "true"
        else
            ngx.header["Access-Control-Allow-Origin"] = ""
        end
    }

    proxy_pass http://backend;
    ...
}
-- cors.lua
local M = {}

function M.is_allowed(origin)
    local whitelist = {
        "https://tenant1.example.com",
        "https://tenant2.example.com"
    }
    for _, allowed in ipairs(whitelist) do
        if origin == allowed then
            return true
        end
    end
    return false
end

return M

这种方式适合大型平台,但对普通项目属于过度设计。Nginx map 已足够。

十二、性能与缓存优化

1.Access-Control-Max-Age设置建议

场景建议值说明
高频 API(如电商)86400(24小时)减少预检请求,提升体验
低频 API(如后台)3600(1小时)平衡安全与性能
开发环境060避免缓存导致调试困难

2. 避免重复响应头

不要在多个 location 中重复设置相同头,会导致响应头重复:

add_header Access-Control-Allow-Origin "a.com";
add_header Access-Control-Allow-Origin "b.com"; # ❌ 会被忽略,只保留最后一个

使用 map 变量是唯一安全方式。

3. 启用 Gzip 压缩(非 CORS,但重要)

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript;

十三、常见错误排查清单

问题原因解决方案
No 'Access-Control-Allow-Origin' headerNginx 没有添加响应头检查 add_header 是否在 location 中,且未被 proxy_hide_header 覆盖
Credentials flag is 'true' but Access-Control-Allow-Origin is '*'前端用了 credentials: true,后端用了 *改为具体域名,或移除 credentials: true
OPTIONS 请求返回 405Nginx 未处理 OPTIONS添加 if ($request_method = 'OPTIONS') { return 204; }
请求被拦截,但响应头有 CORS前端请求头不匹配检查 Access-Control-Allow-Headers 是否包含实际发送的头(如 Authorization
静态资源跨域失败Nginx 静态 location 未配置 CORS/static/ 也添加相同的 CORS 配置
生产环境突然失效SSL 证书过期或域名变更检查 Nginx 配置中 server_name 和 SSL 证书是否匹配

总结:Nginx CORS 配置黄金法则

法则说明
1. 统一管理所有 CORS 由 Nginx 统一处理,后端不配置
2. 白名单优先使用 map 明确允许的源,拒绝 * + credentials
3. 预检必处理必须显式处理 OPTIONS 请求,返回 204
4. 响应头最小化只暴露前端需要的头,避免敏感信息泄露
5. 缓存预检Access-Control-Max-Age: 86400 提升性能
6. 测试验证curl + DevTools 验证,不要只靠前端报错
7. 安全第一永远不要信任前端传来的 Origin,只信任配置白名单

扩展阅读:CORS 与安全

CORS 并非万能。它只是浏览器的访问控制机制,不是后端认证。你仍需:

  • 使用 CSRF Token 保护表单提交
  • 使用 JWTOAuth2 做身份认证
  • 使用 Content-Security-Policy 防止 XSS
  • 使用 SameSite=Strict 保护 Cookie

最终建议:你的 Nginx CORS 配置模板(可直接复制)

# 在 http 或 server 块中定义白名单
map $http_origin $cors_origin {
    default "";
    "~^https://(admin|mobile|www)\.mycompany\.com$" $http_origin;
    "~^https://partner\.example\.org$" $http_origin;
    "~^https://dev\.mycompany\.com$" $http_origin; # 开发环境
}

server {
    listen 443 ssl http2;
    server_name api.mycompany.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location /api/ {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 响应头
        add_header Access-Control-Allow-Origin $cors_origin;
        add_header Access-Control-Allow-Credentials "true";
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH, OPTIONS";
        add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-API-Key,X-Request-ID";
        add_header Access-Control-Expose-Headers "X-Total-Count,X-Request-ID,X-RateLimit-Remaining";

        # 预检请求
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin $cors_origin;
            add_header Access-Control-Allow-Credentials "true";
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH, OPTIONS";
            add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-API-Key,X-Request-ID";
            add_header Access-Control-Max-Age 86400;
            add_header Content-Type 'text/plain; charset=utf-8';
            add_header Content-Length 0;
            return 204;
        }
    }
}

结语:跨域不是问题,是架构的试金石

跨域问题,本质上是架构设计是否清晰的体现。一个优秀的系统,应该:

  • 前后端分离,职责分明
  • API 网关统一管理安全策略
  • 服务无状态,可水平扩展
  • 配置可追踪、可审计、可回滚

Nginx 的 CORS 配置,正是这种“边缘网关”思想的完美体现。它不侵入业务,却守护安全;它不修改代码,却提升体验。

当你下次看到前端报错 CORS policy 时,不要急着在 Spring Boot 里加注解。

打开 Nginx 配置文件,添加一行 map,世界就清净了。

 “真正的工程之美,不在于代码有多炫,而在于边界有多清晰。”

附录:常用 Origin 正则表达式参考

需求正则表达式
只允许 https://example.com"https://example.com"
允许所有子域名"~^https://.*\.example\.com$"
允许多个域名`"~^https://(example1
允许 http 和 https`"~^(https?😕/)(admin
开发环境允许 localhost"~^https?://localhost(:[0-9]+)?$"

你已经掌握了:

  • CORS 的原理与工作流程
  • Nginx 的完整 CORS 配置方案
  • Java 后端无需修改的架构设计
  • 安全白名单的最佳实践
  • 预检请求的处理技巧
  • 调试与验证方法
  • 高可用、高性能的生产部署建议

现在,你已具备在企业级项目中独立解决跨域问题的能力。下一个项目,你就是那个“让前端不再报错”的人。

以上就是Nginx解决CORS跨域配置的指南的详细内容,更多关于Nginx解决CORS跨域的资料请关注脚本之家其它相关文章!

相关文章

  • 使用Kubernetes部署Springboot或Nginx的详细教程

    使用Kubernetes部署Springboot或Nginx的详细教程

    这篇文章主要介绍了用Kubernetes部署Springboot或Nginx的详细教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • Nginx七层及四层反向代理配置的全过程

    Nginx七层及四层反向代理配置的全过程

    反向代理是以代理服务器来接受internet连接请求,然后再把请求转发给另外的服务器,下面这篇文章主要给大家介绍了关于Nginx七层及四层反向代理配置的相关资料,需要的朋友可以参考下
    2022-03-03
  • Nginx设置成服务并开机自动启动的配置

    Nginx设置成服务并开机自动启动的配置

    Nginx 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务,接下来通过本文给大家介绍Nginx设置成服务并开机自动启动的配置,需要的朋友可以参考下
    2022-01-01
  • nginx使用replace-filter-nginx-module实现内容替换的示例

    nginx使用replace-filter-nginx-module实现内容替换的示例

    本篇文章主要介绍了nginx使用replace-filter-nginx-module实现内容替换的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • 前端如何修改nginx配置(在VSCode)

    前端如何修改nginx配置(在VSCode)

    前端开发中Nginx是最常用的 Web 服务器和反向代理工具,主要用于部署静态资源、处理跨域、配置缓存策略等,这篇文章主要介绍了前端如何修改nginx配置的相关资料,需要的朋友可以参考下
    2025-09-09
  • 详解Nginx服务器中配置全站HTTPS安全连接的方法

    详解Nginx服务器中配置全站HTTPS安全连接的方法

    这篇文章主要介绍了详解Nginx服务器中配置全站HTTPS安全连接的方法,其中要点还是在于SSL证书的申请,需要的朋友可以参考下
    2016-01-01
  • nginx 拦截指定ip访问指定url的实现示例

    nginx 拦截指定ip访问指定url的实现示例

    本文主要介绍了nginx 拦截指定ip访问指定url的实现示例,使用$http_x_forwarded_for变量来获取客户端的真实IP地址,感兴趣的可以了解一下
    2024-12-12
  • Nginx timeout超时配置详解

    Nginx timeout超时配置详解

    这篇文章主要介绍了Nginx timeout超时配置详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • 使用ngxtop实时监控Nginx日志文件的示例代码

    使用ngxtop实时监控Nginx日志文件的示例代码

    在Nginx日志分析领域,ngxtop是一款强大的实时监控工具,它能够即时解析Nginx的访问日志文件,提供直观、可定制的实时统计信息,帮助管理员更好地了解服务器的运行状况和Web流量,本文给大家介绍使用ngxtop实时监控Nginx日志文件的示例代码,需要的朋友可以参考下
    2024-01-01
  • nginx中使用nginx-http-concat模块合并静态资源文件

    nginx中使用nginx-http-concat模块合并静态资源文件

    这篇文章主要介绍了nginx中使用nginx-http-concat模块合并静态资源文件,用以加速网站的CSS、JS等静态资源载入速度,需要的朋友可以参考下
    2014-06-06

最新评论