美文网首页
怎样与 CORS 和 cookie 打交道

怎样与 CORS 和 cookie 打交道

作者: 京程一灯 | 来源:发表于2019-04-04 18:26 被阅读0次

> 翻译:疯狂的技术宅

### 前言

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

针对这个问题,[MDN](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS) 上有非常详细的解释,所以这篇文章主要在于整理重点和实际操作时经常出现的问题。

### 同源策略(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 策略的话,会显示下列信息:

![img](https://cdn-images-1.medium.com/max/1200/0*RGcOZAyJQb8oFD3G.png)

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

首先,如果我们按照提示中所说的,将 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Simple_requests)

3. 若有 `Content-Type`(注意是请求头,**不是响应头**),则必须是下列的值:`application/x-www-form-encoded`,`text/plain`,`multipart/form-data`

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

我们试着把 `Content-Type` 改为 `application/json` 来测试一下(不能为 `application/x-www-form-encoded`,`text/plain`,`multipart/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](https://cdn-images-1.medium.com/max/600/1*oGnJZOHld3hPkSZxWdHQ3Q.png)

![GET](https://cdn-images-1.medium.com/max/600/1*4eLJJpllKlfz539dnGnnXQ.png)

*上图为 OPTION,下图为GET*

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

![](https://cdn-images-1.medium.com/max/1200/0*VB-gjFFFRScgbpKh.png)

的确无法通过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](https://cdn-images-1.medium.com/max/800/0*egJJWQOyNy6_Tltp.png)

*服务器回传Set-Cookie*

![没有写入到浏览器中](https://cdn-images-1.medium.com/max/800/0*wqK1RIHqzvsw7aMU.png)

*没有写入浏览器中*

在一般情况下如果再使用 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!](https://cdn-images-1.medium.com/max/800/1*kvlVK61eQhYVYlPeudE0uw.png)

*Request Cookies 里有个 jack!*

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

##### 1.用户禁用了此域的 cookie

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

**解决方法:**

* 改域

* <del>检讨自己为什么被用户封锁</del>

##### 2.用户阻止了所有外部网站的cookie

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

### 后记

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

最后附上[源代码](https://github.com/kjj6198/cors_example)。

### 参考文章

* <https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS>

欢迎关注京程一灯公众号:jingchengyideng,回复“2”领取以上视频课

相关文章

  • 怎样与 CORS 和 cookie 打交道

    > 翻译:疯狂的技术宅 ### 前言 CORS 与 cookie 在前端是个非常重要的问题,不过在大多数情况下,因...

  • CORS跨域请求,怎么将Cookie传给后端

    CORS跨域请求,怎么将Cookie传给后端 后端 增加代码: //CORS跨域 允许Authorization ...

  • Ajax跨域

    CORS Ajax跨域不携带COOKIE PHP jQuery

  • 浏览器环境

    window对象 history对象 Cookie Web Storage:浏览器端数据储存机制 AJAX CORS通信

  • CORS 跨域 Cookie

    前言:在之前的文章中我已经对如何使用 CORS 处理跨域请求进行了介绍,在本文中,我将介绍如何在 CORS 跨域的...

  • 本地存储LocalStorage

    cookie : 和服务端打交道的都用cookie(用户名密码等) 4kb

  • cookie以及nginx,cors下的cookie

    一.cookie设置 1.cookie的名字和值 2.过期时间(expires),格式:Wdy, DD-Mon-Y...

  • 怎样和男人打交道

    今天是幸福的一天,我的两个小伙子助理去莱芜办案,一个特意给我带回来一份炒鸡,一个前几天看到我的律师证套坏了,给我网...

  • CORS跨域发送Cookie

    引言 由于默认情况下浏览器对跨域请求不允许携带Cookie,所以这次开发再与前端同学在权限验证这块踩了好多坑,故将...

  • CORS 跨域 Cookie 的设置与获取

    前言:在之前的文章中我已经对如何使用 CORS 处理跨域请求进行了介绍,在本文中,我将介绍如何在 CORS 跨域的...

网友评论

      本文标题:怎样与 CORS 和 cookie 打交道

      本文链接:https://www.haomeiwen.com/subject/sxiqiqtx.html