常用跨域方法总结(2)——CORS
上篇文章介绍了几种常用的跨域方法:常用跨域方法总结,本片为上一篇的补充,对比较重要的Cross Origin Resource Sharing
详细介绍。
CORS
出于安全原因,从脚本内发起的跨源HTTP请求会受到一定限制。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非使用CORS头文件。跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
若要利用CORS来进行跨域获取资源,还需要服务端的配合。
这里分为两种场景:简单请求和非简单请求
简单请求
什么样的请求才属于简单请求呢,让我们先来看MDN的一段定义
-
必须使用下列方法中的一种:
- GET
- HEAD
- POST
-
请求首部字段不能超出以下几种
- Accept
- Accept-Language
- Content-Language
- Content-Type (需要注意额外的限制)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
-
Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
- 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
- 请求中没有使用 ReadableStream 对象。
同时满足以上5种条件,则可以视为简单请求。
先跑例子吧。
首先上服务端代码:
var http = require('http');
http.createServer(function (req, res) {
res.setHeader("Access-Control-Allow-Origin","*");
res.end(JSON.stringify({'success':true,msg:'今天,我就是要用cors来跨域'+Math.random()}));
}).listen(8080)
很简单有没有,Access-Control-Allow-Origin
:限制发起跨域请求的来源,*表示不限制
(请求首部字段下文详细介绍)
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest()
xhr.open('get', 'http://localhost:8080')
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
console.log(JSON.parse(xhr.responseText))
}
}
xhr.send()
</script>
</body>
</html>
结果肯定是请求成功啦
非简单请求
简单来说吧,不符合简单请求的都是非简单请求(怎么感觉这么大白话呢- -)详见CORS
与前述简单请求不同,“非简单请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
还是先上代码
服务端:
var http = require('http');
http.createServer(function (req, res) {
res.setHeader("Access-Control-Allow-Origin","*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
res.setHeader("Access-Control-Allow-Methods","PUT,GET,POST,DELETE,OPTIONS");
res.end(JSON.stringify({'success':true,msg:'今天,我就是要用cors来跨域'+Math.random()}));
}).listen(8080)
console.log('正在监听8080')
Access-Control-Allow-Headers
::预检请求响应的首部字段定义了实际请求中允许携带的额外的首部字段。Access-Control-Allow-Methods
:预检请求响应的首部字段定义了实际请求所允许使用的 HTTP 方法。(笔者做实验的遇到了一个小插曲,simple method会不受此限制,详见为什么 Access-Control-Allow-Methods 不起作用?)
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest()
xhr.open('get', 'http://localhost:8080')
xhr.setRequestHeader('Content-type','text/html') // 添加了非简单请求的Content-Type
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
console.log(JSON.parse(xhr.responseText))
}
}
xhr.send()
</script>
</body>
</html>
结果肯定也是请求成功啦。
附带身份凭证的请求
这里有一点要注意的地方,这里还是说一下吧
一般而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。
也就是前端请求的时候:xhr.withCredentials = true
对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“”。若请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“”,请求将会失败。
请求与响应首部字段总结
这块内容MDN已经很详细了,为了方便阅读,笔者还是整理过来了。
HTTP 请求首部字段
请注意,这些首部字段无须手动设置。 当开发者使用 XMLHttpRequest 对象发起跨域请求时,它们已经被设置就绪。Origin
首部字段表明预检请求或实际请求的源站。(注意,不管是否为跨域请求,ORIGIN 字段总是被发送。)Access-Control-Request-Method
,用于预检请求,表示实际请求的方法Access-Control-Request-Headers
,用于预检请求,表示实际请求中添加的额外的首部字段
HTTP 请求响应字段
Access-Control-Allow-Method
,预检请求的响应,表示允许的接下来的实际请求的方法。(笔者做实验的遇到了一个小插曲,simple method会不受此限制,详见为什么 Access-Control-Allow-Methods 不起作用?)Access-Control-Allow-Origin
,指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。Access-Control-Allow-Credentials
,当浏览器(比如xhr)的credentials设置为true时是否允许浏览器读取response的内容。当用在对preflight预检测请求的响应中时,它指定了实际的请求是否可以使用credentials(如果请求credentials为true时,该响应首部字段需要设置为true)。Access-Control-Allow-Headers
,预检请求的响应,表示允许的接下来的实际请求的额外的首部字段。预检请求的响应。其指明了实际请求中允许携带的首部字段。Access-Control-Expose-Headers
,在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。Access-Control-Max-Age
:指定了预检请求的结果能够被缓存多久
最后
到这里笔者对跨域算是比较熟悉了,感谢各位的阅读,如有不对的地方,欢迎大家批评指正。
还有,最好是对每个例子都有实际运行理解更深刻哦。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。