🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
你有没有遇到过这种环境:在本身的网页上想哀求别人的API,效果欣赏器直接报错:Access-Control-Allow-Origin' header is missing。为什么欣赏器要克制你?服务器不相应不就完了吗?
本日,用小区门禁的故事,来讲讲 跨域 与 CORS。
什么是"跨域"?
同源战略 — 欣赏器的安全基石
欣赏器有个同源战略(Same-Origin Policy):只有来自同一个"家"的资源才华任意用。
什么叫"同一个家"?看三个条件:协议(http/https)、域名(example.com)、端口(:8080)。三个都一样,才是同源;有一个不一样,就是跨域。
跨域的例子
- ✅ http://example.com 和 http://example.com/profile<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form> // 协议+域名+端口都相同 → 同源
- ✅ https://example.com 和 https://example.com<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form><form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form> // 协议+域名+端口都相同 → 同源
- ❌ http://example.com 和 https://example.com<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form><form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form> // 协议不同 → 跨域
- ❌ http://example.com 和 http://api.example.com<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form><form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>// 域名不同(子域名)→ 跨域
- ❌ http://example.com:8080 和 http://example.com:3000 // 端口不同 → 跨域
复制代码 跨域限定了什么?
欣赏器的同源战略告急限定了三件事:
- DOM 访问:无法读取差别源的 iframe 内容、无法修改差别源的 iframe DOM
- AJAX 哀求:无法哀求差别源的 API
- Cookie/LocalStorage:无法访问差别源的数据
为什么要限定跨域?
模拟一个攻击场景
想象一下:你登录了银行网站 bank.com,欣赏器生存了你的登录 Cookie。
然后你手滑点进了一个恶意网站 evil.com,这个网站里有一段代码:- <form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>
复制代码 如果没有同源战略,这个表单哀求会自动带上 bank.com 的 Cookie,银行服务器以为是你本人操纵的——钱就没了。
同源战略就是欣赏器的"门禁":只有同一家人才华进,陌生人要查证件。
💡 注意:<img> 标签的 GET 哀求固然也会带 Cookie,但当代欣赏器有 SameSite Cookie 掩护。上面表单 POST 场景更范例。
CORS — 跨域的"通行证"
CORS 是什么?
CORS(Cross-Origin Resource Sharing)= 跨域资源共享。
它的工作原理很简朴:让服务器告诉欣赏器,"我允许来自这些源的哀求"。
简朴哀求 vs 预检哀求
简朴哀求
满足以下条件的哀求是"简朴哀求":
简朴哀求的流程:- 1. 浏览器发送请求(自动带上 Origin 头)
- ↓
- 2. 服务器检查 Origin,决定是否允许
- ↓
- 3. 服务器返回响应头 Access-Control-Allow-Origin
- ↓
- 4. 浏览器检查响应头,允许就完事
复制代码 服务器端示例(Node.js):- app.get('/api/data', (req, res) => { const origin = req.headers.origin; if (origin === 'https://example.com') {<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>res.setHeader('Access-Control-Allow-Origin', origin); } res.json({ data: '这是返回的数据' });});
复制代码 相应头:- HTTP/1.1 200 OK
- Access-Control-Allow-Origin: https://example.com
- Content-Type: application/json
- {"data": "这是返回的数据"}
复制代码 预检哀求(Preflight)
不满足"简朴哀求"条件的,欣赏器会先发一个 OPTIONS 哀求"探路":- 1. 浏览器发送 OPTIONS 预检请求
- ↓
- 2. 服务器检查方法/头部/Origin
- ↓
- 3. 服务器返回允许的头 Access-Control-*
- ↓
- 4. 浏览器发送实际请求
复制代码 预检哀求查抄什么?
预检哀求(OPTIONS)就像登机前的安检——先查抄你带没带伤害品。
欣赏器会问服务器三件事:
- 我从哪来?(Origin)
- 我想用什么方法?(Access-Control-Request-Method)
- 我想带什么头?(Access-Control-Request-Headers)
服务器答复"可以",欣赏器才放行实际哀求。- # 哀求(欣赏器发给服务器)OPTIONS /api/data HTTP/1.1Origin: https://example.com<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form><form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form><form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form> # 我从哪来Access-Control-Request-Method: PUT<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form><form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form># 我想用 PUT 方法Access-Control-Request-Headers: Content-Type, Authorization # 我想带这些头---# 相应(服务器告诉欣赏器)HTTP/1.1 204 No ContentAccess-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<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form><form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form> # 预检效果缓存24小时
复制代码 服务器端处理处罚:- app.options('/api/data', (req, res) => { const origin = req.headers.origin; if (origin === 'https://example.com') {<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>res.setHeader('Access-Control-Allow-Origin', origin);<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>res.setHeader('Access-Control-Max-Age', '86400'); } res.status(204).send();});
复制代码 CORS 相应头详解
常用相应头
credentials 模式
默认环境下,CORS 不带 Cookie。如果须要携带 Cookie:
前端:- fetch('/api/data', {
- credentials: 'include'
- });
复制代码 服务端:- res.setHeader('Access-Control-Allow-Origin', 'https://example.com');
- res.setHeader('Access-Control-Allow-Credentials', 'true');
复制代码 注意:Access-Control-Allow-Origin 不能用 *,必须是详细域名。
跨域的办理方案
1. JSONP(已不保举)
使用 [/code]
2. 署理服务器
在本身的服务器上转发哀求,"伪装"成同源:- 浏览器 ──> 我的服务器(同一源) ──> 目标服务器
复制代码 Nginx 署理:- location /api/ {
- proxy_pass http://target-server.com/;
- }
复制代码 Node.js 署理:- app.get('/api/data', async (req, res) => {
- const response = await fetch('http://target-server.com/data');
- const data = await response.json();
- res.json(data);
- });
复制代码 3. Webpack/Vite 开辟署理
开辟环境设置署理:- // vite.config.jsexport default { server: {<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>proxy: {<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form> '/api': {<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form><form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>target: 'http://target-server.com',<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form><form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>changeOrigin: true<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form> }<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>} }};
复制代码 4. postMessage
差别学口/iframe 之间的通讯:- window.addEventListener('message', (event) => { if (event.origin === 'https://example.com') {<form action="http://bank.com/transfer" method="POST">
- <input type="hidden" name="to" value="hacker">
- <input type="hidden" name="amount" value="1000000">
- </form>console.log('收到消息:', event.data); }});iframe.contentWindow.postMessage('hello', 'https://example.com');
复制代码 深入相识 CORS 🔬
第三方 Cookie 的限定
当代欣赏器正在徐徐限定第三方 Cookie:
CORS 和 CSRF 的区别
为什么 OPTIONS 叫"预检"?
"预检"就像登机前的安检——先查抄你带没带伤害品(方法、头部),没标题了才让你登机(发送实际哀求)。
常见错误排查
错误 1:No 'Access-Control-Allow-Origin' header
错误 2:Method not allowed
错误 3:Header not allowed
错误 4:预检哀求 404
总结
写在末了
如今你应该明确了:
- 跨域是欣赏器的安全机制,不是为了刁难你
- CORS 是服务器授权机制,服务器说可以,欣赏器才放行
- 预检哀求 = 安检,OPTIONS 通过了才华发送实际哀求
- 生产环境保举用署理,开辟环境用 webpack/vite 署理
下次遇到跨域错误,先看欣赏器控制台的报错信息——是"缺通行证"(header 缺失)照旧"通行证不对"(origin 不匹配),处理处罚方式不一样的。
如果对您有所资助,欢迎您点个关注,我会定时更新技能文档,各人一起讨论学习,一起进步。
免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金. |