pin_drop当前位置:知识文库 ❯ 图文

后端通信:16. 跨域请求与CORS - 从入门到实践详解

教程简介

跨域请求是前端开发中最常见的问题之一。由于浏览器的同源策略(Same-Origin Policy)限制,不同源之间的HTTP请求默认会被浏览器拦截。CORS(Cross-Origin Resource Sharing,跨源资源共享)是W3C标准中解决跨域问题的官方方案,它通过一组HTTP头部字段,允许服务器声明哪些源站可以通过浏览器访问其资源。

本教程将深入讲解同源策略的原理、CORS的工作机制、预检请求流程、各种跨域解决方案的对比,以及如何在前后端正确配置CORS,帮助你彻底理解和解决跨域问题。

核心概念

同源策略(Same-Origin Policy)

同源策略是浏览器最核心的安全机制,它限制了一个源的文档或脚本如何与另一个源的资源进行交互。

同源的定义:协议(Protocol)、域名(Host)、端口(Port)三者完全相同。

URL A URL B 是否同源 原因
https://example.com/page https://example.com/api 协议、域名、端口都相同
https://example.com http://example.com 协议不同
https://example.com https://api.example.com 域名不同
https://example.com https://example.com:8080 端口不同
https://example.com https://192.168.1.1 域名和IP不同

同源策略的限制范围

受限行为 说明 示例
AJAX请求 不能向不同源发送XHR/Fetch请求 fetch('https://other.com/api')
DOM操作 不能访问不同源iframe的DOM iframe.contentDocument
Cookie/Storage 不能访问不同源的Cookie和Storage document.cookie
Canvas 不能读取不同源图片的像素数据 canvas.getImageData()

CORS工作流程

代码示例

简单请求流程:
浏览器  请求 + Origin头 > 服务器
浏览器 < 响应 + Access-Control-*头  服务器

预检请求流程:
浏览器  OPTIONS预检请求 > 服务器
浏览器 < 预检响应(允许的源/方法/头)  服务器
浏览器  实际请求 > 服务器
浏览器 < 实际响应  服务器

简单请求 vs 预检请求

简单请求条件(必须同时满足):

条件 允许的值
请求方法 GET、HEAD、POST
Content-Type application/x-www-form-urlencoded、multipart/form-data、text/plain
请求头 仅限CORS安全头部:Accept、Accept-Language、Content-Language、Content-Type(受限值)
请求中无ReadableStream对象 -

不满足简单请求条件的请求会触发预检请求(Preflight Request)。

CORS相关HTTP头部

头部 方向 说明
Origin 请求头 标识请求来源
Access-Control-Allow-Origin 响应头 允许的源
Access-Control-Allow-Methods 响应头 允许的HTTP方法
Access-Control-Allow-Headers 响应头 允许的请求头
Access-Control-Allow-Credentials 响应头 是否允许携带凭证
Access-Control-Max-Age 响应头 预检请求缓存时间
Access-Control-Expose-Headers 响应头 允许前端访问的响应头
Access-Control-Request-Method 预检请求头 实际请求将使用的方法
Access-Control-Request-Headers 预检请求头 实际请求将携带的头

语法与用法

Fetch API跨域请求

代码示例

// 简单跨域GET请求
fetch('https://api.example.com/data', {
    method: 'GET',
    mode: 'cors',                    // 跨域模式
    credentials: 'include',          // 携带Cookie
    headers: {
        'Accept': 'application/json'
    }
});

// 需要预检的跨域POST请求
fetch('https://api.example.com/data', {
    method: 'POST',
    mode: 'cors',
    credentials: 'include',
    headers: {
        'Content-Type': 'application/json',  // 触发预检
        'X-Custom-Header': 'value'            // 自定义头触发预检
    },
    body: JSON.stringify({ name: 'test' })
});

XMLHttpRequest跨域请求

代码示例

const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/data', true);
xhr.withCredentials = true;  // 携带Cookie
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.onload = function() {
    console.log(xhr.responseText);
};
xhr.send(JSON.stringify({ name: 'test' }));

CORS模式说明

mode值 说明 适用场景
cors 遵循CORS标准 跨域请求(最常用)
no-cors 不发送CORS头,响应类型受限 不需要读取响应内容的场景
same-origin 仅同源请求 严格限制跨域
navigate 导航请求 页面跳转

代码示例

