安全系列之:跨域资源共享CORS
简介
什么是跨域资源共享呢? 我们知道一个域是由scheme、domain和port三部分来组成的,这三个部分可以唯一标记一个域,或者一个服务器请求的地址。跨域资源共享的意思就是服务器允许其他的域来访问它自己域的资源。
CORS是一个基于HTTP-header检测的机制,本文将会详细对其进行说明。
CORS举例
为了安全起见,一般一个域发起的请求只能获取该域自己的资源,因为域资源内部的互相调用被认为是安全的。
但是随着现代浏览器技术和ajax技术的发展,渐渐的出现了从javascript中去请求其他域资源的需求,我们把这样的需求叫做跨域请求。
比如说客户端从域http://www.flydean.com向域http://www.abc.com/data.json请求数据。
那么客户端是怎么知道服务器是否支持CORS的呢?
这里会使用到一个叫做preflight的请求,这个请求只是向服务器确认是否支持要访问资源的跨域请求,当客户端得到响应之后,才会真正的去请求服务器中的跨域资源。
虽然是客户端去设置HTTP请求的header来进行CORS请求,但是服务端也需要进行一些设置来保证能够响应客户端的请求。所以本文同时适合前端开发者和后端开发者。
CORS protocol
没错,任意一种请求要想标准化,那么必须制定标准的协议,CORS也一样,CORS protocol主要定义了HTTP中的请求头和响应头。我们分别来详细了解。
HTTP request headers
首先是HTTP的请求头。请求头是客户端请求资源时所带的数据。CORS请求头主要包含三部分。
第一部分是Origin,表示发起跨域资源请求的request或者preflight request源:
Origin: <origin>
Origin只包含server name信息,并不包含任何PATH信息。
注意,Origin的值可能为null
第二部分是Access-Control-Request-Method,这是一个preflight request,告诉服务器下一次真正会使用的HTTP资源请求方法:
Access-Control-Request-Method: <method>
第三部分是Access-Control-Request-Headers,同样也是一个preflight request,告诉服务器下一次真正使用的HTTP请求中要带的header数据。header中的数据是和server端的Access-Control-Allow-Headers相对应的。
Access-Control-Request-Headers: <field-name>[, <field-name>]*
HTTP response headers
有了客户端的请求,还需要服务器端的响应,我们看下服务器端都需要设置那些HTTP header数据。
- Access-Control-Allow-Origin
Access-Control-Allow-Origin表示服务器允许的CORS的域,可以指定特定的域,也可以使用*表示接收所有的域。
Access-Control-Allow-Origin: <origin> | *
要注意的是,如果请求带有认证信息,则不能使用*。
我们看一个例子:
Access-Control-Allow-Origin: http://www.flydean.com
Vary: Origin
上面例子表示服务器允许接收来自http://www.flydean.com的请求,这里指定了具体的某一个域,而不是使用*。因为服务器端可以设置一个允许的域列表,所以这里返回的只是其中的一个域地址,所以还需要在下面加上一个Vary:Origin头信息,表示Access-Control-Allow-Origin会随客户端请求头中的Origin信息自动发送变化。
- Access-Control-Expose-Headers
Access-Control-Expose-Headers表示服务器端允许客户端或者CORS资源的同时能够访问到的header信息。其格式如下:
Access-Control-Expose-Headers: <header-name>[, <header-name>]*
例如:
Access-Control-Expose-Headers: Custom-Header1, Custom-Header2
上面的例子将向客户端暴露Custom-Header1, Custom-Header2两个header,客户端可以获取到这两个header的值。
- Access-Control-Max-Age
Access-Control-Max-Age表示preflight request的请求结果将会被缓存多久,其格式如下:
Access-Control-Max-Age: <delta-seconds>
delta-seconds是以秒为单位。
- Access-Control-Allow-Credentials
这个字段用来表示服务器端是否接受客户端带有credentials字段的请求。如果用在preflight请求中,则表示后续的真实请求是否支持credentials,其格式如下:
Access-Control-Allow-Credentials: true
- Access-Control-Allow-Methods
这个字段表示访问资源允许的方法,主要用在preflight request中。其格式如下:
Access-Control-Allow-Methods: <method>[, <method>]*
- Access-Control-Allow-Headers
用在preflight request中,表示真正能够被用来做请求的header字段,其格式如下:
Access-Control-Allow-Headers: <header-name>[, <header-name>]*
有了CORS协议的基本概念之后,我们就可以开始使用CORS来构建跨域资源访问了。
基本CORS
先来看一个最基本的CORS请求,比如现在我们的网站是http://www.flydean.com,在该网站中的某个页面中,我们希望获取到https://google.com/data/dataA,那么我们可以编写的JS代码如下:
const xhr = new XMLHttpRequest();
const url = 'https://google.com/data/dataA';
xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();
该请求是一个最基本的CORS请求,我们看下客户端发送的请求包含哪些数据:
GET /data/dataA HTTP/1.1
Host: google.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://www.flydean.com
这个请求跟CORS有关的就是Origin,表示请求的源域是http://www.flydean.com。
可能的返回结果如下:
HTTP/1.1 200 OK
Date: Mon, 01 May 2021 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[…Data…]
上面的返回结果要注意的是Access-Control-Allow-Origin: *,表示服务器允许所有的Origin请求。
Preflighted requests
上面的例子是一个最基本的请求,客户端直接向服务器端请求资源。接下来我们看一个Preflighted requests的例子,Preflighted requests的请求分两部分,第一部分是请求判断,第二部分才是真正的请求。
注意,GET请求是不会发送preflighted的。
什么时候会发送Preflighted requests呢?
当客户端发送OPTIONS方法给服务器的时候,为了安全起见,因为服务器并不一定能够接受这些OPTIONS的方法,所以客户端需要首先发送一个
preflighted requests,等待服务器响应,等服务器确认之后,再发送真实的请求。我们举一个例子。
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://google.com/data/dataA');flydean
xhr.setRequestHeader('cust-head', 'www.flydean.com');
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.onreadystatechange = handler;
xhr.send('<site>www.flydean.com</site>');
上例中,我们向服务器端发送了一个POST请求,在这个请求中我们添加了一个自定义的header:cust-head。因为这个header并不是HTTP1.1中标准的header,所以需要发送一个Preflighted requests先。
OPTIONS /data/dataA HTTP/1.1
Host: google.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://www.flydean.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: cust-head, Content-Type
请求中添加了Access-Control-Request-Method和Access-Control-Request-Headers这两个多出来的字段。
得到的服务器响应如下:
HTTP/1.1 204 No Content
Date: Mon, 01 May 2021 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: http://www.flydean.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: cust-head, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
响应中返回了Access-Control-Allow-Origin,Access-Control-Allow-Methods和Access-Control-Allow-Headers。
当客户端收到服务器的响应之后,发现配后续的请求,就可以继续发送真实的请求了:
POST /data/dataA HTTP/1.1
Host: google.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
cust-head: www.flydean.com
Content-Type: text/xml; charset=UTF-8
Referer: http://www.flydean.com/index.html
Content-Length: 55
Origin: http://www.flydean.com
Pragma: no-cache
Cache-Control: no-cache
<site>www.flydean.com</site>
在真实的请求中,我们不需要再发送Access-Control-Request*头标记了,只需要发送真实的请求数据即可。
最后,我们得到server端的响应:
HTTP/1.1 200 OK
Date: Mon, 01 May 2021 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: http://www.flydean.com
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some data]
带认证的请求
有时候,我们需要访问的资源需要带认证信息,这些认证信息是通过HTTP cookies来进行传输的,但是对于浏览器来说,默认情况下是不会进行认证的。要想进行认证,必须设置特定的标记:
const invocation = new XMLHttpRequest();
const url = 'https://google.com/data/dataA';
function corscall() {
if (invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
}
上面的例子中,我们设置了withCredentials flag,表示这是一个带认证的请求。
其对应的请求如下:
GET data/dataA HTTP/1.1
Host: google.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Referer: http://www.flydean.com/index.html
Origin: http://www.flydean.com
Cookie: name=flydean
请求中我们带上了Cookie,服务器对应的响应如下:
HTTP/1.1 200 OK
Date: Mon, 01 May 2021 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: http://www.flydean.com
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: name=flydean; expires=Wed, 31-May-2021 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
[text/plain payload]
服务器返回了Access-Control-Allow-Credentials: true,表示服务器接收credentials认证,并且返回了Set-Cookie选项对客户端的cookie进行更新。
要注意的是如果服务器支持credentials,那么返回的Access-Control-Allow-Origin,Access-Control-Allow-Headers和Access-Control-Allow-Methods的值都不能是*。
总结
本文简单介绍了HTTP协议中的CORS协议,要注意的是CORS实际上是HTTP请求头和响应头之间的交互。
本文已收录于 http://www.flydean.com/cors/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
安全系列之:跨域资源共享CORS的更多相关文章
- HTTP系列之跨域资源共享机制(CORS)介绍
前言 本文将继续解析详解HTTP系列1中的请求/ 响应报文的首部字段,今天带来的跨域资源共享(CORS)机制,具体内容包括CORS的原理.流程.实战,希望能给大家带来收获! CORS简介 跨域资源共享 ...
- 跨域资源共享(CORS)问题解决方案
CORS:Cross-Origin Resource Sharing(跨域资源共享) CORS被浏览器支持的版本情况如下:Chrome 3+.IE 8+.Firefox 3.5+.Opera 12+. ...
- 跨域资源共享CORS与JSONP
同源策略限制: 同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果没有同源策略,攻击者可以通过JavaScript获取你的邮件以及其他敏感信息,比如说 ...
- 跨域解决方案 - 跨域资源共享cors
目录 1. cors 介绍 2. 原理 3. cors 解决跨域 4. 自定义HTTP 头部字段解决跨域 5. 代码演示 5. 参考链接 1. cors 介绍 cors 说的是一个机制,其实相当于一个 ...
- VUE SpringCloud 跨域资源共享 CORS 详解
VUE SpringCloud 跨域资源共享 CORS 详解 作者: 张艳涛 日期: 2020年7月28日 本篇文章主要参考:阮一峰的网络日志 » 首页 » 档案 --跨域资源共享 CORS 详解 ...
- 网络编程-跨域资源共享 CORS
目录 1.什么是同源策略? 2.跨域资源共享 CORS 3.预检请求 4.CORS相关字段 5.Golang实现跨域 6.参考资料 1.什么是同源策略? 如果两个 URL 的 protocol.por ...
- 跨域资源共享 CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing). 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从 ...
- 跨域资源共享 CORS 详解
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing). 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从 ...
- 使Web Api 支持跨域资源共享(CORS)
Reference:http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api Imp ...
随机推荐
- Flutter学习(7)——网络请求框架Dio简单使用
原文地址: Flutter学习(7)--网络请求框架Dio简单使用 | Stars-One的杂货小窝 Flutter系列学习之前都是在个人博客发布,感兴趣可以过去看看 网络请求一般APP都是需要的,在 ...
- HCNA Routing&Switching之STP基础
前文我们了解了VLAN动态注册协议GVRP相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15113770.html:今天我们来讨论下二层环路和STP相 ...
- 来看看是什么原因导致生产服上的系统CPU高的?
我们可能会遇到生产服务器CPU很高的问题,有时候能确定是哪个进程,但是不知道这个进程都在干什么,所以也无从下手,无法解决问题.只能不断的重启,重启等. 最近也看了[一线码农]的一些教程,觉得都很不错, ...
- 从事 Android应用开发4年有余,现在工资7500。很不爽!怎么办?
最近到某论坛看到一个帖子: 坐标北京,在一个公司从事android应用开发4年有余(毕业至今没换过公司).公司利润越来越大,工资却每年长1000,如今才到7500.琢磨着换工作,又不想扔下这四年来逐步 ...
- 812考试总结(NOIP模拟37)[数列·数对·最小距离·真相]
前言 考得挺憋屈的... 先是搞了两个半小时的 T1 后来发现假了,又没多想跳了.. 然后一看 T2 这不是队长快跑嘛... 先是根据自己的想法打了一遍(考完之后发现是对的..) 然后回想了一下之前的 ...
- IDEA中Maven的使用初探
Maven Maven官网:https://maven.apache.org/ Apache Maven 是一个软件项目管理和理解工具.基于项目对象模型 (POM) 的概念,Maven 可以从一条中央 ...
- kubernetes/k8s CNI分析-容器网络接口分析
关联博客:kubernetes/k8s CSI分析-容器存储接口分析 kubernetes/k8s CRI分析-容器运行时接口分析 概述 kubernetes的设计初衷是支持可插拔架构,从而利于扩展k ...
- Docker创建Docker-Registry客户端docker-registry-frontend
docker-compose.yml version: '3.1' services: frontend: image: konradkleine/docker-registry-frontend:v ...
- 数据结构与算法——链表 Linked List(单链表、双向链表、单向环形链表-Josephu 问题)
链表是有序的列表,但是在内存中存储图下图所示 链表是以 节点 的方式来存储,是 链式存储 每个节点包含 data 域.next 域,指向下一个节点 链表的各个节点 不一定是连续存储,如上图所示 链表还 ...
- C++11 weak_ptr智能指针
和 shared_ptr.unique_ptr 类型指针一样,weak_ptr 智能指针也是以模板类的方式实现的.weak_ptr<T>( T 为指针所指数据的类型)定义在<memo ...