原文地址:https://segmentfault.com/a/1190000018756960

前言

CORS 与 cookie 在前端是个非常重要的问题,不过在大多数情况下,因为前后端的 domain 一般是相同的,所以很少去关心这些问题。或者只是要求后端设置 Access-Control-Allow-Origin: * 就行了,很少去了解背后运作的机制。

针对这个问题,MDN 上有非常详细的解释,所以这篇文章主要在于整理重点和实际操作时经常出现的问题。

同源策略(same-origin policy)

为了防止 javascript 在网页上随意撒野,同源策略规定了某些特定的资源,代码必须在同源的情况下才可以存取。

什么是同源呢?一份 document 的来源,由 protocol、host 和 port 来定义。也就是说如果文件1来自http://kalan.com,而文件2来自于 https://kalan.com 他们就不算是同源。那如果是子域名呢?像是 https://api.foobar.com 和 https://app.foobar.com。因为他们的 host 不同,所以也不算同一个源。

而有些资源是本来就能够通过跨来源取得的:

  • <img />
  • <video />, <audio />
  • <iframe />:可以通过定义 header 来防止他人嵌入
  • 通过 <link rel="stylesheet" href /> 载入的CSS脚本
  • <script src="" /> 载入的 Javascript

通过代码发出的跨源请求则会受到同源策略的限制(如Fetch,XHR)。

很显然,这样的规定太过严格了。如果都要限制在同源策略下的话,前后端开发会难以进行,也没办法用 XHR 的方式套用其他 SDK 的 API。也因此出现了 CORS( Cross-Origin Resource Sharing)的机制。

CORS(跨源资源共享)

很多人都觉得 CORS 是前端才需要具备的知识。不过 CORS 通常需要后端设定相关的 HTTP 头,并且了解背后的含义才有办法正确运作。

那么跨来源请求是怎么运作的呢?

主要是由两个 Header 来做相对的存取控制:请求当中的 Origin 和响应中的 Access-Control-Allow-Origin

只要发送请求时的 Origin 和响应头中 Access-Control-Allow-Origin 的值相同,或是 Access-Control-Allow-Origin: *(代表允许任何域存取资源),此时就会放宽 CORS 的限制,允许存取跨域资源。

如果不符合 CORS 策略的话,会显示下列信息:

如果你尝试去读取回传的物件,还会得到警告。

首先,如果我们按照提示中所说的,将 fetch mode 改成 no-cors 会发生什么事呢?的确,我们把烦人的错误信息给处理掉了,但是情况似乎并没有变好。

no-cors并不是灵丹妙药,就算用了这个模式,CORS 也不会因此就打开大门,也就是你的请求并不会成功发出。也因此出现了 SyntaxError: Unexpected end of input 这个错误。这个模式通常是跟Service Worker搭配使用的。

从上面这个实验当中可知,要解除CORS的封印只有一招,就是在服务器端加上正确的 Control-Access-Allow-Origin(host 必须跟原来相同或是*)。

另外,CORS 这个机制只会运作在 javascript 送出 XHR 或 fetch 时,一般 curl 或 postman 并没有这个机制,所以也因此常常在测试 API 端点时会忽略这件事,导致前后端在测试 API 时发生出入。

有些跨来源请求不会发生 preflight,而有些请求则会,MDN上写的清清楚楚:

  1. 必须是 GET,HEAD,POST 中的一种方法
  2. 除了 user-agent 自动设置的 header 和特定的 header 之外,不包含其他 header 。可接受的header
  3. 若有 Content-Type(注意是请求头,不是响应头),则必须是下列的值:application/x-www-form-encodedtext/plainmultipart/form-data

也就是说如果不满足以上条件的话,就会发出 preflight 请求。

我们试着把 Content-Type 改为 application/json 来测试一下(不能为 application/x-www-form-encodedtext/plainmultipart/form-data)。

Preflight

所谓的 preflight 就是请求会先用 HTTP 的 OPTION 方法去另外一个域敲门,确认没问题后才会送出真正的请求。一旦触发了这个条件,事情就会变得麻烦得多。

  1. 必须加入一个 OPTIONS 的相同 api endpoint,并且设定 Access-Control-Allow-Origin 来符合 CORS 条件
  2. 必须加入Access-Control-Allow-Headers,且必须包含所有不在条件内 header,否则无法通过。

