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

做者:Martin Splitt
译者:前端小智
来源:dev

我的专栏 ES6 深刻浅出已上线,深刻ES6 ,经过案例学习掌握 ES6 中新特性一些使用技巧及原理,持续更新中,←点击可订阅。html

点赞再看,养成习惯

本文 GitHub https://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了不少个人文档,和教程资料。欢迎Star和完善,你们面试能够参照考点复习,但愿咱们一块儿有点东西。前端

知识要点

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

事例讲解

为了缩小代码量,这里演示部分代码,彻底的代码在 Github 上能够获得。git

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

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

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

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上,该客户端可能会运行如下代码:json

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

但这在咱们的浏览器中不起做用,经过控制的 network 来看看http://thirdparty.com 的请求:跨域

clipboard.png

请求成功,但结果不可用。缘由能够在控制台找到:浏览器

clipboard.png

啊哈!我们缺乏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和获取响应的结果:

clipboard.png

非简单的请求和预检

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

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可使用哪些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面板向咱们展现了缘由:

clipboard.png

浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,"预检"请求用的请求方法是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来容许跨源请求。

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

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


编辑中可能存在的bug无法实时知道,过后为了解决这些bug,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug

原文:https://dev.to/g33konaut/unde...


交流

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

https://github.com/qq449245884/xiaozhi

由于篇幅的限制,今天的分享只到这里。若是你们想了解更多的内容的话,能够去扫一扫每篇文章最下面的二维码,而后关注我们的微信公众号,了解更多的资讯和有价值的内容。

clipboard.png

相关文章
相关标签/搜索