✋🏼🔥 CS Visualized: CORS

#tutorial#webdev#securityLydia Hallie2020年7月27日 ・9 min read

link: https://dev.to/lydiahallie/cs-visualized-cors-5b8h?utm_campaign=React%2BNative%2BNow&utm_medium=web&utm_source=React_Native_Now_69#cs-cors

It’s every developer’s frustration once in a while to see that big red Access to fetched has been blocked by CORS policy error in your console! 😬 Although there are some ways to quickly get rid of this error, let’s not take anything for granted today! Instead, let’s see what CORS is actually doing, and why it’s actually our friend 👏🏼

❗️ In this blog post I won’t explain HTTP basics. In case you’d like to know more about HTTP requests and responses, I wrote a small blog post about it a while ago though 🙂 In my examples I use HTTP/1.1 instead of HTTP/2, this doesn’t affect CORS.

S H O R T C U T S
✋🏼 Same-Origin Policy
🔥 Client-side CORS
💻 Server-side CORS
🚀 Preflight Requests
🍪 Credentials

On the frontend, we often want to display data that’s located elsewhere! Before we can display this data, the browser first has to make a request to a server in order to fetch that data! The client sends an HTTP request with all the information that the server needs in order to send that data back to the client 🙂

Let’s say we’re trying to fetch some user information on our www.mywebsite.com website from a server that’s located at api.website.com!

Alt Text

Perfect! 😃 We just sent an HTTP request to the server, which then responded with the JSON data we asked for.

Let’s try the exact same request but from another domain. Instead of making the request from www.mywebsite.com, we’re now making the request from a website located at www.anotherdomain.com.

Alt Text

Wait, what? We sent the exact same request, but this time the browser shows us a weird error?

We just saw CORS in action! 💪🏼 Let’s see why this error occurred, and what it exactly means.


✋🏼 Same-Origin Policy

The web enforces something called the same-origin policy. By default, we can only access resources that are located at the same origin as the origin of our request! 💪🏼 It’s totally okay to load an image that’s located at https://mywebsite.com/image1.png, for example.

A resource is cross-origin when it’s located at a different (sub)domain, protocol, or port!

image3

Cool, but why does the same-origin policy even exist?

Let’s say that the same-origin policy didn’t exist, and you accidentally clicked one of the many virus links your aunt sent you on Facebook. This link redirects you to an “evil website” that has an iframe embedded which loads your bank’s website, and successfully logs you in by some set cookies! 😬

The developers of this “evil website” made it possible for the website to access this iframe and interact with the DOM contents of your bank’s website in order to send money to their account on your behalf!

Alt Text

Yeah… this is a huge security risk! We don’t want anyone to just be able to access everything 😧

Luckily, the same-origin policy helps us out here! This policy makes sure that we can only access resources from the same origin.

Alt Text

In this case, the origin www.evilwebsite.com tried to access cross-origin resources from www.bank.com! The same-origin policy blocked this from happening and made sure that the evil website’s devs couldn’t just access our bank data 🥳

Okay, so… what does this have to do with CORS?


🔥 Client-side CORS

Although the same-origin policy actually only applies to scripts, browsers “extended” this policy for JavaScript requests: by default, we can only access fetched resources from the same origin!

Alt Text

Hmm, but… We often have to access cross-origin resources 🤔 Maybe our frontend needs to interact with our backend API in order to load the data? In order to allow cross-origin requests safely, the browser uses a mechanism called CORS! 🥳

CORS stands for Cross-Origin Resource Sharing. Although the browser disallows us from accessing resources that aren’t located at the same origin, we can use CORS to change those security restrictions a bit while still making sure that we’re accessing those resources safely 🎉

User agents (a browser, for example) can use the CORS mechanism in order to allow cross-origin requests which otherwise would’ve been blocked, based on the values of certain CORS-specific headers in the HTTP response! ✅

When a cross-origin request is made, the client automatically adds an extra header to our HTTP request: Origin. The value of the Origin header is the origin where the request came from!

Alt Text