如果没有通过 preflight check 的话,会得到错误信息如下:

Access to fetch at 'http://localhost:3001/trigger-preflight' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

或是你没有在 OPTIONS 的响应头里加上 Access-Control-Allow-Origin

Access to fetch at 'http://localhost:3001/trigger-preflight' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

如果成功的话,你会看到 network 里有两个请求,一个是 OPTIONS,另一个则是真正的请求。

上图为 OPTION,下图为GET

如果我们加上一个自制的头呢?根据MDN所定义的条件,也应该触发预检请求才对,我们加上一个X-Access-Token看看会发生什么事。

的确无法通过preflight,如果要通过的话,必须再把 X-Access-Token 加入 Access-Control-Allow-Headers 中。

附带身份验证的请求

cookie 并不能跨域传递,也就是说不同 origin 来的 cookie 没办法互相传递及存取,不然就天下大乱了。

不过如果你在 a 域送出了 b 域的请求,且 b 域回传了 cookie 的信息,那么在 a 域会以 b 域的形式储存一份cookie,如果没有设定 withCredentials 或是 credentials: ‘include’ 的话,就算服务器回传了 Set-Cookie,一样不会被写入。如下图:

服务器回传Set-Cookie

没有写入浏览器中

在一般情况下如果再使用 b 域的 API,cookie 是不会自动被送出去的。这个情况下,你必须在 XHR 设定 withCredentials 或是 fetch 的选项中设置 { credentials: 'include' },因为这也是一个跨域请求,所以也必须按照 CORS 条件加入 Access-Control-Allow-Origin

为了避免安全性的问题,浏览器还有规定 Access-Control-Allow-Origin不能为*

Access to fetch at 'http://localhost:3001/cookie' from origin 'http://localhost:3000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.

不过仅仅这样还是不够,浏览器会自动拒绝没有 Access-Control-Allow-Credentials 的响应,所以如果要将身份信息传到跨域的服务器中,必须额外加上 Access-Control-Allow-Credentials: true。如果这些都设定成功,应该会像下图这样,在 Request Cookie可以看到 cookie 被成功送出。

Request Cookies 里有个 jack!

好吧,如果你成功设定了这些东西,但还是有可能没办法把 cookie 送到服务器。那有可能会是以下几种情况:

1.用户禁用了此域的 cookie

可能使用者把你加入了黑名单,导致 cookie 无法成功送出

解决方法:

  • 改域
  • 检讨自己为什么被用户封锁
2.用户阻止了所有外部网站的cookie

在Safari 中有时会开启“阻止所有Cookie”这一选项,这在调试时会让你尝到不少苦头。

后记

要处理 CORS 是件吃力不讨好的事情,尤其是有时在跑 CI/CD之前忘记加上 Access-Control-Allow-Origin 或是 Access-Control-Allow-Credentials,那么部署可能又是一天以后的事了。这次把一些常见的问题整理起来,希望以后如果再有类似的情形可以知道怎么处理。

最后附上源代码

参考文章

