最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Nginx解決CORS跨域配置的指南

 更新時間:2026年07月11日 09:17:18   作者:知遠漫談  
Nginx作為高性能的反向代理服務器和 Web 服務器,是解決CORS跨域問題的工具之一,本篇將帶你從零開始,深入理解 CORS 原理,掌握 Nginx 中 CORS 的完整配置方案,并結合 Java 后端實戰(zhàn)案例,構建一個安全、靈活、可擴展的跨域解決方案

在現(xiàn)代 Web 開發(fā)中,前后端分離架構已成為主流。前端應用通常部署在獨立的域名或端口上(如 https://frontend.example.com),而后端 API 服務則運行在另一個域(如 https://api.example.com)。當瀏覽器加載前端頁面并嘗試通過 JavaScript 調用跨域 API 時,會觸發(fā) 同源策略(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 后端實戰(zhàn)案例,構建一個安全、靈活、可擴展的跨域解決方案。

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

1.1 同源策略的誕生

瀏覽器的同源策略是 Web 安全的基石之一。它規(guī)定:只有當協(xié)議、域名、端口三者完全相同時,頁面腳本才能訪問另一個頁面的資源。

例如:

URL是否同源原因
https://example.com? 同源
https://example.com/api? 同源路徑不同不影響
http://example.com? 不同源協(xié)議不同(http vs https)
https://api.example.com? 不同源子域名不同
https://example.com:8080? 不同源端口不同

這種限制雖然保護了用戶免受 XSS、CSRF 等攻擊,但也給現(xiàn)代應用開發(fā)帶來了挑戰(zhàn) —— 前后端分離架構下,前端與后端必然不在同一源。

1.2 CORS 的作用

CORS 是 W3C 制定的標準,允許服務器明確聲明哪些外部源可以訪問其資源。它通過在 HTTP 響應頭中添加特定字段,告知瀏覽器:“你可以信任這個來源,允許它發(fā)起跨域請求”。CORS 不是瀏覽器的特性,而是服務器的權限聲明機制。

1.3 CORS 請求類型

CORS 請求分為兩類:

簡單請求(Simple Request)

滿足以下條件即為簡單請求:

  • 方法:GET、POST、HEAD
  • 頭部:僅限 AcceptAccept-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)

不滿足上述條件的請求,瀏覽器會先發(fā)送一個 OPTIONS 預檢請求(Preflight Request),詢問服務器是否允許該跨域請求。只有預檢通過后,才會發(fā)送真實請求。

常見觸發(fā)場景:

  • 使用 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

只有當這些頭信息匹配時,瀏覽器才允許后續(xù)的 PUT 請求。

二、Nginx 作為 CORS 網(wǎng)關:為什么選它?

在微服務架構中,前端通常通過 Nginx 作為統(tǒng)一入口,代理多個后端服務。Nginx 擁有以下優(yōu)勢:

優(yōu)勢說明
性能極高基于事件驅動,單進程處理高并發(fā),內存占用低
配置靈活支持基于 location、if、map 等精細控制
反向代理可統(tǒng)一代理多個后端服務,集中處理 CORS
安全可控可在入口層統(tǒng)一攔截非法請求,減少后端負擔
無依賴不需修改后端代碼,實現(xiàn)零侵入式跨域管理

最佳實踐:將 CORS 配置放在 Nginx 層,而非后端(如 Spring Boot)。這樣可以避免每個服務重復配置,提升維護性。

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

Nginx 通過 add_header 指令設置響應頭,從而實現(xiàn) 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 請求也轉發(fā)給后端,而大多數(shù) Java 服務(如 Spring Boot)默認不處理 OPTIONS,導致返回 405 或 500,前端報錯。

四、實戰(zhàn)場景一:單域名前端 + 單后端服務

假設我們有:

  • 前端:https://app.mycompany.com(Nginx 靜態(tài)站點)
  • 后端:http://localhost:8080(Java Spring Boot 應用)
  • Nginx 監(jiān)聽 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;

    # 靜態(tài)資源
    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()">創(chuàng)建用戶</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>? 創(chuàng)建成功: ${user.name} (${user.email})</p>`;
            } catch (error) {
                document.getElementById('result').innerHTML = 
                    `<p style="color:red">? 創(chuàng)建失敗: ${error.message}</p>`;
            }
        }
    </script>
</body>
</html>

關鍵點:前端必須設置 credentials: 'include',否則瀏覽器不會發(fā)送 Cookie 或 Authorization 頭。

