1. 首页

彻底掌握并理解 CORS(跨域资源共享)

知识要点

  • 浏览器强制执行同源策略,拒绝不同站点的网站访问。
  • 同源策略不会阻止对其他源的请求,但是会禁用对 JS 响应的访问。
  • CORS 标头允许访问跨域响应。
  • CORS 与 Credentials 一起时需要谨慎。
  • CORS 是一个浏览器强制策略,其他应用程序不受此影响。

事例讲解

为了缩小代码量,这里演示部分代码,完全的代码在 Github 上可以得到。

咱们从一个例子开始,假设咱们有一个网站,网址为 http://good.com:8000/public:


app.get('/public', function(req, res) { res.send(JSON.stringify({ message: 'This is public' })); })

咱们还有一个简单的登录功能,用户可以输入一个共享的密匙并设置一个cookie,以将其标识为已验证:


app.post('/login', function(req, res) { if(req.body.password === 'secret') { req.session.loggedIn = true res.send('You are now logged in!') } else { res.send('Wrong password.') } })

咱们通过 /private获取一些私有数据,就可以通过上面登录状态来做进一步验证。


app.get('/private', function(req, res) { if(req.session.loggedIn === true) { res.send(JSON.stringify({ message: 'THIS IS PRIVATE' })) } else { res.send(JSON.stringify({ message: 'Please login first' })) } })

通过 AJAX 从其他域请求咱们的 API

目前,咱们 API 并不是专门设计,但可以允许其他人从 /public URL 中获取数据。 假设咱们的API位于good.com:300/public上,并且咱们的客户端托管在thirdparty.com上,该客户端可能会运行以下代码:


fetch('http://good.com:3000/public') .then(response => response.text()) .then((result) => { document.body.textContent = result })

但这在我们的浏览器中不起作用,通过控制的 network 来看看http://thirdparty.com 的请求:

Js中文网 一个帮助开发者成长的社区,你想要的,在这里都能找到

JS中文网 – 前端进阶资源分享 www.javascriptC.com
一个致力于帮助开发者用代码改变世界为使命的平台,每天都可以在这里找到技术世界的头条内容

请求成功,但结果不可用。原因可以在控制台找到:

Js中文网 一个帮助开发者成长的社区,你想要的,在这里都能找到

啊哈!咱们缺少Access-Control-Allow-Origin标头。 但是,为什么我们需要它,它有什么用呢?

同源策略

我们在 JS 中得不到响应结果的原因是同源策略。该策略的目的是确保一个网站不能读取对另一个网站的请求的结果,并由浏览器强制执行。出于安全方面的考虑,现在的网页都用cookie来进行身份验证,如果不限制读取,网页B里的恶意脚本代码可以随意模仿真实用户进行操作。

例如: 如果在咱们在 example.org上,并不会希望该网站向我们的银行网站发出请求,获取咱们的帐户余额和交易。

同源策略可以防止这种情况的发生。

在这种情况下,“来源”由

  • 协议(如http)
  • 域名(如 example.com)
  • 端口(如8000)

关于 CSRF(跨站点请求伪造) 的说明

请注意,有一类攻击称为CSRF(跨站点请求伪造),它无法通过同源策略来避免。

CSRF攻击中,攻击者向后台的第三方页面发出请求,例如向咱们的银行网站发送POST请求。如果我们与我们的银行存在一个有效的会话,任何网站都可以在后台发出请求,该请求将被执行,除非咱们的银行网站有针对CSRF的反措施。

注意,尽管同源策略已经生效,但是的咱们的示例请求从thirdparty.com成功请求到good.com,只是我们无法获得结果。但对于CSRF来说,不需要获取的结果。

例如,有个 API 通过POST请求方式发送邮件,返回的内容是咱们需要关心的,蛤攻击者不在乎结果,他们关心的是电子邮件是否有发送了成功。

为咱们的 API 启用 CORS

现在,咱们希望允许第三方站点(如thirdparty.com)上的 JS 访问咱们的 API 能得到响应。为此,我们可以根据错误提示启用CORS标头:


app.get('/public', function(req, res) { res.set('Access-Control-Allow-Origin', '*') res.send(...) })

这里将access-control-allow-origin标头设置为*,这意味着:允许任何主机访问此URL和获取响应的结果:

Js中文网 一个帮助开发者成长的社区,你想要的,在这里都能找到

非简单的请求和预检

如果请求不是简单请求,浏览器会先发送一个预请求:

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

前面的例子是一个的简单请求。简单的请求是带有一些允许的标头和标志头值的GETPOST请求。现在,对 thirdparty.com 进行了一些更改让它能获取到JSON格式的数据。