In order for the browser to allow accessing cross-origin resources, it expects certain headers from the server’s response, which specify whether this server allows cross-origin requests!


💻 Server-side CORS

As a server developer, we can make sure that cross-origin requests are allowed by adding extra headers to the HTTP response, which all start with Access-Control-* 🔥 Based on the values of these CORS response headers, the browser can now allow certain cross-origin responses which would’ve normally been blocked by the same-origin policy!

Although there are several CORS headers we can use, there is one header that the browser needs in order to allow cross-origin resource access: Access-Control-Allow-Origin! 🙂
The value of this header specifies which origins are allowed to access the resources that they’re requesting from the server.

If we’re developing a server that https://mywebsite.com should have access to, we can add the value of that domain to the Access-Control-Allow-Origin header!

Alt Text

Awesome! 🎉 This header is now added to the response that the server sends back to the client. By adding this header, the same-policy origin will no longer restrict us from receiving resources that were located at the https://api.mywebsite.com origin, if we sent the request from https://mywebsite.com!

Alt Text

The CORS mechanism within the browser checks whether the value of the Access-Control-Allow-Origin header equals the value of the Origin that was sent by the request 🤚🏼

In this case, the origin of our request is https://www.mywebsite.com, which is listed in the Access-Control-Allow-Origin response header!

Alt Text

Perfect! 🎉 We were able to receive the cross-origin resources successfully! So what happens when we’re trying to access these resources from an origin that’s not listed in the Access-Control-Allow-Origin header? 🤔

Alt Text

Ahh yeah, CORS throws the notorious error that can be so frustrating at times! But now we actually see that it makes total sense

The 'Access-Control-Allow-Origin' header has a value
 'https://www.mywebsite.com' that is not equal 
to the supplied origin. 

In this case, the supplied origin was https://www.anotherwebsite.com. However, the server didn’t have this supplied origin in the list of allowed origins in the Access-Control-Allow-Origin header! CORS successfully blocked the request, and we cannot access the fetched data in our code 😃

CORS also allows us to add the wildcard * as the value for the allowed origins. This means that requests from all origins should have access to the requested resources, so be careful!


Access-Control-Allow-Origin is one of the many CORS headers we can provide. A server developer can extend the server’s CORS policies in order to (dis)allow certain requests! 💪🏼

Another common header is the Access-Control-Allow-Methods header! CORS will only allow cross-origin requests if they were sent with the listed methods.

Alt Text

In this case, only requests with a GETPOST, or PUT method will be allowed! Other methods such as PATCH or DELETE will be blocked ❌

If you’re curious about what the other possible CORS headers are and what they’re used for, check out this list.

Speaking of PUTPATCH, and DELETE requests, CORS actually handles those requests differently! 🙃 These “non-simple” requests initiate something called a preflight request!


🚀 Preflighted Requests

CORS has two types of requests: a simple request and a preflighted request. Whether a request is simple or preflighted depends on some values within the request (don’t worry, you don’t have to memorize this lol).

A request is simple when the request is a GET or POST method and doesn’t have any custom headers! Any other request, such as requests with a PUTPATCH, or DELETE method, will be preflighted.

In case you’re just curious about which requirements a request has to meet in order to be a simple request, MDN has a useful list!

Okay sure, but what does “preflighted request” even mean, and why does this happen?


Before the actual request gets sent, the client generates a preflighted request! The preflighted request contains information about the actual request we’re about to in its Access-Control-Request-* headers 🔥

This gives the server information about the actual request that the browser is trying to make: what is the method of the request, what are the additional headers, and so on.

Alt Text

The server receives this preflighted request, and sends an empty HTTP response back with the server’s CORS headers! The browser receives the preflight response, which contains no data besides the CORS headers, and checks whether the HTTP request should be allowed! ✅

Alt Text

If that’s the case, the browser sends the actual request to the server, which then responds with the data we asked for!

Alt Text

