"OK, but no"
只要用过 AJAX, 你应该就很熟悉浏览器控制台中出现的如下报错:
当你看到这个信息, 就意味着响应失败了; 但你依然能在浏览器开发工具的网络 tab 里看到返回数据 -- 这是什么情况呢?
跨域资源共享(CORS: Cross-Origin Resource Sharing)
你所观察到的这种行为是浏览器 CORS 实现机制的效果在 CORS 成为标准之前, 由于安全原因, 没有办法跨域调用 API 也就是 (一定程度上依旧是) 被所谓同源策略 (Same-Origin Policy) 限制住了
CORS 机制是为了在认可用户发起的请求的同时, 阻止那些恶意 JS; 并在以下情况发起的 HTTP 请求时被触发:
一个不同的域(比如从 example.com 的站点调用 api.com)
一个不同的子域(比如从 example.com 的站点调用 api.example.com)
一个不同的端口(比如从 example.com 的站点调用 example.com:3001)
一个不同的协议(比如从 https://example.com 的站点调用 http://example.com)
这种机制阻止了当你已经登录 www.yourbank.com 的情况下, 攻击者在各种网站上植入的脚本 (比如通过 Google Ads 显示的广告) 向 www.yourbank.com 发起的携带 你的身份凭证 的 AJAX 请求
对于简单的 GET 或 POST 请求, 如果服务器没有对其作出携带特殊 HTTP 头部的响应 -- 请求依然被发送并且数据也照样被返回, 但浏览器将不允许 Javascript 访问该响应
如果浏览器尝试着去弄一个没那么简单的请求(比如一个包含了 cookie 的请求, 或 Content-type 不是
- application/x-www-form-urlencoded
- multipart/form-data
text-plain 三者之一的), 则被称为预检 (preflight) 的机制将被用到, 并且一个 OPTIONS 请求会被发往服务器
关于没那么简单的请求, 一个常见的例子是在请求中加入 cookie 或自定义头部 -- 如果浏览器发送了这样的请求且服务器没有正确响应的话, 则只有预检调用会发送(不包含额外的头部), 而浏览器本应使用的真实的 HTTP 请求就不会被发送了
那些
Access-Control-Allow-
什么什么的...
在 CORS 请求和响应中, 都用到了一些 HTTP 头部, 其中以下这几个是你必须理解的:
Origin
该头部是客户端发起的请求的一部分, 包含了应用所在的域由于安全原因, 浏览器不会允许用户重写这个值
Access-Control-Allow-Credentials
该头部只需要在服务器支持通过 cookie 认证的情况下出现在响应中这种情况下, 其唯一合法值就是 true
Access-Control-Allow-Methods
一个逗号分隔的表示服务器将会支持的 HTTP 请求动词 (如 GET, POST) 列表
Access-Control-Allow-Headers
格式为一个逗号分隔的列表, 表示服务器将会支持的请求头部值如果使用了自定义头部(比如 x-authentication-token), 则应该将其置于这个 ACA 头部(译注: 即
Access-Control-Allow-Headers
)响应中, 并返回到 OPTIONS 调用中; 除非该请求被阻塞了
- HTTP/1.1 200 OK
- Date: Mon, 01 Dec 2008 01:15:39 GMT
- Server: Apache/2.0.61 (Unix)
- Access-Control-Allow-Origin: http://foo.example
- Access-Control-Allow-Methods: POST, GET, OPTIONS
- Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
- Access-Control-Max-Age: 86400
- Vary: Accept-Encoding, Origin
- Content-Encoding: gzip
- Content-Length: 0
- Keep-Alive: timeout=2, max=100
- Connection: Keep-Alive
- Content-Type: text/plain
- Access-Control-Expose-Headers
相似的是, 该响应应包含一个头部列表, 表示将在实际的响应中出现的值, 并应在客户端中有效所有其他头部则会被限制
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
如何搞定 CORS 错误 ?
首先要清楚的是, CORS 行为并非一种错误 -- 这种机制致力于保护你的用户你本身, 或你调用的站点
有时, 缺少合适的头部, 会导致客户端的错误执行(如丢失了 API key 等认证信息)
取决于你面临的场景, 以下手段可以搞定这种错误:
A -- 我开发前端, 也能控制后端, 或者认识那个开发后端的哥们
这是最好的情况了 -- 你能根据调用, 在服务器上实现合适的 CORS 响应如果 API 用 node 的 express 实现, 那么简单的使用 cors 包 (译注: https://github.com/expressjs/cors) 就可以了如果要保证站点的适度安全, 可以考虑为
Access-Control-Allow-Origin
设置一个白名单
B -- 我开发前端, 且暂时控制不了后端, 我需要一个临时的办法
这是次优的情况, 因为其实这就是手段 A, 只是暂时性的受限为了临时解决, 可以让浏览器忽略 CORS 机制 -- 比如使用 ACAO Chrome 扩展(译注: 或指
Allow-Control-Allow-Origin: *
扩展) 或用如下参数在启动 Chrome 时完全禁止 CORS:
chrome --disable-web-security --user-data-dir
切记, 这将禁止浏览器会话期间 所有 网站的 CORS 机制; 要小心慎用
另外的替代方法是使用 devServer.proxy(假设你用到了 webpack 做开发); 或使用一个 CORS-as-a-service 解决方案, 比如 https://cors-anywhere.herokuapp.com/
C -- 我开发前端, 并总是控制不了后端的
Ok, 现在事儿大了首先要搞清为什么服务器没有发送适当的头部
也许是不允许第三方应用访问其 API ? 又或者其 API 只服务于服务器端而非浏览器? 要么就是你需要在 URL 中发送认证令牌?
如果你依然认为可以通过浏览器访问数据, 就得在浏览器应用和 API 之间编写自己的代理了, 就类似于我们在手段 B 中做的那样
在中间加一个代理
该代理不必和你的应用运行在同样的域下, 只要当代理本身和客户端通讯时正确支持 CORS 就行代理和 API 之间的通讯就完全不必支持 CORS 了
你既可以编写自己的平台, 也可以使用诸如 https://www.npmjs.com/package/cors-anywhere 的成熟方案
要记住如果你需要支持身份凭证, 这样的办法会引入一个安全风险
关于 CORS 的更多
如果希望学习更多关于 CORS 的细节, 推荐阅览这篇 MDN 文章 (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
- (end)
- ----------------------------------------
长按二维码或搜索 fewelife 关注我们哦
来源: https://juejin.im/post/5a97b5a3f265da23766ab19a