fetch('http://good.com:3000/public', { headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then((result) => { document.body.textContent = result.message })

但这又让thirdparty.com崩溃了,network面板向我们展示了原因:

Js中文网 一个帮助开发者成长的社区,你想要的,在这里都能找到

浏览器发现,这是一个非简单请求,就自动发出一个”预检”请求,”预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的,头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,”预检”请求的头信息包括两个特殊字段。

(1) Access-Control-Request-Method

该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是GET

(2) Access-Control-Request-Headers

该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段.

此机制允许web服务器决定是否允许实际请求。浏览器设置Access-Control-Request-HeadersAccess-Control-Request-Method标头信息,告诉服务器需要什么请求,服务器用相应的标头信息进行响应。

咱们的服务器还没有响应这些标头信息,所以需要添加它们:


app.get('/public', function(req, res) { res.set('Access-Control-Allow-Origin', '*') res.set('Access-Control-Allow-Methods', 'GET, OPTIONS') res.set('Access-Control-Allow-Headers', 'Content-Type') res.send(JSON.stringify({ message: 'This is public info' })) })

现在,thirdparty.com可以再次获得响应。

凭证(credentials)和 CORS

现在,假设咱们已登录good.com并可以使用敏感信息访问 /private URL。通过设置CORS,可以让其他网站,比如evil.com获得这些敏感信息,来看看:


fetch('http://good.com:3000/private') .then(response => response.text()) .then((result) => { let output = document.createElement('div') output.textContent = result document.body.appendChild(output) })

无论是否已经登录到good.com,都会看到“Please login first”。

原因是当请求来自另一个来源时,来自good.comcookie将不会被发送,在本例中为evil.com。咱们可以要求浏览器发送cookie,即使它是一个跨域源:


fetch('http://good.com:3000/private', { credentials: 'include' }) .then(response => response.text()) .then((result) => { let output = document.createElement('div') output.textContent = result document.body.appendChild(output) })

但同样,这无法在浏览器中工作,其实,这也是个好事。

象一下,任何网站都可以发出经过身份验证的请求,但不会发送实际的cookie,并且无法获得响应。

因此,咱们不希望evil.com能够访问此私有数据-但是,如果我们希望thirdparty.com可以访问/ private,该怎么办?

在这种情况下,需要将Access-Control-Allow-Credentials标头设置为true


app.get('/private', function(req, res) { res.set('Access-Control-Allow-Origin', '*') res.set('Access-Control-Allow-Credentials', 'true') if(req.session.loggedIn === true) { res.send('THIS IS THE SECRET') } else { res.send('Please login first') } })

但这仍然行不通,允许每个经过身份验证的跨源请求是一种危险的做法

当咱们希望允许thirdparty.com访问/private时,可以在标头中指定此来源:


app.get('/private', function(req, res) { res.set('Access-Control-Allow-Origin', 'http://thirdparty.com:8000') res.set('Access-Control-Allow-Credentials', 'true') if(req.session.loggedIn === true) { res.send('THIS IS THE SECRET') } else { res.send('Please login first') } })

现在,http://thirdparty:8000也可以访问私有数据,而evil.com被锁定了。

允许多个来源

现在,咱们已经允许一个源使用身份验证数据进行跨源请求。但是如果多个第三方来源要怎么办呢?

在这种情况下,可以使用白名单:


const ALLOWED_ORIGINS = [ 'http://anotherthirdparty.com:8000', 'http://thirdparty.com:8000' ] app.get('/private', function(req, res) { if(ALLOWED_ORIGINS.indexOf(req.headers.origin) > -1) { res.set('Access-Control-Allow-Credentials', 'true') res.set('Access-Control-Allow-Origin', req.headers.origin) } else { // allow other origins to make unauthenticated CORS requests res.set('Access-Control-Allow-Origin', '*') } // let caches know that the response depends on the origin res.set('Vary', 'Origin'); if(req.session.loggedIn === true) { res.send('THIS IS THE SECRET') } else { res.send('Please login first') } })

再次提醒:不要直接发送req.headers.origin作为CORS原始标头。这将允许任何网站访问对咱们的网站进行身份验证的请求。

这条规则可能有例外,但是在使用没有白名单的凭证实现CORS之前至少要三思。

总结

在本文中,咱们研究了同源策略以及如何在需要时使用CORS来允许跨源请求。

这需要服务器和客户端设置,并且根据请求会出现预检请求。

处理经过身份验证的跨域请求时,应格外小心。 白名单可以帮助允许多个来源,而不会冒泄露敏感数据(在身份验证后受到保护)的风险。

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

作者:Martin Splitt
译者:前端小智
链接:https://segmentfault.com/a/1190000021029334

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:彻底掌握并理解 CORS(跨域资源共享)

链接:https://www.javascriptc.com/3542.html

« 前端框架通用的面试题
Js中文网周刊第63期»
Flutter 中文教程资源

相关推荐

QR code