[转]怎样与 CORS 和 cookie 打交道的更多相关文章

  1. 彻底掌握CORS跨源资源共享

    本文来自于公众号链接: 彻底掌握CORS跨源资源共享 ) 本文接上篇公众号文章:彻底理解浏览器同源策略SOP 一.概述 在云时代,各种SAAS应用层出不穷,各种互联网API接口越来越丰富,H5技术在微 ...

  2. 允许跨域资源共享(CORS)携带 Cookie (转载)

    如何让CORS携带Cookie CORS 是一个 W3C 标准,全称是“跨域资源共享”(Cross-origin resource sharing).默认浏览器为了安全,遵循“同源策略”,不允许 Aj ...

  3. [Go] Cookie 使用简介

    Golang 的 Cookie web 开发免不了要和 cookie 打交道.Go 的 http 库也提供了 cookie 的相关操作. type Cookie struct { Name strin ...

  4. 对cookie的重新认识

    这两天做了一个跟cookie打交道比较多的项目,把其中重新认识的点记录下来: 1.$.cookie(name, value, time),当time为0时,相当于本句没有执行,并不会将原本记录在用户浏 ...

  5. node.js下操作cookie

    cookie,又是cookie.工作中与cookie打交道很多次,不过时间跨度也大,每总结多一次,就加深了解多一点. cookie,一定是放在浏览器中的,用于浏览器保存一些小额度的内容.每次我们去访问 ...

  6. iOS UIWebView 允许所有三方cookie

    前几天项目中用到UIWebView, 而在网页中,用到了多说评论的第三方.但是当我在手机端发表评论的时候,出现禁用第三方cookie,而安卓是没有这种情况的,于是就在找原因.找了很久也没有找到原因.一 ...

  7. 怎样实现登录?| Cookie or JWT

    先问小伙伴们一个问题,登录难吗?"登录有什么难得?输入用户名和密码,后台检索出来,校验一下不就行了."凡是这样回答的小伙伴,你明显就是产品思维,登录看似简单,用户名和密码,后台校验 ...

  8. HTTP协议详细总结

    HTTP超文本传输协议,是WWW上应用的最多的协议.了解和掌握HTTP协议是对程序人员的基本要求. 转载请注明出处 http://www.cnblogs.com/zrtqsk/p/3746891.ht ...

  9. HTTP详解(1)-工作原理【转】

    转自:http://blog.csdn.net/hguisu/article/details/8680808 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] 1 HTTP简 ...

随机推荐

  1. ML-凸优化初识

    ML问题 = 模型 + 优化 类似于, 程序 = 数据结构 + 算法 模型(objective): DL, LR, SCV, Tree, XGBoost..... 优化(train): GD/SGD, ...

  2. k8s 挂载卷介绍(四)

    kubernetes关于pod挂载卷的知识 首先要知道卷是pod资源的属性,pv,pvc是单独的资源.pod 资源的volumes属性有多种type,其中就包含有挂载pvc的类型.这也帮我理清了之间的 ...

  3. 自动化渗透测试工具(Cobalt Strike)3.1 最新破解版

    自动化渗透测试工具(Cobalt Strike)3.1 最新破解版[附使用教程] Cobalt Strike是一款专业的自动化渗透测试工具,它是图形化.可视化的,图形界面非常友好,一键傻瓜化使用MSF ...

  4. jenkins与gitlab集成,分支提交代码后自动构建任务(六)

    一.在gitlab中创建token 复制token,此token只显示一次:6SB8y4jt31NnYG5-nWoi 二.在gitlab上为项目创建trunk分支 三.在jenkins中配置gitla ...

  5. 定制你的“魅力”报告--Allure

    “人世间是一个大囚笼,每个人都在狱中,砥砺前行.九狱台中的刺,是生活中所要面对的砥砺,是锋利的刺,将自己肉身刺得千疮百孔,将自己的道心刺得千疮百孔.” ---<牧神记·九狱锁道心> 一.简 ...

  6. NumPy简单入门教程

    # NumPy简单入门教程 NumPy是Python中的一个运算速度非常快的一个数学库,它非常重视数组.它允许你在Python中进行向量和矩阵计算,并且由于许多底层函数实际上是用C编写的,因此你可以体 ...

  7. dfs 之 下一个排列

    52. 下一个排列 中文English 给定一个整数数组来表示排列,找出其之后的一个排列. Example 例1: 输入:[1] 输出:[1] 例2: 输入:[1,3,2,3] 输出:[1,3,3,2 ...

  8. Zipkin 知识点

    在Spring Cloud D版本,zipkin-server通过引入依赖的方式构建工程,自从E版本之后,这一方式改变了,采用官方的jar形式启动. 出处:https://juejin.im/post ...

  9. SpringMVC+Mybatis学习

    简单Web项目搭建: 一.流程 1. 导包 n个springMVC: 2个mybatis<其中一个是mybatis-spring>: 3个jackson包: 2. xml配置 web.xm ...

  10. Chrome插件安装和用法

    XPath Helper 下载插件,拖入chrome://extensions/ 使用方法:ctrl+shift+x呼出 JSONView的使用: 安装JSONView插件 下载插件,拖入chrome ...