示例1:CORS原理演示

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CORS原理演示</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #1a1a2e;
            color: #e0e0e0;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { text-align: center; color: #48dbfb; margin-bottom: 30px; }
        .card {
            background: #16213e;
            border: 1px solid #2d2d44;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 20px;
        }
        .card h2 { font-size: 16px; color: #feca57; margin-bottom: 16px; }
        .flow-diagram {
            background: #0a0a1a;
            border-radius: 8px;
            padding: 20px;
            font-family: monospace;
            font-size: 13px;
            line-height: 1.8;
            overflow-x: auto;
        }
        .flow-browser { color: #48dbfb; }
        .flow-server { color: #2ecc71; }
        .flow-header { color: #feca57; }
        .flow-error { color: #ff6b6b; }
        .request-tester {
            margin-top: 16px;
        }
        .url-row {
            display: flex;
            gap: 10px;
            margin-bottom: 12px;
        }
        .url-row input, .url-row select {
            padding: 10px 12px;
            background: #0f3460;
            border: 1px solid #2d2d44;
            border-radius: 6px;
            color: #e0e0e0;
            font-size: 14px;
            outline: none;
        }
        .url-row input { flex: 1; }
        .url-row select { min-width: 100px; }
        .url-row input:focus, .url-row select:focus { border-color: #48dbfb; }
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
        }
        .btn-primary { background: #48dbfb; color: #0f0f23; }
        .btn-primary:hover { background: #0abde3; }
        .btn-danger { background: #ff6b6b; color: white; }
        .btn-danger:hover { background: #ee5a5a; }
        .btn-success { background: #2ecc71; color: white; }
        .btn-success:hover { background: #27ae60; }
        .result-panel {
            background: #0a0a1a;
            border-radius: 8px;
            padding: 16px;
            margin-top: 12px;
            font-family: monospace;
            font-size: 13px;
            max-height: 300px;
            overflow-y: auto;
            white-space: pre-wrap;
            word-break: break-all;
        }
        .result-panel.success { border-left: 4px solid #2ecc71; }
        .result-panel.error { border-left: 4px solid #ff6b6b; }
        .headers-table {
            width: 100%;
            border-collapse: collapse;
            font-size: 13px;
            margin-top: 12px;
        }
        .headers-table th, .headers-table td {
            padding: 8px 12px;
            border: 1px solid #2d2d44;
            text-align: left;
        }
        .headers-table th {
            background: #0f3460;
            color: #48dbfb;
            width: 200px;
        }
        .headers-table td { color: #2ecc71; font-family: monospace; }
        .tab-group {
            display: flex;
            gap: 4px;
            margin-bottom: 16px;
        }
        .tab {
            padding: 8px 16px;
            background: transparent;
            border: 1px solid #2d2d44;
            border-radius: 6px;
            color: #888;
            font-size: 13px;
            cursor: pointer;
        }
        .tab.active {
            background: #48dbfb;
            color: #0f0f23;
            border-color: #48dbfb;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>CORS 跨域请求原理演示</h1>

        <!-- 同源策略说明 -->
        <div class="card">
            <h2>同源策略判断</h2>
            <p style="font-size:13px; color:#888; margin-bottom:12px;">
                当前页面源: <span style="color:#48dbfb;" id="currentOrigin">-</span>
            </p>
            <table class="headers-table">
                <thead>
                    <tr>
                        <th>目标URL</th>
                        <th>协议</th>
                        <th>域名</th>
                        <th>端口</th>
                        <th>是否同源</th>
                    </tr>
                </thead>
                <tbody id="originCheckTable"></tbody>
            </table>
        </div>

        <!-- CORS流程图 -->
        <div class="card">
            <h2>CORS请求流程</h2>
            <div class="tab-group">
                <div class="tab active" onclick="showFlow('simple')">简单请求</div>
                <div class="tab" onclick="showFlow('preflight')">预检请求</div>
            </div>

            <div class="flow-diagram" id="flowSimple">
<span class="flow-browser">浏览器</span>                          <span class="flow-server">服务器</span>
                                     
     <span class="flow-header">GET /api/data HTTP/1.1</span>            
     <span class="flow-header">Origin: https://example.com</span>       
     >  
                                     
     <span class="flow-header">HTTP/1.1 200 OK</span>                  
     <span class="flow-header">Access-Control-Allow-Origin: *</span>   
     <span class="flow-header">Content-Type: application/json</span>   
     <  
                                     
      请求成功,读取响应数据         
            </div>

            <div class="flow-diagram" id="flowPreflight" style="display:none;">
<span class="flow-browser">浏览器</span>                          <span class="flow-server">服务器</span>
                                     
     <span class="flow-header">OPTIONS /api/data HTTP/1.1</span>          ← 预检请求
     <span class="flow-header">Origin: https://example.com</span>       
     <span class="flow-header">Access-Control-Request-Method: PUT</span>
     <span class="flow-header">Access-Control-Request-Headers: X-Token</span>
     >  
                                     
     <span class="flow-header">HTTP/1.1 204 No Content</span>           ← 预检响应
     <span class="flow-header">Access-Control-Allow-Origin: *</span>   
     <span class="flow-header">Access-Control-Allow-Methods: PUT</span>
     <span class="flow-header">Access-Control-Allow-Headers: X-Token</span>
     <span class="flow-header">Access-Control-Max-Age: 86400</span>   
     <  
                                     
     <span class="flow-header">PUT /api/data HTTP/1.1</span>             ← 实际请求
     <span class="flow-header">Origin: https://example.com</span>       
     <span class="flow-header">X-Token: abc123</span>                  
     >  
                                     
     <span class="flow-header">HTTP/1.1 200 OK</span>                    ← 实际响应
     <span class="flow-header">Access-Control-Allow-Origin: *</span>   
     <  
                                     
      请求成功                      
            </div>
        </div>

        <!-- 跨域请求测试 -->
        <div class="card">
            <h2>跨域请求测试</h2>
            <div class="request-tester">
                <div class="url-row">
                    <select id="testMethod">
                        <option value="GET">GET</option>
                        <option value="POST">POST</option>
                        <option value="PUT">PUT</option>
                        <option value="DELETE">DELETE</option>
                        <option value="PATCH">PATCH</option>
                    </select>
                    <input type="text" id="testUrl" value="https://httpbin.org/get" placeholder="输入请求URL">
                </div>
                <div class="url-row">
                    <select id="testContentType">
                        <option value="">无Content-Type</option>
                        <option value="application/x-www-form-urlencoded">application/x-www-form-urlencoded</option>
                        <option value="multipart/form-data">multipart/form-data</option>
                        <option value="text/plain">text/plain</option>
                        <option value="application/json" selected>application/json</option>
                    </select>
                    <input type="text" id="testCustomHeader" placeholder="自定义头: X-Custom-Header=value">
                </div>
                <div style="display:flex; gap:8px;">
                    <button class="btn btn-primary" onclick="sendTestRequest()">发送请求</button>
                    <button class="btn btn-danger" onclick="sendNoCorsRequest()">no-cors模式</button>
                    <button class="btn btn-success" onclick="sendProxyRequest()">代理请求</button>
                </div>
                <div class="result-panel" id="testResult" style="display:none;"></div>
            </div>
        </div>

        <!-- CORS头部详解 -->
        <div class="card">
            <h2>CORS响应头部详解</h2>
            <table class="headers-table">
                <thead>
                    <tr>
                        <th>响应头</th>
                        <th>说明</th>
                        <th>示例值</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>Access-Control-Allow-Origin</td>
                        <td style="color:#e0e0e0;">允许的源,*表示任意源</td>
                        <td>* 或 https://example.com</td>
                    </tr>
                    <tr>
                        <td>Access-Control-Allow-Methods</td>
                        <td style="color:#e0e0e0;">允许的HTTP方法</td>
                        <td>GET, POST, PUT, DELETE</td>
                    </tr>
                    <tr>
                        <td>Access-Control-Allow-Headers</td>
                        <td style="color:#e0e0e0;">允许的请求头</td>
                        <td>Content-Type, X-Token</td>
                    </tr>
                    <tr>
                        <td>Access-Control-Allow-Credentials</td>
                        <td style="color:#e0e0e0;">是否允许携带Cookie</td>
                        <td>true</td>
                    </tr>
                    <tr>
                        <td>Access-Control-Max-Age</td>
                        <td style="color:#e0e0e0;">预检结果缓存时间(秒)</td>
                        <td>86400</td>
                    </tr>
                    <tr>
                        <td>Access-Control-Expose-Headers</td>
                        <td style="color:#e0e0e0;">允许前端读取的响应头</td>
                        <td>X-Total-Count, X-Request-Id</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>

    <script>
        // 显示当前源
        document.getElementById('currentOrigin').textContent = window.location.origin;

        // 同源判断表格
        const testUrls = [
            { url: window.location.href, label: '当前页面' },
            { url: 'https://httpbin.org/get', label: 'httpbin.org' },
            { url: 'http://httpbin.org/get', label: 'httpbin.org (HTTP)' },
            { url: 'https://api.github.com/users', label: 'api.github.com' },
            { url: 'https://jsonplaceholder.typicode.com/posts', label: 'jsonplaceholder' },
        ];

        const tbody = document.getElementById('originCheckTable');
        testUrls.forEach(item => {
            try {
                const u = new URL(item.url);
                const current = new URL(window.location.href);
                const isSame = u.protocol === current.protocol &&
                               u.hostname === current.hostname &&
                               u.port === current.port;
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td style="color:#e0e0e0;">${item.label}</td>
                    <td>${u.protocol.replace(':', '')}</td>
                    <td>${u.hostname}</td>
                    <td>${u.port || (u.protocol === 'https:' ? '443' : '80')}</td>
                    <td style="color:${isSame ? '#2ecc71' : '#ff6b6b'}; font-weight:600;">${isSame ? '是' : '否'}</td>
                `;
                tbody.appendChild(row);
            } catch (e) {}
        });

        // 流程图切换
        function showFlow(type) {
            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
            event.target.classList.add('active');
            document.getElementById('flowSimple').style.display = type === 'simple' ? 'block' : 'none';
            document.getElementById('flowPreflight').style.display = type === 'preflight' ? 'block' : 'none';
        }

        // 跨域请求测试
        async function sendTestRequest() {
            const method = document.getElementById('testMethod').value;
            const url = document.getElementById('testUrl').value;
            const contentType = document.getElementById('testContentType').value;
            const customHeader = document.getElementById('testCustomHeader').value;
            const result = document.getElementById('testResult');

            result.style.display = 'block';
            result.className = 'result-panel';
            result.textContent = '发送请求中...\n';

            try {
                const options = {
                    method: method,
                    mode: 'cors',
                    headers: {}
                };

                if (contentType) {
                    options.headers['Content-Type'] = contentType;
                }

                if (customHeader) {
                    const [key, value] = customHeader.split('=');
                    if (key && value) {
                        options.headers[key.trim()] = value.trim();
                    }
                }

                if (['POST', 'PUT', 'PATCH'].includes(method)) {
                    options.body = JSON.stringify({ test: 'data' });
                }

                const startTime = performance.now();
                const response = await fetch(url, options);
                const elapsed = (performance.now() - startTime).toFixed(2);

                // 读取响应头
                let headers = '响应头:\n';
                response.headers.forEach((value, key) => {
                    headers += `  ${key}: ${value}\n`;
                });

                const data = await response.text();

                result.className = 'result-panel success';
                result.textContent =
                    ` 请求成功 (${elapsed}ms)\n\n` +
                    `状态码: ${response.status} ${response.statusText}\n` +
                    `类型: ${response.type}\n\n` +
                    headers + '\n' +
                    `响应体:\n${data.substring(0, 500)}`;

            } catch (error) {
                result.className = 'result-panel error';
                result.textContent =
                    ` 请求失败\n\n` +
                    `错误类型: ${error.name}\n` +
                    `错误信息: ${error.message}\n\n` +
                    `可能的原因:\n` +
                    `1. 目标服务器未设置CORS响应头\n` +
                    `2. 请求被浏览器同源策略拦截\n` +
                    `3. 网络连接问题\n` +
                    `4. 服务器不可达`;
            }
        }

        // no-cors模式
        async function sendNoCorsRequest() {
            const url = document.getElementById('testUrl').value;
            const result = document.getElementById('testResult');

            result.style.display = 'block';
            result.className = 'result-panel';
            result.textContent = '发送no-cors请求中...\n';

            try {
                const response = await fetch(url, {
                    mode: 'no-cors'
                });

                result.className = 'result-panel success';
                result.textContent =
                    `no-cors模式响应\n\n` +
                    `状态码: ${response.status}\n` +
                    `类型: ${response.type}\n` +
                    `ok: ${response.ok}\n\n` +
                    `注意:no-cors模式下:\n` +
                    `- 响应类型为 "opaque"\n` +
                    `- 状态码始终为 0\n` +
                    `- 无法读取响应头和响应体\n` +
                    `- 只能知道请求已发送,无法获取结果`;

            } catch (error) {
                result.className = 'result-panel error';
                result.textContent = `请求失败: ${error.message}`;
            }
        }

        // 通过代理请求
        async function sendProxyRequest() {
            const url = document.getElementById('testUrl').value;
            const result = document.getElementById('testResult');

            result.style.display = 'block';
            result.className = 'result-panel';
            result.textContent = '通过CORS代理请求中...\n';

            try {
                // 使用公共CORS代理(仅用于演示,生产环境请自建代理)
                const proxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`;

                const response = await fetch(proxyUrl);
                const data = await response.text();

                result.className = 'result-panel success';
                result.textContent =
                    ` 代理请求成功\n\n` +
                    `代理URL: ${proxyUrl}\n` +
                    `状态码: ${response.status}\n\n` +
                    `响应体:\n${data.substring(0, 500)}\n\n` +
                    `注意:公共CORS代理仅用于开发测试,\n` +
                    `生产环境应使用自己的代理服务器。`;

            } catch (error) {
                result.className = 'result-panel error';
                result.textContent = `代理请求失败: ${error.message}`;
            }
        }
    </script>
</body>
</html>

示例2:各种跨域解决方案对比

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>各种跨域解决方案对比</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #f5f7fa;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { text-align: center; color: #2c3e50; margin-bottom: 30px; }
        .solution-card {
            background: white;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.06);
            border-left: 4px solid;
        }
        .solution-card.cors { border-left-color: #2ecc71; }
        .solution-card.jsonp { border-left-color: #3498db; }
        .solution-card.proxy { border-left-color: #e67e22; }
        .solution-card.postMessage { border-left-color: #9b59b6; }
        .solution-card.websocket { border-left-color: #1abc9c; }
        .solution-card.nginx { border-left-color: #e74c3c; }
        .solution-card h2 {
            font-size: 18px;
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .badge {
            display: inline-block;
            padding: 2px 8px;
            border-radius: 4px;
            font-size: 11px;
            font-weight: 600;
        }
        .badge-recommended { background: #d4edda; color: #155724; }
        .badge-legacy { background: #fff3cd; color: #856404; }
        .badge-advanced { background: #d1ecf1; color: #0c5460; }
        .solution-card p {
            font-size: 14px;
            color: #666;
            line-height: 1.8;
            margin-bottom: 12px;
        }
        .code-block {
            background: #2c3e50;
            color: #2ecc71;
            padding: 16px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.6;
            overflow-x: auto;
            white-space: pre;
            margin: 12px 0;
        }
        .pros-cons {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 12px;
            margin-top: 12px;
        }
        .pros, .cons {
            padding: 12px;
            border-radius: 8px;
            font-size: 13px;
        }
        .pros { background: #d4edda; }
        .cons { background: #f8d7da; }
        .pros h4 { color: #155724; margin-bottom: 6px; }
        .cons h4 { color: #721c24; margin-bottom: 6px; }
        .pros li, .cons li { margin-left: 16px; margin-bottom: 4px; }
        .comparison-table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 20px;
            font-size: 13px;
        }
        .comparison-table th, .comparison-table td {
            padding: 10px 12px;
            border: 1px solid #e0e0e0;
            text-align: center;
        }
        .comparison-table th {
            background: #2c3e50;
            color: white;
        }
        .comparison-table td { color: #555; }
        .check { color: #2ecc71; font-weight: bold; }
        .cross { color: #e74c3c; font-weight: bold; }
        .partial { color: #f39c12; font-weight: bold; }
    </style>
</head>
<body>
    <div class="container">
        <h1>跨域解决方案全面对比</h1>

        <!-- CORS -->
        <div class="solution-card cors">
            <h2>CORS <span class="badge badge-recommended">推荐方案</span></h2>
            <p>
                CORS(Cross-Origin Resource Sharing)是W3C标准的跨域解决方案。
                通过在服务器端设置Access-Control-*响应头,告诉浏览器允许哪些跨域请求。
                这是最现代、最标准的跨域解决方案。
            </p>
            <div class="code-block">// 前端代码
fetch('https://api.example.com/data', {
    method: 'POST',
    mode: 'cors',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: 'test' })
});

// 后端代码(Node.js Express示例)
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', 'https://example.com');
    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');
    res.header('Access-Control-Max-Age', '86400');

    // 处理预检请求
    if (req.method === 'OPTIONS') {
        return res.sendStatus(204);
    }
    next();
});</div>
            <div class="pros-cons">
                <div class="pros">
                    <h4>优点</h4>
                    <ul>
                        <li>W3C标准方案</li>
                        <li>支持所有HTTP方法</li>
                        <li>支持发送和接收自定义头</li>
                        <li>支持携带Cookie</li>
                        <li>错误处理完善</li>
                    </ul>
                </div>
                <div class="cons">
                    <h4>缺点</h4>
                    <ul>
                        <li>需要服务器端配置</li>
                        <li>兼容IE10+</li>
                        <li>预检请求增加延迟</li>
                        <li>某些旧代理可能剥离CORS头</li>
                    </ul>
                </div>
            </div>
        </div>

        <!-- JSONP -->
        <div class="solution-card jsonp">
            <h2>JSONP <span class="badge badge-legacy">传统方案</span></h2>
            <p>
                JSONP(JSON with Padding)利用script标签不受同源策略限制的特性,
                通过动态创建script标签加载跨域数据。只支持GET请求。
            </p>
            <div class="code-block">// 前端代码
function jsonp(url, callbackName) {
    return new Promise((resolve, reject) => {
        // 定义回调函数
        window[callbackName] = function(data) {
            resolve(data);
            delete window[callbackName];
            document.head.removeChild(script);
        };

        // 创建script标签
        const script = document.createElement('script');
        script.src = `${url}?callback=${callbackName}`;
        script.onerror = () => reject(new Error('JSONP请求失败'));
        document.head.appendChild(script);
    });
}

// 使用
jsonp('https://api.example.com/data', 'handleData')
    .then(data => console.log(data));

// 后端返回格式
// handleData({"name": "zhangsan", "age": 25});</div>
            <div class="pros-cons">
                <div class="pros">
                    <h4>优点</h4>
                    <ul>
                        <li>兼容性好(支持IE8+)</li>
                        <li>实现简单</li>
                        <li>不需要服务器特殊配置</li>
                    </ul>
                </div>
                <div class="cons">
                    <h4>缺点</h4>
                    <ul>
                        <li>只支持GET请求</li>
                        <li>存在安全风险(XSS)</li>
                        <li>无法获取HTTP状态码</li>
                        <li>无法设置请求头</li>
                        <li>错误处理困难</li>
                    </ul>
                </div>
            </div>
        </div>

        <!-- 代理服务器 -->
        <div class="solution-card proxy">
            <h2>代理服务器 <span class="badge badge-advanced">开发常用</span></h2>
            <p>
                通过同源的代理服务器转发请求,浏览器只与同源代理通信,
                代理服务器再与目标服务器通信,绕过浏览器同源策略限制。
            </p>
            <div class="code-block">// 开发环境 - Vite配置代理
// vite.config.js
export default {
    server: {
        proxy: {
            '/api': {
                target: 'https://api.example.com',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api/, '')
            }
        }
    }
};

// 开发环境 - webpack devServer配置
module.exports = {
    devServer: {
        proxy: {
            '/api': {
                target: 'https://api.example.com',
                changeOrigin: true,
                pathRewrite: { '^/api': '' }
            }
        }
    }
};

// 生产环境 - Node.js代理
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

app.use('/api', createProxyMiddleware({
    target: 'https://api.example.com',
    changeOrigin: true,
    pathRewrite: { '^/api': '' }
}));</div>
            <div class="pros-cons">
                <div class="pros">
                    <h4>优点</h4>
                    <ul>
                        <li>前端无需修改</li>
                        <li>支持所有HTTP方法</li>
                        <li>可添加额外处理逻辑</li>
                        <li>开发环境配置简单</li>
                    </ul>
                </div>
                <div class="cons">
                    <h4>缺点</h4>
                    <ul>
                        <li>增加服务器负担</li>
                        <li>多一层网络延迟</li>
                        <li>生产环境需额外部署</li>
                        <li>可能成为性能瓶颈</li>
                    </ul>
                </div>
            </div>
        </div>

        <!-- postMessage -->
        <div class="solution-card postMessage">
            <h2>postMessage <span class="badge badge-advanced">iframe通信</span></h2>
            <p>
                window.postMessage方法可以安全地实现跨源通信,
                主要用于不同源的两个窗口(如主页面和iframe)之间的数据传递。
            </p>
            <div class="code-block">// 主页面(发送消息到iframe)
const iframe = document.getElementById('myIframe');
iframe.contentWindow.postMessage(
    { type: 'REQUEST_DATA', payload: { id: 123 } },
    'https://other-domain.com'  // 目标源
);

// 主页面(接收iframe的回复)
window.addEventListener('message', (event) => {
    // 验证消息来源
    if (event.origin !== 'https://other-domain.com') return;

    console.log('收到消息:', event.data);
});

// iframe页面(接收消息并回复)
window.addEventListener('message', (event) => {
    if (event.origin !== 'https://main-domain.com') return;

    if (event.data.type === 'REQUEST_DATA') {
        // 处理请求
        event.source.postMessage(
            { type: 'RESPONSE_DATA', payload: { result: 'success' } },
            event.origin
        );
    }
});</div>
            <div class="pros-cons">
                <div class="pros">
                    <h4>优点</h4>
                    <ul>
                        <li>安全可控</li>
                        <li>双向通信</li>
                        <li>支持复杂数据</li>
                        <li>兼容性好</li>
                    </ul>
                </div>
                <div class="cons">
                    <h4>缺点</h4>
                    <ul>
                        <li>仅限窗口间通信</li>
                        <li>需要iframe配合</li>
                        <li>需验证消息来源</li>
                        <li>不适合API请求</li>
                    </ul>
                </div>
            </div>
        </div>

        <!-- Nginx反向代理 -->
        <div class="solution-card nginx">
            <h2>Nginx反向代理 <span class="badge badge-recommended">生产推荐</span></h2>
            <p>
                使用Nginx作为反向代理,将API请求转发到后端服务器,
                同时可以统一域名和端口,从根本上解决跨域问题。
            </p>
            <div class="code-block"># Nginx配置
server {
    listen 80;
    server_name example.com;

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

    # API代理
    location /api/ {
        proxy_pass https://api-server: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头
    location /api/ {
        proxy_pass https://api-server:8080/;
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS";
        add_header Access-Control-Allow-Headers "Content-Type,Authorization";

        if ($request_method = OPTIONS) {
            return 204;
        }
    }
}</div>
            <div class="pros-cons">
                <div class="pros">
                    <h4>优点</h4>
                    <ul>
                        <li>前端完全无感知</li>
                        <li>性能好</li>
                        <li>可负载均衡</li>
                        <li>可缓存静态资源</li>
                    </ul>
                </div>
                <div class="cons">
                    <h4>缺点</h4>
                    <ul>
                        <li>需要运维配置</li>
                        <li>增加架构复杂度</li>
                        <li>调试相对困难</li>
                    </ul>
                </div>
            </div>
        </div>

        <!-- 综合对比表 -->
        <div class="solution-card" style="border-left-color:#2c3e50;">
            <h2>方案综合对比</h2>
            <table class="comparison-table">
                <thead>
                    <tr>
                        <th>方案</th>
                        <th>GET</th>
                        <th>POST</th>
                        <th>PUT/DELETE</th>
                        <th>Cookie</th>
                        <th>自定义头</th>
                        <th>需要后端</th>
                        <th>推荐度</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td><strong>CORS</strong></td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">首选</td>
                    </tr>
                    <tr>
                        <td><strong>JSONP</strong></td>
                        <td class="check">✓</td>
                        <td class="cross">✗</td>
                        <td class="cross">✗</td>
                        <td class="cross">✗</td>
                        <td class="cross">✗</td>
                        <td class="partial">部分</td>
                        <td class="cross">过时</td>
                    </tr>
                    <tr>
                        <td><strong>代理服务器</strong></td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">推荐</td>
                    </tr>
                    <tr>
                        <td><strong>postMessage</strong></td>
                        <td class="partial">特殊</td>
                        <td class="partial">特殊</td>
                        <td class="cross">✗</td>
                        <td class="cross">✗</td>
                        <td class="cross">✗</td>
                        <td class="check">✓</td>
                        <td class="partial">特定场景</td>
                    </tr>
                    <tr>
                        <td><strong>Nginx代理</strong></td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">✓</td>
                        <td class="check">推荐</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</body>
</html>

示例3:CORS预检请求与缓存

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CORS预检请求与缓存</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #0f0f23;
            color: #e0e0e0;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 800px; margin: 0 auto; }
        h1 { text-align: center; color: #48dbfb; margin-bottom: 30px; }
        .card {
            background: #1a1a2e;
            border: 1px solid #2d2d44;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 20px;
        }
        .card h2 { font-size: 16px; color: #feca57; margin-bottom: 16px; }
        .scenario {
            background: #0a0a1a;
            border-radius: 8px;
            padding: 16px;
            margin-bottom: 12px;
        }
        .scenario h3 {
            font-size: 14px;
            margin-bottom: 8px;
        }
        .scenario .result {
            font-size: 13px;
            padding: 8px 12px;
            border-radius: 6px;
            margin-top: 8px;
        }
        .result.simple { background: #0a2e1a; color: #2ecc71; border-left: 3px solid #2ecc71; }
        .result.preflight { background: #2e1a0a; color: #feca57; border-left: 3px solid #feca57; }
        .request-log {
            background: #0a0a1a;
            border-radius: 8px;
            padding: 16px;
            font-family: monospace;
            font-size: 12px;
            max-height: 400px;
            overflow-y: auto;
            white-space: pre-wrap;
            word-break: break-all;
        }
        .log-time { color: #888; }
        .log-method { color: #48dbfb; font-weight: bold; }
        .log-url { color: #feca57; }
        .log-header { color: #2ecc71; }
        .log-status { color: #ff6b6b; font-weight: bold; }
        .log-success { color: #2ecc71; font-weight: bold; }
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
            margin: 4px;
        }
        .btn-primary { background: #48dbfb; color: #0f0f23; }
        .btn-primary:hover { background: #0abde3; }
        .btn-warning { background: #feca57; color: #0f0f23; }
        .btn-warning:hover { background: #f0b730; }
        .btn-danger { background: #ff6b6b; color: white; }
        .btn-danger:hover { background: #ee5a5a; }
        .test-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
            gap: 10px;
            margin-top: 12px;
        }
        .test-item {
            padding: 12px;
            background: #0f3460;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.3s;
            text-align: center;
        }
        .test-item:hover { background: #162d50; transform: translateY(-2px); }
        .test-item .method { color: #48dbfb; font-weight: 600; font-size: 13px; }
        .test-item .desc { color: #888; font-size: 11px; margin-top: 4px; }
        .test-item .type { font-size: 10px; margin-top: 6px; padding: 2px 6px; border-radius: 3px; }
        .type-simple { background: #0a2e1a; color: #2ecc71; }
        .type-preflight { background: #2e1a0a; color: #feca57; }
    </style>
</head>
<body>
    <div class="container">
        <h1>CORS 预检请求与缓存</h1>

        <!-- 判断规则 -->
        <div class="card">
            <h2>简单请求 vs 预检请求 判断</h2>

            <div class="scenario">
                <h3 style="color:#2ecc71;">简单请求场景</h3>
                <div id="simpleScenarios"></div>
            </div>

            <div class="scenario">
                <h3 style="color:#feca57;">预检请求场景</h3>
                <div id="preflightScenarios"></div>
            </div>
        </div>

        <!-- 交互式测试 -->
        <div class="card">
            <h2>交互式请求测试</h2>
            <p style="font-size:13px; color:#888; margin-bottom:12px;">
                点击下方按钮发送不同类型的请求,观察是否触发预检请求。
            </p>

            <div class="test-grid">
                <div class="test-item" onclick="testRequest('GET', {})">
                    <div class="method">GET</div>
                    <div class="desc">无自定义头</div>
                    <div class="type type-simple">简单请求</div>
                </div>
                <div class="test-item" onclick="testRequest('POST', {'Content-Type':'application/x-www-form-urlencoded'})">
                    <div class="method">POST</div>
                    <div class="desc">x-www-form-urlencoded</div>
                    <div class="type type-simple">简单请求</div>
                </div>
                <div class="test-item" onclick="testRequest('POST', {'Content-Type':'application/json'})">
                    <div class="method">POST</div>
                    <div class="desc">application/json</div>
                    <div class="type type-preflight">预检请求</div>
                </div>
                <div class="test-item" onclick="testRequest('PUT', {})">
                    <div class="method">PUT</div>
                    <div class="desc">PUT方法</div>
                    <div class="type type-preflight">预检请求</div>
                </div>
                <div class="test-item" onclick="testRequest('DELETE', {})">
                    <div class="method">DELETE</div>
                    <div class="desc">DELETE方法</div>
                    <div class="type type-preflight">预检请求</div>
                </div>
                <div class="test-item" onclick="testRequest('GET', {'X-Custom-Header':'value'})">
                    <div class="method">GET</div>
                    <div class="desc">自定义头</div>
                    <div class="type type-preflight">预检请求</div>
                </div>
                <div class="test-item" onclick="testRequest('POST', {'Authorization':'Bearer token'})">
                    <div class="method">POST</div>
                    <div class="desc">Authorization头</div>
                    <div class="type type-preflight">预检请求</div>
                </div>
                <div class="test-item" onclick="testRequest('PATCH', {})">
                    <div class="method">PATCH</div>
                    <div class="desc">PATCH方法</div>
                    <div class="type type-preflight">预检请求</div>
                </div>
            </div>

            <div style="margin-top:16px; display:flex; gap:8px;">
                <button class="btn btn-warning" onclick="clearLog()">清空日志</button>
            </div>

            <div class="request-log" id="requestLog">等待测试...</div>
        </div>

        <!-- 预检缓存 -->
        <div class="card">
            <h2>预检请求缓存</h2>
            <p style="font-size:13px; color:#888; line-height:1.8;">
                预检请求会增加额外的网络开销。通过 Access-Control-Max-Age 头部,
                服务器可以告诉浏览器缓存预检结果的时间。<br>
                在缓存有效期内,相同来源的跨域请求不需要再次发送预检请求。<br><br>
                各浏览器对预检缓存的最大时间限制不同:<br>
                - Chrome: 7200秒(2小时)<br>
                - Firefox: 86400秒(24小时)<br>
                - Safari: 604800秒(7天)
            </p>
            <div style="margin-top:12px; display:flex; gap:8px;">
                <button class="btn btn-primary" onclick="testPreflightCache()">测试预检缓存</button>
            </div>
            <div class="request-log" id="cacheLog" style="margin-top:12px;">等待测试...</div>
        </div>
    </div>

    <script>
        // 填充场景列表
        const simpleScenarios = [
            'GET请求,无自定义头',
            'HEAD请求,无自定义头',
            'POST请求,Content-Type: application/x-www-form-urlencoded',
            'POST请求,Content-Type: multipart/form-data',
            'POST请求,Content-Type: text/plain',
        ];
        const preflightScenarios = [
            'PUT、DELETE、PATCH等非简单方法',
            'Content-Type: application/json',
            '自定义请求头(如X-Custom-Header)',
            'Authorization头',
            '请求中使用了ReadableStream',
        ];

        document.getElementById('simpleScenarios').innerHTML = simpleScenarios
            .map(s => `<div class="result simple">${s}</div>`).join('');
        document.getElementById('preflightScenarios').innerHTML = preflightScenarios
            .map(s => `<div class="result preflight">${s}</div>`).join('');

        // 请求日志
        let logCount = 0;

        function addLog(message) {
            const log = document.getElementById('requestLog');
            const time = new Date().toLocaleTimeString();
            logCount++;
            log.textContent += `[${time}] #${logCount} ${message}\n`;
            log.scrollTop = log.scrollHeight;
        }

        function clearLog() {
            document.getElementById('requestLog').textContent = '日志已清空\n';
            logCount = 0;
        }

        // 判断请求类型
        function isSimpleRequest(method, headers) {
            // 检查方法
            if (!['GET', 'HEAD', 'POST'].includes(method)) return false;

            // 检查Content-Type
            const contentType = headers['Content-Type'] || '';
            const simpleContentTypes = [
                'application/x-www-form-urlencoded',
                'multipart/form-data',
                'text/plain'
            ];
            if (contentType && !simpleContentTypes.includes(contentType)) return false;

            // 检查自定义头
            const simpleHeaders = ['accept', 'accept-language', 'content-language', 'content-type'];
            const customHeaders = Object.keys(headers).filter(
                h => !simpleHeaders.includes(h.toLowerCase())
            );
            if (customHeaders.length > 0) return false;

            return true;
        }

        // 测试请求
        async function testRequest(method, headers) {
            const url = 'https://httpbin.org/' + method.toLowerCase();
            const isSimple = isSimpleRequest(method, headers);

            addLog(``);
            addLog(`<span class="log-method">${method}</span> <span class="log-url">${url}</span>`);
            addLog(`请求类型: ${isSimple ? '简单请求' : '预检请求'}`);

            if (!isSimple) {
                addLog(`触发预检原因:`);
                if (!['GET', 'HEAD', 'POST'].includes(method)) {
                    addLog(`  - 非简单方法: ${method}`);
                }
                const contentType = headers['Content-Type'];
                if (contentType && contentType !== 'application/x-www-form-urlencoded' &&
                    contentType !== 'multipart/form-data' && contentType !== 'text/plain') {
                    addLog(`  - 非简单Content-Type: ${contentType}`);
                }
                const customHeaders = Object.keys(headers).filter(
                    h => !['accept', 'accept-language', 'content-language', 'content-type'].includes(h.toLowerCase())
                );
                if (customHeaders.length > 0) {
                    addLog(`  - 自定义头: ${customHeaders.join(', ')}`);
                }
            }

            addLog(`请求头: ${JSON.stringify(headers)}`);

            try {
                const startTime = performance.now();
                const options = { method, mode: 'cors', headers };

                if (['POST', 'PUT', 'PATCH'].includes(method)) {
                    options.body = JSON.stringify({ test: 'data' });
                }

                const response = await fetch(url, options);
                const elapsed = (performance.now() - startTime).toFixed(2);

                addLog(`<span class="log-success"> 响应: ${response.status} (${elapsed}ms)</span>`);

                // 读取CORS相关响应头
                const corsHeaders = [
                    'access-control-allow-origin',
                    'access-control-allow-methods',
                    'access-control-allow-headers',
                    'access-control-allow-credentials',
                    'access-control-max-age'
                ];
                corsHeaders.forEach(h => {
                    const value = response.headers.get(h);
                    if (value) {
                        addLog(`  <span class="log-header">${h}: ${value}</span>`);
                    }
                });

            } catch (error) {
                addLog(`<span class="log-status"> 失败: ${error.message}</span>`);
            }

            addLog('');
        }

        // 测试预检缓存
        async function testPreflightCache() {
            const cacheLog = document.getElementById('cacheLog');
            cacheLog.textContent = '测试预检缓存...\n\n';

            const url = 'https://httpbin.org/put';
            const headers = { 'Content-Type': 'application/json', 'X-Custom': 'test' };

            // 第一次请求(可能触发预检)
            cacheLog.textContent += '第1次请求(可能触发预检):\n';
            const start1 = performance.now();
            try {
                await fetch(url, { method: 'PUT', mode: 'cors', headers, body: '{}' });
                cacheLog.textContent += `  耗时: ${(performance.now() - start1).toFixed(0)}ms\n`;
            } catch (e) {
                cacheLog.textContent += `  失败: ${e.message}\n`;
            }

            // 短暂延迟后第二次请求
            await new Promise(r => setTimeout(r, 500));

            cacheLog.textContent += '\n第2次请求(预检可能已缓存):\n';
            const start2 = performance.now();
            try {
                await fetch(url, { method: 'PUT', mode: 'cors', headers, body: '{}' });
                cacheLog.textContent += `  耗时: ${(performance.now() - start2).toFixed(0)}ms\n`;
            } catch (e) {
                cacheLog.textContent += `  失败: ${e.message}\n`;
            }

            cacheLog.textContent += '\n注意:如果第2次请求更快,说明预检结果被缓存了。\n';
            cacheLog.textContent += '缓存时间由服务器的 Access-Control-Max-Age 头决定。';
        }
    </script>
</body>
</html>

示例4:CORS与Cookie凭证

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CORS与Cookie凭证</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #fafafa;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 800px; margin: 0 auto; }
        h1 { text-align: center; color: #2c3e50; margin-bottom: 30px; }
        .card {
            background: white;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 20px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.06);
        }
        .card h2 { font-size: 16px; color: #2c3e50; margin-bottom: 16px; }
        .warning-box {
            background: #fff3cd;
            border-left: 4px solid #ffc107;
            padding: 12px 16px;
            border-radius: 4px;
            margin-bottom: 16px;
            font-size: 13px;
            color: #856404;
            line-height: 1.8;
        }
        .info-box {
            background: #d1ecf1;
            border-left: 4px solid #17a2b8;
            padding: 12px 16px;
            border-radius: 4px;
            margin-bottom: 16px;
            font-size: 13px;
            color: #0c5460;
            line-height: 1.8;
        }
        .code-block {
            background: #2c3e50;
            color: #2ecc71;
            padding: 16px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.6;
            overflow-x: auto;
            white-space: pre;
            margin: 12px 0;
        }
        .config-table {
            width: 100%;
            border-collapse: collapse;
            font-size: 13px;
            margin: 12px 0;
        }
        .config-table th, .config-table td {
            padding: 10px 12px;
            border: 1px solid #e0e0e0;
            text-align: left;
        }
        .config-table th {
            background: #2c3e50;
            color: white;
        }
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
            margin: 4px;
        }
        .btn-primary { background: #3498db; color: white; }
        .btn-primary:hover { background: #2980b9; }
        .result-area {
            background: #2c3e50;
            color: #2ecc71;
            padding: 16px;
            border-radius: 8px;
            font-family: monospace;
            font-size: 13px;
            white-space: pre-wrap;
            word-break: break-all;
            max-height: 200px;
            overflow-y: auto;
            margin-top: 12px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>CORS 与 Cookie 凭证</h1>

        <div class="card">
            <h2>CORS携带凭证的三要素</h2>
            <div class="warning-box">
                <strong>重要:</strong>跨域请求携带Cookie需要前后端同时配置,缺一不可!
            </div>

            <table class="config-table">
                <thead>
                    <tr>
                        <th>要素</th>
                        <th>前端配置</th>
                        <th>后端配置</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td><strong>1. 凭证标志</strong></td>
                        <td>credentials: 'include'</td>
                        <td>Access-Control-Allow-Credentials: true</td>
                    </tr>
                    <tr>
                        <td><strong>2. 允许源</strong></td>
                        <td>-</td>
                        <td>Access-Control-Allow-Origin 必须是具体域名,不能是 *</td>
                    </tr>
                    <tr>
                        <td><strong>3. Cookie属性</strong></td>
                        <td>-</td>
                        <td>Set-Cookie: SameSite=None; Secure</td>
                    </tr>
                </tbody>
            </table>

            <div class="code-block">// ===== 前端代码 =====

// Fetch API
fetch('https://api.example.com/data', {
    method: 'POST',
    mode: 'cors',
    credentials: 'include',  // 关键:携带Cookie
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: 'test' })
});

// XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/data', true);
xhr.withCredentials = true;  // 关键:携带Cookie
xhr.send();

// ===== 后端代码(Node.js) =====

// 错误配置 - 携带凭证时不能使用 *
res.header('Access-Control-Allow-Origin', '*');  //  浏览器会拒绝
res.header('Access-Control-Allow-Credentials', 'true');

// 正确配置 - 必须指定具体域名
res.header('Access-Control-Allow-Origin', 'https://example.com');  // 
res.header('Access-Control-Allow-Credentials', 'true');

// 动态设置允许的源
const allowedOrigins = ['https://example.com', 'https://app.example.com'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin);
    res.header('Access-Control-Allow-Credentials', 'true');
}

// Cookie设置(跨域场景)
res.cookie('token', 'abc123', {
    httpOnly: true,
    secure: true,      // HTTPS only
    sameSite: 'none',  // 允许跨域发送
    maxAge: 86400000   // 1天
});</div>
        </div>

        <div class="card">
            <h2>credentials模式对比</h2>
            <table class="config-table">
                <thead>
                    <tr>
                        <th>值</th>
                        <th>说明</th>
                        <th>发送Cookie</th>
                        <th>适用场景</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td><code>omit</code></td>
                        <td>从不发送Cookie</td>
                        <td>否</td>
                        <td>不需要认证的公开API</td>
                    </tr>
                    <tr>
                        <td><code>same-origin</code></td>
                        <td>仅同源时发送Cookie(默认)</td>
                        <td>同源时发送</td>
                        <td>默认行为</td>
                    </tr>
                    <tr>
                        <td><code>include</code></td>
                        <td>始终发送Cookie</td>
                        <td>始终发送</td>
                        <td>跨域认证请求</td>
                    </tr>
                </tbody>
            </table>
        </div>

        <div class="card">
            <h2>SameSite Cookie属性</h2>
            <div class="info-box">
                Chrome 80+ 默认将Cookie的SameSite设为Lax,这意味着跨站POST请求不会发送Cookie。
                跨域场景需要显式设置 SameSite=None; Secure。
            </div>
            <table class="config-table">
                <thead>
                    <tr>
                        <th>SameSite值</th>
                        <th>跨站GET</th>
                        <th>跨站POST</th>
                        <th>跨站XHR/Fetch</th>
                        <th>安全性</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td><code>Strict</code></td>
                        <td>不发送</td>
                        <td>不发送</td>
                        <td>不发送</td>
                        <td>最高</td>
                    </tr>
                    <tr>
                        <td><code>Lax</code>(默认)</td>
                        <td>发送</td>
                        <td>不发送</td>
                        <td>不发送</td>
                        <td>中</td>
                    </tr>
                    <tr>
                        <td><code>None</code></td>
                        <td>发送</td>
                        <td>发送</td>
                        <td>发送</td>
                        <td>需配合Secure</td>
                    </tr>
                </tbody>
            </table>
        </div>

        <div class="card">
            <h2>常见错误排查</h2>
            <div class="code-block">// 错误1:Allow-Origin为*且Allow-Credentials为true
// 浏览器控制台错误:
// "Credentials flag is 'true', but the 'Access-Control-Allow-Origin'
//  header is '*'."
// 解决:将*替换为具体的域名

// 错误2:Cookie未设置SameSite=None
// 浏览器控制台警告:
// "A cookie associated with a cross-site resource was set without
//  the `SameSite` attribute."
// 解决:Set-Cookie: token=abc; SameSite=None; Secure

// 错误3:重定向后丢失CORS头
// 浏览器控制台错误:
// "Redirect from 'https://a.com/api' to 'https://b.com/api'
//  has been blocked by CORS policy."
// 解决:确保重定向目标也设置了正确的CORS头

// 错误4:响应头缺少Expose-Headers
// 前端无法读取自定义响应头
// 解决:后端添加 Access-Control-Expose-Headers: X-Total-Count

// 错误5:预检请求未正确处理
// 浏览器控制台错误:
// "Response to preflight request doesn't pass access control check."
// 解决:确保OPTIONS请求返回204且包含正确的CORS头</div>
        </div>
    </div>
</body>
</html>

示例5:开发环境代理配置

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>开发环境代理配置</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #1e1e2e;
            color: #cdd6f4;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 900px; margin: 0 auto; }
        h1 { text-align: center; color: #89b4fa; margin-bottom: 30px; }
        .tool-card {
            background: #313244;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
            border: 1px solid #45475a;
        }
        .tool-card h2 {
            font-size: 16px;
            margin-bottom: 16px;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .tool-tag {
            padding: 2px 8px;
            border-radius: 4px;
            font-size: 11px;
            font-weight: 600;
        }
        .tag-vite { background: #646cff; color: white; }
        .tag-webpack { background: #8dd6f9; color: #1e1e2e; }
        .tag-node { background: #a6e3a1; color: #1e1e2e; }
        .tag-next { background: #f38ba8; color: #1e1e2e; }
        .code-block {
            background: #1e1e2e;
            color: #a6e3a1;
            padding: 16px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.6;
            overflow-x: auto;
            white-space: pre;
            margin: 12px 0;
        }
        .comment { color: #6c7086; }
        .keyword { color: #cba6f7; }
        .string { color: #a6e3a1; }
        .number { color: #fab387; }
        p { font-size: 14px; line-height: 1.8; margin-bottom: 12px; color: #bac2de; }
    </style>
</head>
<body>
    <div class="container">
        <h1>开发环境代理配置大全</h1>

        <!-- Vite -->
        <div class="tool-card">
            <h2>Vite 代理配置 <span class="tool-tag tag-vite">Vite</span></h2>
            <div class="code-block"><span class="comment">// vite.config.js</span>
<span class="keyword">import</span> { defineConfig } <span class="keyword">from</span> <span class="string">'vite'</span>
<span class="keyword">import</span> react <span class="keyword">from</span> <span class="string">'@vitejs/plugin-react'</span>

<span class="keyword">export default</span> defineConfig({
  plugins: [react()],
  server: {
    <span class="comment">// 基础代理配置</span>
    proxy: {
      <span class="comment">// 将 /api 请求代理到目标服务器</span>
      <span class="string">'/api'</span>: {
        target: <span class="string">'https://api.example.com'</span>,
        changeOrigin: <span class="keyword">true</span>,
        <span class="comment">// 重写路径:/api/users → /users</span>
        rewrite: (path) => path.replace(<span class="string">/^\/api/</span>, <span class="string">''</span>)
      },

      <span class="comment">// WebSocket代理</span>
      <span class="string">'/ws'</span>: {
        target: <span class="string">'ws://localhost:8080'</span>,
        ws: <span class="keyword">true</span>
      },

      <span class="comment">// 多个API端点</span>
      <span class="string">'/auth'</span>: {
        target: <span class="string">'https://auth.example.com'</span>,
        changeOrigin: <span class="keyword">true</span>
      }
    }
  }
})</div>
        </div>

        <!-- Webpack -->
        <div class="tool-card">
            <h2>Webpack DevServer 代理配置 <span class="tool-tag tag-webpack">Webpack</span></h2>
            <div class="code-block"><span class="comment">// webpack.config.js</span>
module.exports = {
  devServer: {
    proxy: {
      <span class="comment">// 基础代理</span>
      <span class="string">'/api'</span>: {
        target: <span class="string">'https://api.example.com'</span>,
        changeOrigin: <span class="keyword">true</span>,
        pathRewrite: { <span class="string">'^/api'</span>: <span class="string">''</span> },
        <span class="comment">// 添加自定义请求头</span>
        headers: {
          <span class="string">'X-Custom-Header'</span>: <span class="string">'dev-proxy'</span>
        },
        <span class="comment">// 仅代理HTTPS请求</span>
        secure: <span class="keyword">true</span>,
        <span class="comment">// 代理超时</span>
        timeout: <span class="number">30000</span>
      },

      <span class="comment">// 条件代理</span>
      <span class="string">'/api'</span>: {
        target: <span class="string">'https://api.example.com'</span>,
        changeOrigin: <span class="keyword">true</span>,
        <span class="comment">// 仅代理GET请求</span>
        bypass: function(req, res, options) {
          <span class="keyword">if</span> (req.method !== <span class="string">'GET'</span>) {
            <span class="keyword">return</span> <span class="string">'/fallback.html'</span>;
          }
        }
      },

      <span class="comment">// 多个目标</span>
      <span class="string">'/api/v1'</span>: {
        target: <span class="string">'https://v1.example.com'</span>,
        changeOrigin: <span class="keyword">true</span>
      },
      <span class="string">'/api/v2'</span>: {
        target: <span class="string">'https://v2.example.com'</span>,
        changeOrigin: <span class="keyword">true</span>
      }
    }
  }
}</div>
        </div>

        <!-- Node.js -->
        <div class="tool-card">
            <h2>Node.js 代理服务器 <span class="tool-tag tag-node">Node.js</span></h2>
            <div class="code-block"><span class="comment">// server.js - Express + http-proxy-middleware</span>
<span class="keyword">const</span> express = require(<span class="string">'express'</span>);
<span class="keyword">const</span> { createProxyMiddleware } = require(<span class="string">'http-proxy-middleware'</span>);

<span class="keyword">const</span> app = express();

<span class="comment">// CORS中间件(如果前后端分离部署)</span>
app.use((req, res, next) => {
  <span class="keyword">const</span> allowedOrigins = [<span class="string">'http://localhost:3000'</span>];
  <span class="keyword">const</span> origin = req.headers.origin;

  <span class="keyword">if</span> (allowedOrigins.includes(origin)) {
    res.setHeader(<span class="string">'Access-Control-Allow-Origin'</span>, origin);
    res.setHeader(<span class="string">'Access-Control-Allow-Methods'</span>, <span class="string">'GET,POST,PUT,DELETE'</span>);
    res.setHeader(<span class="string">'Access-Control-Allow-Headers'</span>, <span class="string">'Content-Type,Authorization'</span>);
    res.setHeader(<span class="string">'Access-Control-Allow-Credentials'</span>, <span class="string">'true'</span>);
  }

  <span class="keyword">if</span> (req.method === <span class="string">'OPTIONS'</span>) {
    <span class="keyword">return</span> res.sendStatus(<span class="number">204</span>);
  }
  next();
});

<span class="comment">// API代理</span>
app.use(<span class="string">'/api'</span>, createProxyMiddleware({
  target: <span class="string">'https://api.example.com'</span>,
  changeOrigin: <span class="keyword">true</span>,
  pathRewrite: { <span class="string">'^/api'</span>: <span class="string">''</span> },
  <span class="comment">// 修改代理请求</span>
  onProxyReq: (proxyReq, req, res) => {
    <span class="comment">// 添加认证头</span>
    proxyReq.setHeader(<span class="string">'X-Proxy-Secret'</span>, <span class="string">'my-secret'</span>);
  },
  <span class="comment">// 处理代理响应</span>
  onProxyRes: (proxyRes, req, res) => {
    console.log(<span class="string">`[Proxy] ${req.method} ${req.url} → ${proxyRes.statusCode}`</span>);
  },
  <span class="comment">// 错误处理</span>
  onError: (err, req, res) => {
    console.error(<span class="string">'Proxy error:'</span>, err);
    res.status(<span class="number">500</span>).json({ error: <span class="string">'Proxy error'</span> });
  }
}));

<span class="comment">// 静态文件服务</span>
app.use(express.static(<span class="string">'dist'</span>));

app.listen(<span class="number">3000</span>, () => {
  console.log(<span class="string">'Server running on http://localhost:3000'</span>);
});</div>
        </div>

        <!-- Next.js -->
        <div class="tool-card">
            <h2>Next.js API Routes 代理 <span class="tool-tag tag-next">Next.js</span></h2>
            <div class="code-block"><span class="comment">// next.config.js - Rewrites方式</span>
module.exports = {
  async rewrites() {
    <span class="keyword">return</span> [
      {
        source: <span class="string">'/api/:path*'</span>,
        destination: <span class="string">'https://api.example.com/:path*'</span>,
      },
    ]
  }
}

<span class="comment">// pages/api/[...path].js - API Route方式</span>
<span class="keyword">export default async function</span> handler(req, res) {
  <span class="keyword">const</span> { path } = req.query;
  <span class="keyword">const</span> targetUrl = <span class="string">`https://api.example.com/${path.join('/')}`</span>;

  <span class="keyword">try</span> {
    <span class="keyword">const</span> response = <span class="keyword">await</span> fetch(targetUrl, {
      method: req.method,
      headers: {
        <span class="string">'Content-Type'</span>: <span class="string">'application/json'</span>,
        <span class="string">'Authorization'</span>: req.headers.authorization || <span class="string">''</span>,
      },
      body: [<span class="string">'GET'</span>, <span class="string">'HEAD'</span>].includes(req.method) ? <span class="keyword">undefined</span> : JSON.stringify(req.body),
    });

    <span class="keyword">const</span> data = <span class="keyword">await</span> response.json();
    res.status(response.status).json(data);
  } <span class="keyword">catch</span> (error) {
    res.status(<span class="number">500</span>).json({ error: <span class="string">'Proxy error'</span> });
  }
}</div>
        </div>
    </div>
</body>
</html>

浏览器兼容性

特性 Chrome Firefox Safari Edge IE
CORS (XMLHttpRequest) 4+ 3.5+ 4+ 12+ 10+
CORS (Fetch API) 42+ 39+ 10.1+ 14+ 不支持
withCredentials 3+ 3.5+ 4+ 12+ 10+
Access-Control-Max-Age 4+ 3.5+ 4+ 12+ 10+
Access-Control-Expose-Headers 4+ 3.5+ 4+ 12+ 不支持
SameSite Cookie 76+ 60+ 13+ 79+ 不支持
postMessage 2+ 3+ 4+ 12+ 8+
JSONP 全部 全部 全部 全部 全部

注意事项与最佳实践

1. 安全配置原则

代码示例

// 后端CORS安全配置(Node.js示例)
function corsMiddleware(req, res, next) {
    // 1. 不要使用 * 通配符(生产环境)
    // res.header('Access-Control-Allow-Origin', '*');  //  不安全

    // 2. 使用白名单验证Origin
    const allowedOrigins = [
        'https://example.com',
        'https://app.example.com'
    ];
    const origin = req.headers.origin;

    if (allowedOrigins.includes(origin)) {
        res.header('Access-Control-Allow-Origin', origin);  //  动态设置
    } else {
        // 不在白名单中的源,不设置CORS头
        return res.status(403).json({ error: 'Origin not allowed' });
    }

    // 3. 限制允许的方法
    res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE');

    // 4. 限制允许的请求头
    res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization');

    // 5. 仅在需要时允许凭证
    res.header('Access-Control-Allow-Credentials', 'true');

    // 6. 设置合理的预检缓存时间
    res.header('Access-Control-Max-Age', '86400');

    // 7. 暴露必要的响应头
    res.header('Access-Control-Expose-Headers', 'X-Total-Count,X-Request-Id');

    // 8. 处理预检请求
    if (req.method === 'OPTIONS') {
        return res.sendStatus(204);
    }

    next();
}

2. 避免CORS的常见陷阱

代码示例

// 陷阱1:重定向导致CORS头丢失
// 301/302重定向后,浏览器会重新检查CORS头
// 如果重定向目标没有设置CORS头,请求会失败
// 解决:确保重定向目标也设置了正确的CORS头

// 陷阱2:错误处理中的CORS问题
fetch('https://api.example.com/data')
    .then(response => {
        // 即使HTTP状态码是4xx/5xx,只要CORS头正确,
        // 这个then也会执行(不会进入catch)
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        return response.json();
    })
    .catch(error => {
        // 只有网络错误或CORS被拒绝时才会进入catch
        // CORS被拒绝时,error.message通常是 "Failed to fetch"
        // 无法获取具体的CORS错误信息
        console.error('请求失败:', error);
    });

// 陷阱3:304 Not Modified与CORS
// 浏览器缓存了304响应,但CORS头可能已变化
// 解决:在开发时禁用缓存,或确保CORS配置一致

// 陷阱4:文件协议(file://)的CORS限制
// file://协议的页面发送请求时,Origin为null
// 大多数服务器不会允许Origin: null
// 解决:使用本地开发服务器(http://localhost)

3. 调试CORS问题

代码示例

// 1. 使用浏览器开发者工具
// Network面板中查看:
// - 请求是否发送了OPTIONS预检请求
// - 响应头中是否包含Access-Control-*头
// - 控制台中的CORS错误信息

// 2. 常见CORS错误信息及解决方案
const corsErrors = {
    'No Access-Control-Allow-Origin':
        '服务器未设置Access-Control-Allow-Origin响应头',

    'CORS policy: Response to preflight':
        '预检请求失败,检查OPTIONS响应的CORS头',

    'Credentials flag is true':
        '携带凭证时Allow-Origin不能为*,需指定具体域名',

    'Redirect is not allowed':
        '预检请求不能重定向',

    'Does not match any allowed origins':
        '请求源不在服务器允许的列表中'
};

// 3. 使用curl测试(绕过浏览器CORS)
// curl -H "Origin: https://example.com" \
//      -H "Access-Control-Request-Method: POST" \
//      -H "Access-Control-Request-Headers: Content-Type" \
//      -X OPTIONS https://api.example.com/data -v

代码规范示例

规范的CORS中间件

代码示例

/**
 * CORS中间件
 * 提供安全、灵活的跨域配置
 */
class CorsMiddleware {
    /**
     * @param {Object} options - 配置项
     * @param {string[]} options.allowedOrigins - 允许的源列表
     * @param {string[]} options.allowedMethods - 允许的HTTP方法
     * @param {string[]} options.allowedHeaders - 允许的请求头
     * @param {string[]} options.exposedHeaders - 暴露的响应头
     * @param {boolean} options.allowCredentials - 是否允许凭证
     * @param {number} options.maxAge - 预检缓存时间(秒)
     */
    constructor(options = {}) {
        this.allowedOrigins = options.allowedOrigins || [];
        this.allowedMethods = options.allowedMethods || ['GET', 'POST', 'PUT', 'DELETE'];
        this.allowedHeaders = options.allowedHeaders || ['Content-Type', 'Authorization'];
        this.exposedHeaders = options.exposedHeaders || [];
        this.allowCredentials = options.allowCredentials || false;
        this.maxAge = options.maxAge || 86400;
    }

    /**
     * 检查Origin是否被允许
     */
    isOriginAllowed(origin) {
        if (!origin) return false;

        return this.allowedOrigins.some(allowed => {
            // 支持通配符匹配
            if (allowed.startsWith('*.')) {
                const domain = allowed.slice(2);
                return origin === `https://${domain}` ||
                       origin.endsWith(`.${domain}`);
            }
            return origin === allowed;
        });
    }

    /**
     * Express中间件
     */
    express() {
        return (req, res, next) => {
            const origin = req.headers.origin;

            if (!this.isOriginAllowed(origin)) {
                if (req.method === 'OPTIONS') {
                    return res.sendStatus(403);
                }
                return next();
            }

            // 设置CORS头
            res.setHeader('Access-Control-Allow-Origin', origin);
            res.setHeader('Access-Control-Allow-Methods',
                this.allowedMethods.join(','));
            res.setHeader('Access-Control-Allow-Headers',
                this.allowedHeaders.join(','));

            if (this.allowCredentials) {
                res.setHeader('Access-Control-Allow-Credentials', 'true');
            }

            if (this.exposedHeaders.length > 0) {
                res.setHeader('Access-Control-Expose-Headers',
                    this.exposedHeaders.join(','));
            }

            if (this.maxAge > 0) {
                res.setHeader('Access-Control-Max-Age',
                    this.maxAge.toString());
            }

            // 处理预检请求
            if (req.method === 'OPTIONS') {
                return res.sendStatus(204);
            }

            next();
        };
    }
}

// 使用示例
const cors = new CorsMiddleware({
    allowedOrigins: [
        'https://example.com',
        '*.example.com',    // 支持通配符
        'http://localhost:3000'  // 开发环境
    ],
    allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
    allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-Id'],
    exposedHeaders: ['X-Total-Count', 'X-Request-Id'],
    allowCredentials: true,
    maxAge: 86400
});

app.use(cors.express());

常见问题与解决方案

问题1:本地开发跨域问题

代码示例

// 问题:前端localhost:3000访问后端localhost:8080被CORS拦截

// 解决方案1:开发服务器代理(推荐)
// vite.config.js
export default {
    server: {
        proxy: {
            '/api': {
                target: 'http://localhost:8080',
                changeOrigin: true
            }
        }
    }
};

// 解决方案2:后端开发环境允许所有源
if (process.env.NODE_ENV === 'development') {
    app.use((req, res, next) => {
        res.header('Access-Control-Allow-Origin', '*');
        res.header('Access-Control-Allow-Methods', '*');
        res.header('Access-Control-Allow-Headers', '*');
        if (req.method === 'OPTIONS') return res.sendStatus(204);
        next();
    });
}

// 解决方案3:浏览器禁用CORS(仅开发用)
// Chrome启动参数:
// --disable-web-security --user-data-dir=/tmp/chrome_dev

问题2:第三方API无CORS头

代码示例

// 问题:调用第三方API,但对方未设置CORS头

// 解决方案1:自建代理服务器
app.use('/proxy', createProxyMiddleware({
    target: 'https://third-party-api.com',
    changeOrigin: true,
    pathRewrite: { '^/proxy': '' }
}));

// 解决方案2:使用公共CORS代理(仅开发)
const proxyUrl = 'https://api.allorigins.win/raw?url=';
fetch(proxyUrl + encodeURIComponent(targetUrl));

// 解决方案3:JSONP(如果API支持)
function jsonp(url, callback) {
    const script = document.createElement('script');
    script.src = `${url}?callback=${callback}`;
    document.head.appendChild(script);
}

问题3:CORS与认证Token

代码示例

// 问题:使用Bearer Token认证时CORS配置

// 前端:在请求头中添加Authorization
fetch('https://api.example.com/data', {
    headers: {
        'Authorization': `Bearer ${token}`  // 触发预检请求
    }
});

// 后端:必须允许Authorization头
res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization');

// 注意:Bearer Token不受SameSite Cookie限制
// 如果可能,优先使用Token认证而非Cookie认证

总结

跨域请求与CORS是前端与后端通信中必须掌握的核心知识,本教程涵盖了以下内容:

  1. 同源策略:理解浏览器同源策略的定义、限制范围和存在意义,这是理解所有跨域问题的基础。

  2. CORS机制:掌握CORS的工作流程,包括简单请求和预检请求的区别、触发条件、相关HTTP头部的含义和配置。

  3. 跨域解决方案:全面了解CORS、JSONP、代理服务器、postMessage、Nginx反向代理等方案,根据场景选择合适的方案。

  4. Cookie与凭证:理解跨域请求携带Cookie的三要素(credentials、Allow-Credentials、SameSite),以及常见的配置错误。

  5. 开发环境配置:掌握Vite、Webpack、Node.js、Next.js等工具和框架的代理配置方法,解决本地开发跨域问题。

  6. 安全与调试:学习CORS的安全配置原则、常见陷阱和调试方法,确保跨域配置既安全又正确。

  7. 最佳实践:生产环境使用CORS或Nginx代理,开发环境使用devServer代理,避免使用通配符*,验证Origin白名单。

常见问题

什么是16. 跨域请求与CORS?

16. 跨域请求与CORS是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。

如何学习16. 跨域请求与CORS的实际应用?

教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。

16. 跨域请求与CORS有哪些注意事项?

常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。

16. 跨域请求与CORS适合初学者吗?

本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。

16. 跨域请求与CORS的核心要点是什么?

核心要点包括教程简介等内容,建议按教程顺序逐步学习。

标签: Promise HTTP头部 全双工通信 OAuth 响应码 网络安全 键盘输入 数据展示

本文由小确幸生活整理发布,转载请注明出处

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 后端通信:15. 文件上传与下载 - 从入门到实践详解 下一篇: 后端通信:17. 请求认证与授权 - 从入门到实践详解

poll相关推荐