五、實戰(zhàn)場景二:多前端域名授權(白名單機制)

在企業(yè)級應用中,可能有多個前端項目(如管理后臺、移動端 H5、第三方合作伙伴)需要訪問同一個 API。

5.1 需求

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

5.2 Nginx 實現(xiàn)方案:使用 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;

        # 動態(tài)設置 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 不能為 *,必須是明確的源。

正確做法:動態(tài)匹配 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
}

前端發(fā)送 PUT 請求時,瀏覽器先發(fā) 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、AuthorizationX-Admin-Token 等敏感頭!

推薦暴露頭(僅限前端需要的

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

七、CORS 請求流程圖(Mermaid)

下面是一個完整的 CORS 請求流程圖,涵蓋簡單請求和預檢請求兩種路徑:

注意: 該流程圖是瀏覽器行為,Nginx 只是響應者。Nginx 的任務是“正確地響應預檢請求”和“正確地添加響應頭”。

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

很多開發(fā)者會在 Spring Boot 中使用 @CrossOrigin 注解:

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

不推薦!

為什么?

問題說明
重復配置每個 Controller 都要加,維護成本高
無法統(tǒng)一管理無法動態(tài)切換白名單
緊耦合前端域名變更,需重新編譯部署 Java 服務
容易遺漏新增接口忘記加注解
不支持預檢@CrossOrigin 無法處理 OPTIONS 請求的復雜邏輯

正確做法:只在 Nginx 層統(tǒng)一處理 CORS

Java 后端保持“干凈”,只關注業(yè)務邏輯。CORS 由邊緣網(wǎng)關(Nginx)統(tǒng)一管理,這才是微服務架構的正確姿勢。

最佳實踐:Java 后端禁用所有 CORS,全部交給 Nginx 處理。

九、Nginx + Java 實戰(zhàn):完整部署流程

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;

    # 靜態(tài)資源(可選)
    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()">創(chuàng)建用戶</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>? 創(chuàng)建成功: ${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. 發(fā)起請求
  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 實現(xiàn)動態(tài) CORS(可選)

如果你的架構更復雜(如多租戶、動態(tài)租戶域名),可以使用 OpenResty(Nginx + Lua)實現(xiàn)動態(tài)白名單從數(shù)據(jù)庫加載:

-- 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 已足夠。

十二、性能與緩存優(yōu)化

1.Access-Control-Max-Age設置建議

場景建議值說明
高頻 API(如電商)86400(24小時)減少預檢請求,提升體驗
低頻 API(如后臺)3600(1小時)平衡安全與性能
開發(fā)環(huán)境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 是否包含實際發(fā)送的頭(如 Authorization
靜態(tài)資源跨域失敗Nginx 靜態(tài) location 未配置 CORS/static/ 也添加相同的 CORS 配置
生產環(huán)境突然失效SSL 證書過期或域名變更檢查 Nginx 配置中 server_name 和 SSL 證書是否匹配

總結:Nginx CORS 配置黃金法則

法則說明
1. 統(tǒng)一管理所有 CORS 由 Nginx 統(tǒng)一處理,后端不配置
2. 白名單優(yōu)先使用 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; # 開發(fā)環(huán)境
}

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;
        }
    }
}

結語:跨域不是問題,是架構的試金石

跨域問題,本質上是架構設計是否清晰的體現(xiàn)。一個優(yōu)秀的系統(tǒng),應該:

  • 前后端分離,職責分明
  • API 網(wǎng)關統(tǒng)一管理安全策略
  • 服務無狀態(tài),可水平擴展
  • 配置可追蹤、可審計、可回滾

Nginx 的 CORS 配置,正是這種“邊緣網(wǎng)關”思想的完美體現(xiàn)。它不侵入業(yè)務,卻守護安全;它不修改代碼,卻提升體驗。

當你下次看到前端報錯 CORS policy 時,不要急著在 Spring Boot 里加注解。

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

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

附錄:常用 Origin 正則表達式參考

需求正則表達式
只允許 https://example.com"https://example.com"
允許所有子域名"~^https://.*\.example\.com$"
允許多個域名`"~^https://(example1
允許 http 和 https`"~^(https???/)(admin
開發(fā)環(huán)境允許 localhost"~^https?://localhost(:[0-9]+)?$"

你已經(jīng)掌握了:

  • CORS 的原理與工作流程
  • Nginx 的完整 CORS 配置方案
  • Java 后端無需修改的架構設計
  • 安全白名單的最佳實踐
  • 預檢請求的處理技巧
  • 調試與驗證方法
  • 高可用、高性能的生產部署建議