However, if it’s not the case, CORS will block the preflighted request, and the actual request never gets sent ✋🏼 The preflighted request is a great way to prevent us from accessing or modifying resources on servers that don’t have any CORS policies enabled (yet)! Servers are now protected from potentially unwanted cross-origin requests 😃

💡 In order to reduce the number of roundtrips to our server, we can cache the preflighted responses by adding an Access-Control-Max-Age header to our CORS requests! We can cache the preflighted response this way, which the browser can use instead of sending a new preflighted request!


🍪 Credentials

Cookies, authorization headers, and TLS certificates are by default only set on same-origin requests! However, we may want to use these credentials in our cross-origin request. Maybe we want to include cookies on the request that the server can use in order to identify the user!

Although CORS doesn’t include credentials by default, we can change this by adding the Access-Control-Allow-Credentials CORS header! 🎉

If we want to include cookies and other authorization headers to our cross-origin request, we need to set the withCredentials field to true on the request and add the Access-Control-Allow-Credentials header to the response.

Alt Text

All set! We can now include credentials in our cross-origin request 🥳


Although I think we can all agree that CORS errors can be frustrating at times, it’s amazing that it enables us to safely make cross-origin requests in the browser (it should receive a bit more love lol) ✨

Obviously there is so much more to the same-origin policy and CORS than I was able to cover here in this blog post! Luckily, there are many great resources out there like this one or the W3 spec if you want to read more about it 💪🏼


15 张精美动图全面讲解 CORS

前言:

本文翻译自 Lydia Hallie 小姐姐写的 ✋🏼🔥 CS Visualized: CORS,她用了大量的动图去解释 CORS 这个概念,国内还没有人翻译本文,所以我在原文的理解上翻译了本文并修改了一些错误,希望能帮到大家。

觉得翻译的不错一定要点赞哦,谢谢你,这对我真的很重要! 🌟

注:原文的动图均为 keynote 制作

前端开发中,我们经常要使用其他站点的数据。前端显示这些数据之前,必须向服务器发出请求以获取该数据。

假设我们正在访问 https://api.mywebsite.com 这个站点,点击按钮向 https://api.mywebsite.com/users 发送请求,获取网站上的一些用户信息:

⚠️:这里原作者有个笔误,把 https://api.mywebsite.com 误写为 https://www.mywebsite.com 了,图中也有这个错误,读者要注意一下不要被误导

从结果上看表现非常完美,我们向服务器发送请求,服务器返回了我们需要的 JSON 数据,前端也正常的渲染出了结果。

下面我们换一个网站试试。用 https://www.anotherwebsite.com 这个网站向 https://api.website.com/users 发送请求:

问题来了,我们请求同样的接口网站,但是这次浏览器给我们抛出一个 Error。

刚刚浏览器抛出的就是 CORS Error,下面让我们分析一下为什么会产生这种 Error,以及这个 Error 的确切含义是什么。

1.同源策略

浏览器网络请求时,有一个同源策略的机制。即默认情况下,使用 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源。

比如说, https://www.mywebsite.com 请求 https://www.mywebsite.com/page 是完全没有问题的。但是当资源位于不同协议子域端口的站点时,这个请求就是跨域的。

目前来看,同源策略会让三种行为受限:

  • Cookie、LocalStorage 和 IndexDB 访问受限
  • 无法操作跨域 DOM(常见于 iframe)
  • Javascript 发起的 XHR 和 Fetch 请求受限

那么,为什么会存在同源策略呢?

我们做个假设,如果不存在同源策略,你无意中点击了七大姑在微信上给你发的一篇养生文章链接。其实这个网页是个钓鱼网站,访问链接后就把你重定向到一个嵌入了 iframe 的攻击网站,这个 iframe 会自动加载银行网站,并通过 cookies 登录你的账户。

登陆成功后,这个钓鱼网站还可以控制 iframe 的 DOM,通过一系列骚操作把你卡里的钱转走。

这是一个非常严重的安全漏洞,我们不希望自己在互联网的内容被随便访问,更不要说这种涉及到钱的网站了。

同源策略可以帮助我们解决这个安全问题,这个策略确保我们只能访问同一站点的资源。

