CORS(跨域资源共享)

CORS(跨域资源共享)使用额外的HTTP头部告诉浏览器,允许运行在origin(domain)上的Web应用访问来自不同源服务器上的指定资源。

浏览器访问一个web应用,这个web应用会发很多的跨域请求,例如加载不同源的JS/CSS脚本,或者加载不同源图片等。但是并没有发现请求的异常,这些资源是可以正常返回的。而通过JS发送的跨域HTTP请求却时常得到错误,所以跨域请求很常见,但是浏览器对于请求跨域的限制却只存在于脚本发送的HTTP请求(Ajax/Fetch)。

同源限制在安全上是有其必要性的,例如可以很轻松规避CSRF攻击。

通过上面的描述可以看出来同源策略的限制存在于浏览器端,而CORS策略用额外的HTTP请求头字段来告诉浏览器,该资源是允许被当前域上的web应用跨域访问的。

CORS的具体步骤

那么具体CORS是怎么做的呢?

  1. 浏览器察觉请求跨域没有发起请求?
  2. 浏览器发起跨域请求没有正常返回结果?

上面说到“通过额外的HTTP头告诉浏览器源上的web应用被允许访问来自不同源服务器上的指定资源”。告诉浏览器的自然是通过服务端的响应携带的额外的HTTP头,所以可以看出来请求是发送了的。那么服务端是携带了标识当前源允许访问的该资源的HTTP头?如果允许的话自然是请求一切正常,不允许的话浏览器则会报错并且不会将请求结果返回给请求的发起方,也就是Ajax/Fetch代码,并且代码中获取不到是哪一步出了错只能在浏览器中看到错误日志(为了安全)。

那么就只是这样吗?跨域请求失败是因为浏览器正常发送了请求,服务端正常响应了请求,然后浏览器发觉响应头中没有允许跨域的标识,然后拦截返回结果并报错。

并不完全是这样,这只是CORS的一部分,这部分被称为简单请求。

既然有简单请求就有非简单请求。非简单请求的具体步骤和上面描述的简单请求很不一样,会在发送真正的请求之前发送一个预检请求(preflight request)询问服务端是否允许当前源跨域访问该资源,允许则继续发送真正的请求,否则直接报错。

所以上面的两种做法都被应用到CORS策略中:一种是拦截请求的返回结果,一种是不发送真正的跨域请求。

简单请求

