理解 CORS (跨域资源共享)指南攻略_资源小白入门

知识要点浏览器强制执行同源策略,拒绝不同站点的网站访问。同源策略不会阻止对其他源的请求,但是会禁用对 JS 响应的访问。CORS 标头允许访问跨域响应。CORS 与 Credentials 一起时需要谨慎。CORS 是一个浏览器强制策略,其他应用程序不受此影响。事例讲解为了缩小代码量,这里演示部分代码,完全的代码在 Github 上可以得到。咱们从一个例子开

理解 CORS (跨域资源共享)指南攻略

知识要点

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

事例讲解

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

理解 CORS (跨域资源共享)指南攻略_资源小白入门

咱们从一个例子开始,假设咱们有一个网站,网址为 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 的请求:

理解 CORS (跨域资源共享)指南攻略_资源小白入门

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

理解 CORS (跨域资源共享)指南攻略_资源小白入门

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

理解 CORS (跨域资源共享)指南攻略_资源小白入门

非简单的请求和预检

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

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

前面的例子是一个的简单请求。简单的请求是带有一些允许的标头和标志头值的GET或POST请求。现在,对 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面板向我们展示了原因:

理解 CORS (跨域资源共享)指南攻略_资源小白入门

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

(1) Access-Control-Request-Method

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

(2) Access-Control-Request-Headers

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

此机制允许web服务器决定是否允许实际请求。浏览器设置Access-Control-Request-Headers和Access-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.com的cookie将不会被发送,在本例中为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来允许跨源请求。

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

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

原文:https://dev.to/g33konaut/understanding-cors-aaf

海计划公众号
(0)
上一篇 2020/03/22 01:27
下一篇 2020/03/22 01:27

