为什么禁止我请求别的网站的接口?——跨域与CORS
<h1 data-id="heading-0">🧑💻 写在开头</h1><p>点赞 + 收藏 === 学会🤣🤣🤣</p>
<p> </p>
<div>
<div>
<blockquote>
<p>你有没有遇到过这种情况:在自己的网页上想请求别人的API,结果浏览器直接报错:<code>Access-Control-Allow-Origin' header is missing</code>。为什么浏览器要阻止你?服务器不响应不就完了吗?</p>
</blockquote>
<p>今天,用<strong>小区门禁</strong>的故事,来讲讲 <code>跨域</code> 与 <code>CORS</code>。</p>
</div>
<div>
<div>
<h2 data-id="heading-1">什么是"跨域"?</h2>
<h3 data-id="heading-2">同源策略 — 浏览器的安全基石</h3>
<p>浏览器有个<strong>同源策略</strong>(<code>Same-Origin Policy</code>):只有来自同一个"家"的资源才能随便用。</p>
<p>什么叫"同一个家"?看三个条件:<strong>协议</strong>(http/https)、<strong>域名</strong>(example.com)、<strong>端口</strong>(:8080)。三个都一样,才是同源;有一个不一样,就是跨域。</p>
<h3 data-id="heading-3">跨域的例子</h3>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">✅ http://example.com 和 http://example.com/profile // 协议+域名+端口都相同 → 同源
✅ https://example.com 和 https://example.com // 协议+域名+端口都相同 → 同源
❌ http://example.com 和 https://example.com // 协议不同 → 跨域
❌ http://example.com 和 http://api.example.com // 域名不同(子域名)→ 跨域
❌ http://example.com:8080 和 http://example.com:3000// 端口不同 → 跨域</pre>
</div>
<div>
<div>
<h3 data-id="heading-4">跨域限制了什么?</h3>
<p>浏览器的同源策略主要限制了三件事:</p>
<ul>
<li><strong>DOM 访问</strong>:无法读取不同源的 iframe 内容、无法修改不同源的 iframe DOM</li>
<li><strong>AJAX 请求</strong>:无法请求不同源的 API</li>
<li><strong>Cookie/LocalStorage</strong>:无法访问不同源的数据</li>
</ul>
<hr>
<h2 data-id="heading-5">为什么要限制跨域?</h2>
<h3 data-id="heading-6">模拟一个攻击场景</h3>
<p>想象一下:你登录了银行网站 <code>bank.com</code>,浏览器保存了你的登录 Cookie。</p>
<p>然后你手滑点进了一个恶意网站 <code>evil.com</code>,这个网站里有一段代码:</p>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;"><form action="http://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="hacker">
<input type="hidden" name="amount" value="1000000">
</form>
<script>document.forms.submit();</script></pre>
</div>
<div>
<div>
<p>如果没有同源策略,这个表单请求会自动带上 <code>bank.com</code> 的 Cookie,银行服务器以为是你本人操作的——钱就没了。</p>
<p><strong>同源策略就是浏览器的"门禁"</strong>:只有同一家人才能进,陌生人要查证件。</p>
<blockquote>
<p>💡 注意:<code><img></code> 标签的 GET 请求虽然也会带 Cookie,但现代浏览器有 <code>SameSite</code> Cookie 保护。上面表单 POST 场景更典型。</p>
</blockquote>
<hr>
<h2 data-id="heading-7">CORS — 跨域的"通行证"</h2>
<h3 data-id="heading-8">CORS 是什么?</h3>
<p><strong>CORS</strong>(Cross-Origin Resource Sharing)= 跨域资源共享。</p>
<p>它的工作原理很简单:<strong>让服务器告诉浏览器,"我允许来自这些源的请求"</strong>。</p>
<h3 data-id="heading-9">简单请求 vs 预检请求</h3>
<h4 data-id="heading-10">简单请求</h4>
<p>满足以下条件的请求是"简单请求":</p>
</div>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260409121249926-1936102589.png" alt="ScreenShot_2026-04-09_115501_750" loading="lazy"></p>
<p> 简单请求的流程:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">1. 浏览器发送请求(自动带上 Origin 头)
↓
2. 服务器检查 Origin,决定是否允许
↓
3. 服务器返回响应头 Access-Control-Allow-Origin
↓
4. 浏览器检查响应头,允许就完事</pre>
</div>
<p>服务器端示例(Node.js):</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">app.get('/api/data', (req, res) => {
const origin = req.headers.origin;
if (origin === 'https://example.com') {
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.json({ data: '这是返回的数据' });
});</pre>
</div>
<p>响应头:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Content-Type: application/json
{"data": "这是返回的数据"}</pre>
</div>
<h4 data-id="heading-11">预检请求(Preflight)</h4>
<p>不满足"简单请求"条件的,浏览器会先发一个 OPTIONS 请求"探路":</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">1. 浏览器发送 OPTIONS 预检请求
↓
2. 服务器检查方法/头部/Origin
↓
3. 服务器返回允许的头 Access-Control-*
↓
4. 浏览器发送实际请求</pre>
</div>
<div>
<div>
<p><strong>预检请求检查什么?</strong></p>
<p>预检请求(OPTIONS)就像登机前的安检——先检查你带没带危险品。</p>
<p>浏览器会问服务器三件事:</p>
<ul>
<li><strong>我从哪来?</strong>(Origin)</li>
<li><strong>我想用什么方法?</strong>(Access-Control-Request-Method)</li>
<li><strong>我想带什么头?</strong>(Access-Control-Request-Headers)</li>
</ul>
<p>服务器回答"可以",浏览器才放行实际请求。</p>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;"># 请求(浏览器发给服务器)
OPTIONS /api/data HTTP/1.1
Origin: https://example.com # 我从哪来
Access-Control-Request-Method: PUT # 我想用 PUT 方法
Access-Control-Request-Headers: Content-Type, Authorization# 我想带这些头
---
# 响应(服务器告诉浏览器)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com# 允许这个源
Access-Control-Allow-Methods: GET, POST, PUT, DELETE# 允许这些方法
Access-Control-Allow-Headers: Content-Type, Authorization# 允许这些头
Access-Control-Max-Age: 86400 # 预检结果缓存24小时</pre>
</div>
<p>服务器端处理:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">app.options('/api/data', (req, res) => {
const origin = req.headers.origin;
if (origin === 'https://example.com') {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
}
res.status(204).send();
});</pre>
</div>
<h2 data-id="heading-12">CORS 响应头详解</h2>
<h3 data-id="heading-13">常用响应头</h3>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260409121429921-268955804.png" alt="ScreenShot_2026-04-09_115526_181" loading="lazy"></p>
<h3 data-id="heading-14">credentials 模式</h3>
<p>默认情况下,<code>CORS</code> 不带 <code>Cookie</code>。如果需要携带 Cookie:</p>
<p>前端:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">fetch('/api/data', {
credentials: 'include'
});</pre>
</div>
<p>服务端:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">res.setHeader('Access-Control-Allow-Origin', 'https://example.com');
res.setHeader('Access-Control-Allow-Credentials', 'true');</pre>
</div>
<p>注意:<code>Access-Control-Allow-Origin</code> 不能用 <code>*</code>,必须是具体域名。</p>
<hr>
<h2 data-id="heading-15">跨域的解决方案</h2>
<h3 data-id="heading-16">1. JSONP(已不推荐)</h3>
<p>利用 <code><script></code> 标签不受同源策略限制的特性:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;"><script>
function handleData(data) {
console.log(data);
}
</script>
<script src="http://api.example.com/data?callback=handleData"></script></pre>
</div>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260409121513533-1204655060.png" alt="ScreenShot_2026-04-09_115533_589" loading="lazy"></p>
<h3 data-id="heading-17">2. 代理服务器</h3>
<p>在自己的服务器上转发请求,"伪装"成同源:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">浏览器 ──> 我的服务器(同一源) ──> 目标服务器</pre>
</div>
<p>Nginx 代理:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">location /api/ {
proxy_pass http://target-server.com/;
}</pre>
</div>
<p>Node.js 代理:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">app.get('/api/data', async (req, res) => {
const response = await fetch('http://target-server.com/data');
const data = await response.json();
res.json(data);
});</pre>
</div>
<h3 data-id="heading-18">3. Webpack/Vite 开发代理</h3>
<p>开发环境配置代理:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://target-server.com',
changeOrigin: true
}
}
}
};</pre>
</div>
<h3 data-id="heading-19">4. postMessage</h3>
<p>不同窗口/iframe 之间的通信:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">window.addEventListener('message', (event) => {
if (event.origin === 'https://example.com') {
console.log('收到消息:', event.data);
}
});
iframe.contentWindow.postMessage('hello', 'https://example.com');</pre>
</div>
<h2 data-id="heading-20">深入了解 CORS 🔬</h2>
<h3 data-id="heading-21">第三方 Cookie 的限制</h3>
<p>现代浏览器正在逐步限制第三方 Cookie:</p>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260409121637698-393940376.png" alt="ScreenShot_2026-04-09_115540_201" loading="lazy"></p>
<h3 data-id="heading-22">CORS 和 CSRF 的区别</h3>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260409121650645-1229573127.png" alt="ScreenShot_2026-04-09_115545_437" loading="lazy"></p>
<h3 data-id="heading-23">为什么 OPTIONS 叫"预检"?</h3>
<p>"预检"就像登机前的安检——先检查你带没带危险品(方法、头部),没问题了才让你登机(发送实际请求)。</p>
<hr>
<h2 data-id="heading-24">常见错误排查</h2>
<h3 data-id="heading-25">错误 1:No 'Access-Control-Allow-Origin' header</h3>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260409121703374-1380994.png" alt="ScreenShot_2026-04-09_115552_373" loading="lazy"></p>
<h3 data-id="heading-26">错误 2:Method not allowed</h3>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260409121713156-1191446275.png" alt="ScreenShot_2026-04-09_115557_961" loading="lazy"></p>
<h3 data-id="heading-27">错误 3:Header not allowed</h3>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260409121722766-550595350.png" alt="ScreenShot_2026-04-09_115605_285" loading="lazy"></p>
<h3 data-id="heading-28">错误 4:预检请求 404</h3>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260409121734844-1700572815.png" alt="ScreenShot_2026-04-09_115610_888" loading="lazy"></p>
<h2 data-id="heading-29">总结</h2>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260409121743746-274242585.png" alt="ScreenShot_2026-04-09_115616_685" loading="lazy"></p>
<div>
<div>
<h2 data-id="heading-30">写在最后</h2>
<p>现在你应该明白了:</p>
<ul>
<li><strong>跨域是浏览器的安全机制</strong>,不是为了刁难你</li>
<li><strong>CORS 是服务器授权机制</strong>,服务器说可以,浏览器才放行</li>
<li><strong>预检请求</strong> = 安检,OPTIONS 通过了才能发送实际请求</li>
<li><strong>生产环境推荐用代理</strong>,开发环境用 webpack/vite 代理</li>
</ul>
<p>下次遇到跨域错误,先看浏览器控制台的报错信息——是"缺通行证"(header 缺失)还是"通行证不对"(origin 不匹配),处理方式不一样的。</p>
</div>
</div>
<div>
<h3 id="tid-D8HBxE">如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。</h3>
</div>
<p><em><img src="https://img2024.cnblogs.com/blog/2149129/202501/2149129-20250122165814748-630765389.png" alt="" loading="lazy"></em></p>
</div>
</div>
</div>
</div>
</div><br><br>
来源:https://www.cnblogs.com/smileZAZ/p/19840032
頁:
[1]