Nginx解决CORS跨域配置的指南
在现代 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)
满足以下条件即为简单请求:
- 方法:
GET、POST、HEAD - 头部:仅限
Accept、Accept-Language、Content-Language、Content-Type(仅限application/x-www-form-urlencoded、multipart/form-data、text/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),询问服务器是否允许该跨域请求。只有预检通过后,才会发送真实请求。
常见触发场景:
- 使用
PUT、DELETE、PATCH方法 - 自定义请求头(如
Authorization、X-API-Key) Content-Type: application/json、application/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-Cookie、Authorization、X-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
- 打开 Network 面板
- 发起请求
- 查看响应头是否包含:
Access-Control-Allow-OriginAccess-Control-Allow-CredentialsAccess-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小时) | 平衡安全与性能 |
| 开发环境 | 0 或 60 | 避免缓存导致调试困难 |
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' header | Nginx 没有添加响应头 | 检查 add_header 是否在 location 中,且未被 proxy_hide_header 覆盖 |
Credentials flag is 'true' but Access-Control-Allow-Origin is '*' | 前端用了 credentials: true,后端用了 * | 改为具体域名,或移除 credentials: true |
| OPTIONS 请求返回 405 | Nginx 未处理 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保护表单提交 - 使用
JWT或OAuth2做身份认证 - 使用
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的详细教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2020-07-07
nginx使用replace-filter-nginx-module实现内容替换的示例
本篇文章主要介绍了nginx使用replace-filter-nginx-module实现内容替换的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2018-03-03
nginx中使用nginx-http-concat模块合并静态资源文件
这篇文章主要介绍了nginx中使用nginx-http-concat模块合并静态资源文件,用以加速网站的CSS、JS等静态资源载入速度,需要的朋友可以参考下2014-06-06


最新评论