您可能感兴趣的内容

  • javascript如何打印页面?攻略教程_页面入门基础知识

    我们在网页开发过程中经常会有打印页面的需求,通过JS来实现的方法有很多,下面我们看一下JavaScript中通过window.print()方法与jqprint()插件打印页面的方法。方式一:window.print()整体打印<a href="javascrīpt:window.print()" rel="external nofollow" target

    2020/03/24
  • 《深入浅出webpack》有感小白入门_webpack使用教程

    对于前端仔来说,相信大家对webpack都再熟悉不过了,但是你对webpack的了解程度又有多深呢,笔者花了几天时间看了一下《深入浅出webpack》,虽然说书中大部分介绍的是配置和使用相关的,但是如果你对webpack的配置、使用、原理和构建流程更加熟悉的话,对于你的开发可以说是百里无一害!本文不会局限于介绍配置,也不会详细介绍打包原理(后面打算写一篇有关

    2020/03/23
  • 微信小程序开发注意指南和优化实践菜鸟指南_小程序菜鸟知识

    前言转眼间已经参与过我厂好几个小程序的开发了,下面本妹子将开发中的那些注意点和各位小伙伴们分享下,妥妥的干货一枚。其中培训视频以上传到B站中,欢迎小伙伴们来围观评论^ ^ https://www.bilibili.com/video/av56083647一、WXML不要换行写,有空格不行微信开发者工具不会对代码进行trim操作,如果代码中换行,页面也直接换行

    2020/03/29
  • es6 函数的扩展入门教程_函数菜鸟指南

    函数参数的默认值
    我们都知道声明函数可以设置形参,但你有没有想过形参也可以直接设置默认值,我们接下来看看如何去写
    代码
    function f(x,y=2) {return x+y
    }
    console.log(f(2)) // 4
    上面的小例子只是设置了一个y的默认值2,然后我们使用这个函数的时候,只传递了x的参数2,所以我们会得到4,如果我们给y传递参数1呢

    2020/03/30
  • 一段代码,带你理解js执行上下文的工作流程新手入门_流程入门攻略

    我相信很多前端初学者一开始都会被执行上下文这个概念弄晕,或者说似懂非懂。对于工作两年的我来说,说来实在惭愧,虽然知道它大概是什么,但总觉得没有一个更为清晰的认识(无法把它的工作过程描述清楚),因此最近特意温习了一遍,写下了这篇文章执行上下文要说清它的大体工作流程,需要提前说明三个基本概念,分别是thread of exection(线程)、variable

    2020/03/29
  • emotion小白攻略_CSS-in-JS库,用于高性能样式合成

    emotion小白攻略 官方网址:https://emotion.sh/ GitHub:https://github.com/emotion-js/emotion 简介描述:CSS…

    2020/03/11
  • 微词云小白知识_简单强大的文字云艺术生成器

    微词云小白知识 官方网址:https://www.weiciyun.com/ 简介描述:简单强大的文字云艺术生成器 微词云是一个生成艺术文字的网站。 利用这个网站,我们可以轻松的生…

    2020/03/12
  • Js模块打包 exports和require 与 export和import 的用法和区别入门教程_打包基础入门

    1、CommonJS 之 exports和require用法CommoneJS规定每个文件是一个模块。将一个JavaScript文件直接通过script标签引入页面中,和封装成CommonJS模块最大的不同在于:前者的顶层作用域是全局作用域,在进行变量及函数声明时会污染全局环境;而后者会形成一个属于模块自身的作用域,所有的变量及函数只有自己能访问,对外是不可

    2020/03/20
  • TypeScript声明文件小白入门_文件小白攻略

    声明文件简介当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。什么是声明语句假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 标签引入 jQuery,然后就可以使用全局变量 $ 或 jQuery 了。如:$(‘body’)但是在 ts 中,编译器并不知道 $ 或 jQuery 是什

    2020/03/29
  • 嫁给程序员好吗?为什么劝你嫁给程序员!小白攻略_程序员攻略教程

    一、找程序员不用担心外遇。程序员是对着电脑工作,周围同事大部分是男生。他的生活中基本接触不到mm,所以不会有办公室恋情的发生,也就不会有外遇问题发生。而且面对的诱惑少,不像销售啊等职位,需要和外人打交道,而且应酬多,所面对的诱惑多,外遇出轨问题容易发生。程序员常常加班到半夜,MM们可以非常放心,唯一担心的就是他的身体是否吃得消。二、程序员很老实。在单位,老板

    2020/03/30
  • 前端安全系列之如何防止 XSS 攻击?基础知识教程_攻击使用指南

    作者:美团技术团队链接:https://my.oschina.net/meituantech/blog/2218539前端安全随着互联网的高速发展,信息安全问题已经成为企业最为关注的焦点之一,而前端又是引发企业安全问题的高危据点。在移动互联网时代,前端人员除了传统的 XSS、CSRF 等安全问题之外,又时常遭遇网络劫持、非法调用 Hybrid API 等新型

    2020/04/03
  • Bootstrap Table插件自定义排序使用方法基础指南_插件入门攻略

    Bootstrap Table 插件含有样式的数据如何排序,如下面的字段stargazers_countvar data = [{“name”: “bootstrap-table”,”stargazers_count”: “526“,”stargazers_count1”: “526”,”forks_count”: “122”,”d

    2020/03/26
  • 币世界网基础入门_快速获知比特币、区块链的行情价格资讯

    币世界网基础入门 官方网址:https://www.bishijie.com 简介描述:快速获知比特币、区块链的行情价格资讯 币世界(bishijie.com)是一个数字货币投资社…

    2020/03/06
  • ScrollingLettersAnimation使用指南一款滚动页面标题文字动态变化js特效

    ScrollingLettersAnimation菜鸟教程 官方网址:https://tympanus.net/codrops/? GitHub:https://github.co…

    2020/03/06
  • 你应该知道的简单易用的CSS技巧菜鸟教程网_技巧菜鸟教程

    作为前端,在工作中难免会遇到关于排版的问题,以下是我整理的一些关于CSS的技巧,希望对你能有帮助。1、每个单词的首字母大写一般我们会用JS实现,其实CSS就可以实现。JS代码:var str = ‘hello world’;
    str.replace(/( |^)[a-z]/g,(L)=>L.toUpperCase()
    Heool World css实现:t

    2020/03/23
  • 前端的正则表达式:基本概念使用说明_正则教程视频

    正则表达式(regex)是定义搜索模式的字符序列。由于对程序员的日常工作非常有用,所以在 JavaScript 中也支持它。在这个系列文章中,我会向你展示其工作方式以及其实际用途。希望在结束本系列后,你将能够轻松的写出自己的正则表达式。创建正则表达式的方法在 JavaScript 中可以通过两种方式去构造正则表达式。要完全理解它,你需要知道正则表达式包含在两

    2020/03/23