简单请求并不会发送预检请求,而是直接发送真正的请求。简单请求必须要全部满足下面的条件:

  • 使用下列方法之一:
    • GET
    • HEAD
    • POST
  • 不得人为设置该集合之外的其他首部字段。该集合为:
    • Accep
    • Accept-Language
    • Content-Language
    • Content-Type(需要注意额外的限制)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type 的值仅限于下列三者之一:(这个集合中没有application/json
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  • 请求中的任意XMLHttpRequestUpload`对象均没有注册任何事件监听器;XMLHttpRequestUpload对象可以使用 XMLHttpRequest.upload 属性访问。
  • 请求中没有使用ReadableStream对象。

下面我们将从源http://dev.jd.com:9091访问http://dev.jd.com:9090的资源。

如果没有使用CORS显然会被浏览器的同源策略限制从而报错,如下:

在这里插入图片描述

分别查看请求头:

GET /corsget HTTP/1.1
Host: dev.jd.com:9090
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1
Accept: */*
Origin: http://dev.jd.com:9091
Referer: http://dev.jd.com:9091/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

通过请求头可以看出来其中包含了很多简单请求定义的字段以外的字段,但是却并没有触发预检请求,这是因为这些头字段并不是人为设置的

响应头:

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 16
ETag: W/"10-oV4hJxRVSENxc/wX8+mA4/Pe4tA"
Date: Mon, 06 Apr 2020 09:45:36 GMT
Connection: keep-alive

修改服务端程序,让响应头带上标识,告诉浏览器允许源http://dev.jd.com:9091上的web应用访问不同源(http://dev.jd.com:9090)上的资源/corsget。

请求头同上,响应头如下:

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
Content-Length: 16
ETag: W/"10-oV4hJxRVSENxc/wX8+mA4/Pe4tA"
Date: Mon, 06 Apr 2020 09:58:06 GMT
Connection: keep-alive

可以看到多了一个Access-Control-Allow-Origin这个字段,该字段表示被允许访问该资源的不同源,这里指定了*表示告诉浏览器任何源都可以访问该资源。

通过观察还可以发现请求头中有字段Origin正好对应的是就是http://dev.jd.com:9091web应用所在的源。

在这里插入图片描述

非简单请求

上面简单请求的条件只要有一个没有满足就会变成非简单请求。非简单请求会首先发送一个预检请求询问服务端是否允许跨域,服务端允许后才会发送真正的请求。

注:chrome的network面板中看不到预检请求,可以查看 chrome://flags/#out-of-blink-cors 配置,改成 disabled 后重启 Chrome ,或者换个浏览器Firefox是可以看到的,目前是74.0版本。

预检的请求头:

OPTIONS /corsget HTTP/1.1
Host: dev.jd.com:9090
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Origin: http://dev.jd.com:9091
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1
Accept: */*
Referer: http://dev.jd.com:9091/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

通过人为修改请求头的content-type为application/json将原本的简单请求变成了非简单请求,因为application/json并不在简单请求的三种content-type中。

这样就需要首先发送一个预检请求。

可以看到请求头中有如下字段:

  1. Access-Control-Request-Method: GET

    用于预检请求,将实际发送的请求的method告知服务器

  2. Access-Control-Request-Headers: content-type

    用于预检请求,将实际发送的请求头(不满足简单请求条件)告知给服务器

  3. Origin: http://dev.jd.com:9091

    告诉服务器请求源

预检响应头:

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: OPTIONS
Access-Control-Allow-Headers: Content-Type
Content-Type: text/html; charset=utf-8
Content-Length: 0
ETag: W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"
Date: Mon, 06 Apr 2020 10:57:40 GMT
Connection: keep-alive

可以看到如下字段:

  1. Access-Control-Allow-Origin: *

    告诉浏览器任何源都可访问该资源

  2. Access-Control-Allow-Methods: OPTIONS
    告诉浏览器options被允许跨域访问该资源。options并不在简单请求被允许的method中,但是预检请求确是options,所以需要指定options被允许。

  3. Access-Control-Allow-Headers: Content-Type

    告诉浏览器三个Content-Type值之外的Content-Type值被允许跨域访问该资源。因为application/json并不在简单请求的三个content-type中。

然后发送实际的get请求,请求头如下:

GET /corsget HTTP/1.1
Host: dev.jd.com:9090
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Origin: http://dev.jd.com:9091
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1
Content-Type: application/json
Accept: */*
Referer: http://dev.jd.com:9091/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

实际请求的响应头如下:

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
Content-Length: 16
ETag: W/"10-oV4hJxRVSENxc/wX8+mA4/Pe4tA"
Date: Mon, 06 Apr 2020 10:57:40 GMT
Connection: keep-alive

可以看到实际请求的响应头中并没有复杂的访问控制类型的HTTP头,只有一个Access-Control-Allow-Origin: *。在实际请求的响应头中这个字段是必须的,如果没有这个头部即使预检通过了,实际发送的请求还是会失败,返回的结果还是会被浏览器拦截,并不会返回给脚本,并报错。

在这里插入图片描述

跨域请求和凭证

Ajax和Fetch的跨域请求默认不会携带凭证。可以通过Ajax/Fetch的设置让发送请求的时候带上凭证,但是如果服务端并不允许携带凭证的跨域请求,那么理所当然的跨域请求会失败。

在这里插入图片描述
Ajax: xhr.withCredentials = true

Fetch: fetch(url, {credentials: 'include', mode: 'cors'})

当服务端设置 Access-Control-Allow-Credentials: true 允许跨域请求携带凭证的时候对于Access-Control-Allow-Origin还有一个限制,就是值不能为*。

在这里插入图片描述
那么我们就需要在后端设置上指定允许携带凭证跨域的源,如果有多个怎么办呢?因为Access-Control-Allow-Origin这个字段并不能设置多个值,可以通过代码获取请求头的Origin来判断是否允许获取该资源,允许的话将Origin值设置给响应的HTTP头字段Access-Control-Allow-Origin即可。
在这里插入图片描述
仔细观察就可以发现Access-Control-Allow-Credentials: true这个响应头在预检和实际请求的响应头中被返回了两次(一次都不能少,否则会报错,一样是跨域错误),但是之前设置的Access-Control-Allow-Headers: Content-Type只有在预检的时候才会被返回。

其实关于Access-Control的HTTP头还有一个是控制预检请求的缓存时间的,就是Access-Control-Max-Age单位是秒。

所以初步猜测Access-Control-Allow-Credentials这个响应头并不能被缓存?

问题:

脚本会发送跨域请求,Origin指向的是HTML所在源还是脚本所在源呢?

参考:

  1. HTTP访问控制(CORS)
  2. Server-Side Access Control
  3. Fetch

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362022.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[转]jQuery设计思想

转自:http://kb.cnblogs.com/page/109875/ jQuery是目前使用最广泛的javascript函数库。 据统计,全世界排名前100万的网站,有46%使用jQuery,远远超过其他库。微软公司甚至把jQuery作为他们的官方库。 对于网页开发者来说&#xff…

敏捷开发绩效管理之四:为团队设立外部绩效目标(目标管理,外向型绩效)...

这是敏捷开发绩效管理的第四篇。(之一,之二,之三,之四,之五,之六,之七)最近在看德鲁克的书,发现其中很明确地写着“企业的绩效只存在于外部,而企业内部只有成…

【K8S in Action】服务:让客户端发现pod 并与之通信(2)

一 通过Ingress暴露服务 Ingress (名词) 一一进入或进入的行为;进入的权利;进入的手段或地点;入口。一个重要的原因是每个 LoadBalancer 服务都需要自己的负载均衡器, 以及 独有的公有 IP 地址, 而 Ingres…

一个罐子统治一切:Apache TomEE + Shrinkwrap == JavaEE引导

警告:我不是Spring Boot的专家。 我发现很多事情对此非常有趣,并且当然可以真正改善您的日常工作。 而且,我对Spring Boot没有任何反对,也没有开发或使用它的人。 但是我认为社区高估了该产品。 一年前,我开始收到很多…

iview-admin框架运行步骤

第一步: 前往github下载整个iview-admin框架的全部源码 github地址: https://github.com/iview/iview-admin 第二步: 点击Clone or download绿色按钮。下载整个压缩包 第三步: 解压至D盘,在根目录中按 1、前往github下…

.net 垃圾回收学习[How To: Use CLR Profiler][翻译学习]【2】

http://msdn.microsoft.com/zh-cn/library/ms979205 注意:内容可能已经过期了。 注意:CLR Profiler最新版本:http://www.microsoft.com/download/en/details.aspx?id16273 Identifying Common Garbage Collection Issues 可以使用CLR Profil…

JavaOne 2014:会议与合同利益冲突

杜克街咖啡馆,工程师可以在街上进行走廊交谈 。 与签约不兼容 我的第11届JavaOne会议(2004年至2014年为11 10 1)非常出色。 值得参加此活动并结识社区中所有参与的人。 现在,这里是绅士的,但 。 除了经济上的明显优…

JQuery(三)-- AJAX的深入理解以及JQuery的使用

HTTP HTTP http: 超文本传输协议。特点: 简单、快速、灵活、无状态、无连接 URL: 统一资源定位符。 组成:协议名://主机IP:端口号/项目资源地址?传递参数的键值对#锚点 ①ip地址在同一个网段是唯一的。如果是在公…

JSF的工作方式和调试方式–可以使用polyglot吗?

JSF不是我们通常认为的那样。 这也是一个调试起来可能有些棘手的框架,尤其是在初次遇到时。 在这篇文章中,让我们继续探讨为什么会出现这种情况,并提供一些JSF调试技术。 我们将讨论以下主题: JSF不是我们经常想到的 JSF调试的难…

React组件实现越级传递属性

如果有这样一个结构:三级嵌套,分别是:一级父组件、二级子组件、三级孙子组件,且前者包含后者,结构如图: 如果把一个属性,比如color,从一级传递给三级,一般做法是使用prop…

尝试Office 2003 VSTO的开发、部署

背景:一年前,某项目需要使用到Excel进行数据录入,考虑到很多用户还是使用XPOffice 2003,所以开发的时候直接使用Excel 2003版本进行VBA开发。也许很多人都会说,Win10都出了,微软的Office都要免费了&#xf…

初级开发人员在编写单元测试时常犯的错误

自从我编写第一个单元测试以来已经有10年了。 从那时起,我不记得我已经编写了成千上万的单元测试。 老实说,我在源代码和测试代码之间没有任何区别。 对我来说是同一回事。 测试代码是源代码的一部分。 在过去的3-4年中,我与多个开发团队合作…

OpenDaylight开发hello-world项目之开发工具安装

OpenDaylight开发hello-world项目之开发环境搭建 OpenDaylight开发hello-world项目之开发工具安装 OpenDaylight开发hello-world项目之代码框架搭建 在ODL开发之前,要安装好开发环境。ODL使用java语言开发,所以要安装好java。ODL的代码框架是有maven这个…

Google Chrome 扩展程序开发

根据公司的规定,每月八小时,弹性工作制。所以大家平时来的不太准时,如果有事,下班也就早些回去了。所以一个月下来工作时间可能不够,但是公司的考勤日历是这样的: 除了请假和法定节假日外,其他样…

[Silverlight入门系列]使用MVVM模式(7):ViewModel的INotifyPropertyChanged接口实现

本文说说ViewModel的这个INotifyPropertyChanged接口可以用来做啥? 举例1:我有个TabControl,里面放了很多View,每个由ViewModel控制,我想是想TabSelectionChanged就打开相应的ViewModel,怎么做?…

[读书笔记]TCP/IP详解V1读书笔记-4 5

IP地址与以太网地址之间的关系 R P发送一份称作A R P请求的以太网数据帧给以太网上的每个主机。这个过程称作广播,在32 bit的I P地址和采用不同网络技术的硬件地址之间提供动态映射 ----------------------------------------- arp以太网帧的类型字段为x 0 8 0 6&am…

未来是Apache Karaf上的微服务架构

这是Jamie Goodyear的客座博客文章( 博客 , icbts )。 他是Savoir Technologies的开源倡导者,Apache开发人员和计算机系统分析师; 他为全球大型组织设计,批判和支持了体系结构。 他拥有纽芬兰纪念大学的计…

使用内存回流的方法来实现将image的内容转换为 byte[]

在今天的开发中老大不知道怎么突发奇想&#xff0c;要使用Image的Byte数据。当时使用老几种方式效果均不理想&#xff0c;最后发现其实可以使用内存回流的方式来实现。多的不说老&#xff0c;马上贴上代码&#xff1a;/**//// <summary> /// 将byte[]转换为Image…

通过设计国际象棋游戏来了解策略模式

今天&#xff0c;我们将借助一个示例来尝试了解策略模式。 我们将考虑的示例是国际象棋游戏。 这里的目的是解释策略模式&#xff0c;而不是构建全面的国际象棋游戏解决方案。 策略模式&#xff1a;策略模式被称为行为模式-用于管理对象之间的算法&#xff0c;关系和职责。 策…

群发邮件

最近&#xff0c;通过两周的学习&#xff0c;对.net 的基础知识有了进一步的了解。觉得自己可以写个小程序了。于是花了两天时间写了一个 群发邮件的一个WinForm小程序。自己在这里小秀一下&#xff0c;表扬及鼓励一下自己。哈哈&#xff01; 此小程序在发送邮件的基础上还添加…