在这种情况下,https://www.evilwebsite.com 尝试跨站访问 https://www.bank.com 的资源,同源策略就会阻止这个操作,让钓鱼网站无法访问银行网站的数据。

说了这么多,同源策略和 CORS 又有什么关系?

2.浏览器 CORS

出于安全原因,浏览器限制从脚本内发起的跨域 HTTP 请求。 例如 XHR 和 Fetch 就遵循同源策略。这意味着使用 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源。

日常的业务开发中,我们会经常访问跨域资源,为了安全的请求跨域资源,浏览器使用一种称为 CORS 的机制。

CORS 的全名是 Cross-Origin Resource Sharing,即跨域资源共享。尽管默认情况下浏览器禁止我们访问跨域资源,但是我们可以利用 CORS 放宽这种限制,在保证安全性的前提下访问跨域资源。

浏览器可以利用 CORS 机制,放行符合规范的跨域访问,阻止不合规范的跨域访问。浏览器内部是怎么做的呢?我们下面就来分析一下。

Web 程序发出跨域请求后,浏览器会自动向我们的 HTTP header 添加一个额外的请求头字段:OriginOrigin 标记了请求的站点来源:

GET https://api.website.com/users HTTP/1/1
Origin: https://www.mywebsite.com // <- 浏览器自己加的
复制代码

为了使浏览器允许访问跨域资源, 服务器返回的 response 还需要加一些响应头字段,这些字段将显式表明此服务器是否允许这个跨域请求。

3.服务端 CORS

作为服务器开发人员,我们可以通过在 HTTP 响应中添加额外的响应头字段 Access-Control-* 来表明是否允许跨域请求。根据这些 CORS 响应头字段,浏览器可以允许一些被同源策略限制的跨源响应。

虽然有好几个 CORS 响应头字段,但有一个字段是必加的,那就是 Access-Control-Allow-Origin。这个头字段的值指定了哪些站点被允许跨域访问资源。

1️⃣ 如果我们有服务器的开发权限,我们可以给 https://www.mywebsite.com 加上访问权限:将该域添加到 Access-Control-Allow-Origin 中。

这个响应头字段现在被添加到服务器发回给客户端的 response header 中。这个字段添加后,如果我们从 https://www.mywebsite.com 发送跨域请求,同源策略将不再限制 https://api.mywebsite.com 站点返回的资源。

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.mywebsite.com
Date: Fri, 11 Oct 2019 15:47 GM
Content-Length: 29
Content-Type: application/json
Server: Apache

{user: [{...}]}
复制代码

2️⃣ 收到服务器返回的 response 后,浏览器中的 CORS 机制会检查 Access-Control-Allow-Origin 的值是否等于 request 中 Origin 的值。

在这个例子中,request 的 Origin 是 https://www.mywebsite.com,这和 response 中 Access-Control-Allow-Origin 的值是一样的:

3️⃣ 浏览器校验通过,前端成功地接收到跨域资源。

那么,当我们试图从一个没有在 Access-Control-Allow-Origin 中列出的网站跨域访问这些资源会发生什么呢?

如上图所示,从 https://www.anotherwebsite.com 跨域访问 https://api.mywebsite.com 资源,浏览器抛出一个 CORS Error,经过上面的讲解,我们可以读懂这个报错信息了:

The 'Access-Control-Allow-Origin' header has a value
 'https://www.mywebsite.com' that is not equal 
to the supplied origin. 
复制代码

在这种情况下,Origin 的值是 https://www.anotherwebsite.com。然而,服务器在 Access-Control-Allow-Origin 响应头字段中没有标记这个站点,浏览器 CORS 机制就阻止了这个响应,我们无法在我们的代码中获取响应数据。

CORS 还允许我们添加通配符 * 作为允许的外域,这意味着该资源可以被任意外域访问,所以要注意这种特殊情况


Access-Control-Allow-Origin 是 CORS 机制提供的众多头字段之一。服务器开发人员还可以通过其它头字段扩展服务器的 CORS 策略,以允许/禁止某些请求。