現(xiàn)在,你已具備在企業(yè)級項目中獨立解決跨域問題的能力。下一個項目,你就是那個“讓前端不再報錯”的人。

以上就是Nginx解決CORS跨域配置的指南的詳細內容,更多關于Nginx解決CORS跨域的資料請關注腳本之家其它相關文章!

相關文章

  • 使用Kubernetes部署Springboot或Nginx的詳細教程

    使用Kubernetes部署Springboot或Nginx的詳細教程

    這篇文章主要介紹了用Kubernetes部署Springboot或Nginx的詳細教程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • Nginx七層及四層反向代理配置的全過程

    Nginx七層及四層反向代理配置的全過程

    反向代理是以代理服務器來接受internet連接請求,然后再把請求轉發(fā)給另外的服務器,下面這篇文章主要給大家介紹了關于Nginx七層及四層反向代理配置的相關資料,需要的朋友可以參考下
    2022-03-03
  • Nginx設置成服務并開機自動啟動的配置

    Nginx設置成服務并開機自動啟動的配置

    Nginx?是一個高性能的HTTP和反向代理web服務器,同時也提供了IMAP/POP3/SMTP服務,接下來通過本文給大家介紹Nginx設置成服務并開機自動啟動的配置,需要的朋友可以參考下
    2022-01-01
  • nginx使用replace-filter-nginx-module實現(xiàn)內容替換的示例

    nginx使用replace-filter-nginx-module實現(xiàn)內容替換的示例

    本篇文章主要介紹了nginx使用replace-filter-nginx-module實現(xiàn)內容替換的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • 前端如何修改nginx配置(在VSCode)

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

    前端開發(fā)中Nginx是最常用的?Web?服務器和反向代理工具,主要用于部署靜態(tài)資源、處理跨域、配置緩存策略等,這篇文章主要介紹了前端如何修改nginx配置的相關資料,需要的朋友可以參考下
    2025-09-09
  • 詳解Nginx服務器中配置全站HTTPS安全連接的方法

    詳解Nginx服務器中配置全站HTTPS安全連接的方法

    這篇文章主要介紹了詳解Nginx服務器中配置全站HTTPS安全連接的方法,其中要點還是在于SSL證書的申請,需要的朋友可以參考下
    2016-01-01
  • nginx?攔截指定ip訪問指定url的實現(xiàn)示例

    nginx?攔截指定ip訪問指定url的實現(xiàn)示例

    本文主要介紹了nginx?攔截指定ip訪問指定url的實現(xiàn)示例,使用$http_x_forwarded_for變量來獲取客戶端的真實IP地址,感興趣的可以了解一下
    2024-12-12
  • Nginx timeout超時配置詳解

    Nginx timeout超時配置詳解

    這篇文章主要介紹了Nginx timeout超時配置詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-12-12
  • 使用ngxtop實時監(jiān)控Nginx日志文件的示例代碼

    使用ngxtop實時監(jiān)控Nginx日志文件的示例代碼

    在Nginx日志分析領域,ngxtop是一款強大的實時監(jiān)控工具,它能夠即時解析Nginx的訪問日志文件,提供直觀、可定制的實時統(tǒng)計信息,幫助管理員更好地了解服務器的運行狀況和Web流量,本文給大家介紹使用ngxtop實時監(jiān)控Nginx日志文件的示例代碼,需要的朋友可以參考下
    2024-01-01
  • nginx中使用nginx-http-concat模塊合并靜態(tài)資源文件

    nginx中使用nginx-http-concat模塊合并靜態(tài)資源文件

    這篇文章主要介紹了nginx中使用nginx-http-concat模塊合并靜態(tài)資源文件,用以加速網(wǎng)站的CSS、JS等靜態(tài)資源載入速度,需要的朋友可以參考下
    2014-06-06

最新評論

根河市| 孝感市| 合作市| 双流县| 大邑县| 合水县| 巧家县| 漳平市| 大关县| 班戈县| 凤台县| 新河县| 海阳市| 耒阳市| 手游| 平潭县| 兴义市| 诏安县| 通山县| 松溪县| 抚顺市| 东光县| 瑞昌市| 栖霞市| 鹿邑县| 昭苏县| 海宁市| 深水埗区| 丹江口市| 鄯善县| 黔南| 民和| 平远县| 县级市| 福海县| 莱阳市| 龙游县| 诸暨市| 陵川县| 弥渡县| 邵武市|