安全系列之:跨域资源共享CORS_access-control-request-method-程序员宅基地

技术标签: CORS  CS揭秘  http  程序那些事  ajax  javascript  

简介

什么是跨域资源共享呢? 我们知道一个域是由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数据。

  1. 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信息自动发送变化。

  1. 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的值。

  1. Access-Control-Max-Age

Access-Control-Max-Age表示preflight request的请求结果将会被缓存多久,其格式如下:

Access-Control-Max-Age: <delta-seconds>

delta-seconds是以秒为单位。

  1. Access-Control-Allow-Credentials

这个字段用来表示服务器端是否接受客户端带有credentials字段的请求。如果用在preflight请求中,则表示后续的真实请求是否支持credentials,其格式如下:

Access-Control-Allow-Credentials: true
  1. Access-Control-Allow-Methods

这个字段表示访问资源允许的方法,主要用在preflight request中。其格式如下:

Access-Control-Allow-Methods: <method>[, <method>]*
  1. 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/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/superfjj/article/details/120260968

智能推荐

后端配置(宝塔):SSH终端设置_宝塔ssh密钥登录-程序员宅基地

本文介绍了Future接口的主要作用是用于异步处理任务,并列举了其中的方法。同时展示了运行结果,说明了futureTask.get()方法需要5秒才能返回结果,导致主线程被阻塞了5秒。

计算机考试金麦圈编号教程,计算机二级:数据处理-程序员宅基地

文章浏览阅读7.6k次。《计算机二级:数据处理》由会员分享,可在线阅读,更多相关《计算机二级:数据处理(10页珍藏版)》请在人人文库网上搜索。1、精品文库打开 Excelkt 文件夹下的 Excel14A.xlsx 工作簿文件,按下列要求操作。1、基本编辑 将Exceikt文件夹下的ScoreA.docx文件中的数据复制到Sheetl工作表A2单元格开始 处。 编辑 Sheet1 工作表A. 在最左端插入 1列,列宽10..._公式填充商品编号,商品名称有金麦圈

360路由器的虚拟服务器设置,360路由器无线万能中继设置教程图解-程序员宅基地

文章浏览阅读2.1k次。如果WiFi在很久之前就出现了,皇宫也准备安装路由器该怎么办?首先需要通过两个关卡的考验。第一关:【安全】惜命的皇上夸张到每道菜都要找专人试毒,面对有着无限可能,潜藏着DNS劫持和钓鱼网站等诸多陷阱的网络世界,安全自然是最为重要的。考虑到360安全路由有9大安全防护秘籍,过关!第二关:【信号覆盖】封建社会,为了凸显皇权的至高无上,必然会要求整个皇宫只能皇上自己使用主路由,而后妃等不能僭越。这就产生..._360路由器p4g无线中续

虎牙直播网页弹幕过滤小探索_虎牙弹幕脚本js-程序员宅基地

文章浏览阅读703次。虎牙直播网页弹幕过滤小探索没过滤前,一堆 333过滤后,舒服了js代码使用方法网页看直播时候,没发现有过滤弹幕的功能,自己摸索了一下。没过滤前,一堆 333过滤后,舒服了js代码 //过滤内容 var filterKeyWord= '333'; $("#danmudiv").unbind("DOMNodeInserted"); $("#chat-room__list").unbind("DOMNodeInserted"); /** * 清理弹幕 _虎牙弹幕脚本js

Java 自定义注解及注解读取解析--模拟框架生成SQL语句_* 1.读取类中的注解信息 *2.根据注解中包含的建表结构信息创建sql语句ja* 3.使用j-程序员宅基地

文章浏览阅读247次。Java 自定义注解及注解读取解析--模拟框架生成SQL语句假设们使用一张简单的表,结构如下:定义注解:表注解:package com.xzlf.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.la..._* 1.读取类中的注解信息 *2.根据注解中包含的建表结构信息创建sql语句ja* 3.使用j

GDKOI 2021 提高组 Day1 第三题 回文(manachar+ST表)_gdkoi2021 day1t3-程序员宅基地

文章浏览阅读175次。GDKOI 2021 提高组 Day1 第三题 回文题目大意给出长为nnn的串,和qqq组询问,每次询问区间中的最长回文串。n,q≤5∗105n,q\le5*10^5n,q≤5∗105题解可以先用manachar求出以每个位置为中心的回文串,询问时二分答案,然后在区间中判断是否存在长度为midmidmid的回文串,用ST表维护区间最值。注意二分判断时并非在整个区间[l,r][l,r][l,r]中找最大值,而需分别将左端点右移和右端点左移大约midmidmid(因奇偶而不同)的位置,以保证找_gdkoi2021 day1t3

随便推点

pythonTensor Flow预测数据_python tensorflow 预测下一组数据-程序员宅基地

文章浏览阅读789次。通过载入csv表格数据,创建数据模型,实现梯度递减,对数据进行预测#导入tensorflowimport tensorflow as tf#导入excel处理模块import pandas as pdimport numpy as nb#导入数据可视化模块 pyplot:图形绘制模块import matplotlib.pyplot as mpl数据载入data = pd.read_csv('yq.csv')x = nb.array(data.get('date'))y = nb.a_python tensorflow 预测下一组数据

使用a标签创建引入js中的方法_a标签导入外部js-程序员宅基地

文章浏览阅读704次。1. a href="javascript:js_method();" 这是平台上常用的方法,但是这种方法在传递this等参数的时候很容易出问题,而且javascript:协议作为a的href属性的时候不仅会导致不必要的触发window.onbeforeunload事件,在IE里面更会使gif动画图片停止播放。W3C标准不推荐在href里面执行javascript语句 2. a h_a标签导入外部js

小程序怎么自定义导航栏,导航栏放图片、设置高度_小程序自定义导航栏-程序员宅基地

文章浏览阅读1.1w次,点赞4次,收藏28次。这样就搞定了,希望你们的logo比我的好看。今天来说一下小程序的自定义导航栏。_小程序自定义导航栏

Introduction to Robotics 总结1~6-程序员宅基地

文章浏览阅读6.8k次,点赞4次,收藏32次。机器人学中经典教材 《Introduction to Robotics: Mechanics and Control》,也就是John Craig的中文版《机器人学导论》,刚来实验室的时候,就发现师兄们人手一本了,某些章节自己啃也是有点难度的,之前在 Youtube上看完了斯坦福 Oussama Khatib 教授的视频Introduction to Robotics,他们上课使用的教材就是这本,一共十六篇lecture,讲解也是很通俗易懂,涵盖了机器人坐标变化、D-H参数建模、动力学、运动学、PD、PI._introduction to robotics

【HDU3038】How Many Answers Are Wrong(带权并查集)_tt and ff are ... friends. uh... very very good fr-程序员宅基地

文章浏览阅读146次。题目链接 How Many Answers Are Wrong Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 15260 Accepted Submission(s): 5361 Proble..._tt and ff are ... friends. uh... very very good friends -________-b ff is a

库文件整理-程序员宅基地

文章浏览阅读110次。纸上得来终觉浅,绝知此事要躬行。C系统提供了丰富的系统文件,称为库文件。C的库文件分为两类,一类是扩展名为‘.h’的文件,称为头文件。在该类文件中包含了常量的定义、类型定义、宏定义、函数原型及各种编译选择设置等信息。另一类是库函数,包含了各种函数的目标代码,供用户在程序中调用。通常在程序中调用一个库函数时,要在调用之前包含该函数原型所在的.h文件。alloc.h 内存管理..._库文件整理

推荐文章

热门文章

相关标签