另一个常见的响应头字段是 Access-Control-Allow-Methods。其指明了跨域请求所允许使用的 HTTP 方法。

在上图的案例中,只有GETPOST 或 PUT 方法被允许跨域访问资源。其他 HTTP 方法,例如 PATCH 和 DELETE 都会被阻止。

如果您想知道其它的 CORS 响应头字段是什么以及它们的用途,可以查看此列表

说到PUTPATCH 和 DELETE 这几个 HTTP 方法,CORS 处理这些方法时还有些不同。这些非简单请求会触发 CORS 的预检请求。

4.预检请求

CORS 有两种类型的请求:一种是简单请求(simple request),一种是预检请求(preflight request)。一个跨域请求到底是简单的的还是预检的,取决于一些 request header。

当请求是 GET 或 POST 方法并且没有任何自定义 Header 字段时,一般来说就是个简单请求。除此之外的任何请求,诸如 PUTPATCH 或 DELETE 方法,将会产生预检。

如果你想知道一个请求必须满足哪些要求才能成为简单请求,可以查看 MDN 简单请求相关的文档

说了这么多,「预检请求」到底是什么意思?下面我们就来探讨一下。


1️⃣ 在发送实际请求之前,客户端会先使用 OPTIONS 方法发起一个预检请求,预检请求的 Access-Control-Request-* 中包含有关我们将要处理的实际请求的信息:

OPTIONS https://api.mywebsite.com/user/1 HTTP/1.1
Origin: https://www.mywebsite.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
复制代码

2️⃣ 服务器接收到预检请求后,会返回一个没有 body 的 HTTP 响应,这个响应标记了服务器允许的 HTTP 方法和 HTTP Header 字段:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.mywebsite.com
Access-Control-Request-Method: GET POST PUT
Access-Control-Request-Headers: Content-Type
复制代码

3️⃣ 浏览器收到预检响应,并检查是否应允许发送实际请求。

⚠️:上图预检响应漏了 Access-Control-Allow-Headers: Content-Type

4️⃣ 如果预检响应检测通过,浏览器会将实际请求发送到服务器,然后服务器返回我们需要的资源。

如果预检响应没有检验通过,CORS 会阻止跨域访问,实际的请求永远不会被发送。预检请求是一种很好的方式,可以防止我们访问或修改那些没有启用 CORS 策略的服务器上的资源。

💡 为了减少网络往返次数,我们可以通过在 CORS 请求中添加 Access-Control-Max-Age 头字段来缓存预检响应。浏览器可以使用缓存来代替发送新的预检请求。

5.认证

XHR 或 Fetch 与 CORS 的一个有趣的特性是,我们可以基于 Cookies 和 HTTP 认证信息发送身份凭证。一般而言,对于跨域 XHR 或 Fetch 请求,浏览器不会发送身份凭证信息。

尽管 CORS 默认情况下不发送身份凭证,但我们可以通过添加 Access-Control-Allow-Credentials CORS 响应头来更改它。

如果要在跨域请求中包含 cookie 和其他授权信息,我们需要做以下操作:

  • XHR 请求中将 withCredentials 字段设置为 true
  • Fetch 请求中将 credentials 设为 include
  • 服务器把 Access-Control-Allow-Credentials: true 添加到响应头中
// 浏览器 fetch 请求
fetch('https://api.mywebsite,com.users', {
  credentials: "include"
})

// 浏览器 XHR 请求
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;

// 服务器添加认证字段
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
复制代码

把上面的工作做好后,我们就可以在跨域请求中包含身份凭证信息了。

6.总结

CORS Error 一定程度上会让前端开发很头疼,但是遵循它的相关规定后,它可以让我们在浏览器中进行安全的跨域请求。

同源策略和 CORS 的知识点有很多,本文只讲了一些关键知识点,如果你想全面学习 CORS 的相关知识,我推荐你查阅MDN 文档和 W3C 规范,这些一手知识是最准确的。

Leave a Reply

Your email address will not be published. Required fields are marked *