原文链接:https://medium.com/@baphemot/understanding-cors-18ad6b478e2b
如果你在前端使用过AJAX,你应该对下面出现在浏览器控制台里的错误很熟悉。如果你没见过,那只能说明你还年轻。
如果你看到这个错误信息,这表示这次返回数据失败了,但是你仍然可以打开浏览器的Network栏,看到返回的数据——这到底是怎么回事?
Cross-Origin Resource Sharing (CORS)
上面的错误是因为浏览器的CORS机制导致的。COSR(跨站点资源分享)通俗的讲是跨域问题,严格来说它是跨域问题的解决方案之一,而且是官方解决方案。
在CORS成为标准之前,是没有办法请求不同域名的后端API的,因为安全原因。请求会被同源策略阻止,现在也是。
CORS是一种可以让你实现跨站点请求并同时阻止恶意js的请求,它会在你发送下面几种HTTP请求时触发:
- 不同的域名 (比如在网站 example.com 请求 api.com)
- 不同的子域名 (比如在网站 example.com 请求 api.example.com)
- 不同的端口 (比如在网站 example.com 请求 example.com:3001)
- 不同协议 (比如在网站 https://example.com 请求 http://example.com)
这个机制阻止攻击者在一些网站上放置js脚本(比如通过Googls Ads展示的广告)发起一个AJAX请求访问www.yourbank.com,假设你刚好登陆过这个网站,就可能使用你的验证信息发起一笔转账。
如果你的浏览器发起一个“非简单”请求(比如这个请求里包含了cookies,或者Content-type不包含application/x-ww-form-urlencoded, multipart/form-data 或者 text-plain)一个叫做预检查的机制会发送一个OPTIONS请求到服务器。如果服务器没有返回带有特殊头部的数据,简单请求GET或者POST请求仍然会发送,服务器的数据也会返回,但是浏览器会阻止Javascript获取这次请求。
如果明确的需要在一个请求里添加cookies,自定义头部信息或则其他特性,这将不在是一个简单请求,并且服务器没有适当的返回,这次请求讲不会发送。就是复杂请求时,如果OPTIONS的请求,服务器没有做出适当的返回,后面真实的请求将不会发送。
Access-Control-Allow-What?
CORS使用一些HTTP头信息——包括请求和返回——为了让工作继续开展下去,你必须了解一下的一些头信息:
Access-Control-Allow-Origin
这个头部信息由服务器返回,用来明确指定那些客户端的域名允许访问这个资源。它的值可以是:
- * —— 允许任意域名
- 一个完整的域名名字(比如:https://example.com)
如果你需要客户端传递验证信息到头部(比如:cookies)。这个值不能为 * —— 必须为完整的域名(这点很重要)。
Access-Control-Allow-Credentials
这个头部信息只会在服务器支持通过cookies传递验证信息的返回数据里。它的值只有一个就是 true。跨站点带验证信息时,服务器必须要争取设置这个值,服务器才能获取到用户的cookie。
Access-Control-Allow-Headers
提供一个逗号分隔的列表表示服务器支持的请求数据类型。假如你使用自定义头部(比如:x-authentication-token 服务器需要在返回OPTIONS请求时,要把这个值放到这个头部里,否则请求会被阻止)。
Access-Control-Expose-Headers
相似的,这个返回信息里包含了一组头部信息,这些信息表示那些客户端可以使用。其他没有在里面的头部信息将会被限制(译者注:这个头信息实战中使用较少)。
Access-Control-Allow-Methods
一个逗号分隔的列表,表明服务器支持的请求类型(比如:GET, POST)
Origin
这个头部信息,属于请求数据的一部分。这个值表明这个请求是从浏览器打开的哪个域名下发出的。出于安全原因,浏览器不允许你修改这个值。
如何修复CORS“错误”?
你应该明白了CORS的行为并不是一个错误——它是一个机制,用来保护你的用户,你和你请求的服务器。
有时,缺乏适当的头部信息是因为客户端实现错误(比如:丢失验证信息比如API key)。
下面有几个适应不同情况,“修复这个错误”的方法:
A——我在开发前端并且可以控制或者认识开发后端的人员
这是一个最好的情况——你应该能让返回信息的头里包含适当的CORS字段。如果你请求的API使用node的experss,你可以使用cors包。如果你想让你的网站更加的安全,你应该使用白名单来返回Access-Control-Allow-Origin头。
B——我在开发前端,但是我不能控制后端,我需要一个临时方案
这是第二个最好的情况,特别是在有时间限制的情况里。临时的解决这个问题可以让你的浏览器忽略CORS机制——比如安装ACAOChrmoe插件或者启动Chrome时输入下面的指令:
chrome --disable-web-security --user-data-dir
重要:需要记住的是,这个方法会关闭整个浏览器的CORS机制,包含你浏览器正在访问的网站,要小心使用,非常不安全。(译者注:这个方法没有用过,个人觉得风险太大,临时测试也慎用,怕你开了插件忘记关掉)
其他方法可以使用devServer.proxy(假设你使用webpack来启动你的app)或者使用CORS-as-a-service解决方案,比如https://cors-anywhere.herokuapp.com/
C——我在开发前端,但是我控制不了后端,而且将来也控制不了
好的。事情越来越复杂了。首先,你应该思考,为什么服务器没有返回适当的头部。
也许你请求的API不许允第三方应用请求它们?或者这些API仅仅给APP使用,而不是浏览器?或者你应该发送一个验证token在你请求的URL里?
假如你坚持要通过浏览器获得它们的数据,你应该自己写一个代理,在浏览器和你要请求的API之间。就像我们在方法B里做的那样。
这个代理没有运行在和你应用相同的域名下,但是这个代理为你的请求提供正确的CORS响应。这个代理去请求API时就不需要CORS支持,因为这个代理不是用浏览器去访问的API,而是通过程序直接发起的请求。
你可以根据你的平台实现这个代理,或者使用已经做好的解决方案,比如:https://www.npmjs.com/package/cors-anywhere
请记住,如果你想支持用户验证,这些方法会引入安全风险。
译者注:实战中,能控制服务器的情况下。最好是服务器上正确配置CORS,可以在服务器API层进行配置,也可以在nginx或者apache层进行配置(这样后端新加服务器不用再配置)。最好配置上白名单。真实项目中,CORS问题主要出现在开发阶段,本地启动的前端开发服务器域名是localhost。调试接口可能是一个其他域名,这个时候的解决方法。AC都可以,不过C方法会导致每个前端项目都需要自己的开发服务器支持一个proxy。个人偏向去测试环境的nginx服务层配置跨域,这样,开发环境统一支持前端本地开发跨域调试接口。
更多关于CORS
如果你希望学习更多关于CORS的细节,请访问MDN article。
推荐一个模拟接口的应用,可以在你本地开发前端,但是后端你不能直接访问或者还没写好时,模拟调试你的接口https://www.npmjs.com/package/back-mock