2.跨域资源共享(CORS)
跨域简介
当访问一个资源文件时,如果从非该资源文件所在的服务器不同域名或者端口处进行访问时,该资源会发起一个跨域请求。
例如,网站A的地址是http://www.domain-a.com ,该网站中HTML页面通过 img
标签中的 src
属性请求http://www.domain-b.com/static/xxx.png 图片资源,此时,就发生了跨域请求。
处于安全考虑,浏览器限制从脚本内发起的跨域资源请求。例如,XHR和Fetch的API遵循同源策略。这意味着使用这些API只能从同一个域才能请求资源文件。
需要注意的是,跨域不一定是浏览器限制了发起跨域请求,也可能是跨域请求可以正常发起,但是返回的数据被浏览器截获。
最好的例子就是CSRF跨站攻击,有些浏览器不允许从HTTPS的网站跨域访问HTTP, 比如Chorme 和 Firefox,这些浏览器在请求还未发出时就会拦截请求。
CORS 机制允许Web 服务器对跨域访问进控制,从而保证跨域数据传输安全进行。目前浏览器都支持该机制,不过需要跨域访问需要客户端和服务端同时支持。
服务端支持CORS
浏览器会针对XHR和Fetch中发起的跨域请求发送特殊的HTTP header 请求头信息。并且服务端也必须返回允许使用特定的跨域响应的header 头信息。
跨域访问常见场景
- XHR 或者 Fetch 发起的跨域HTTP请求
- Web 字体(通过CSS 中的@font-face使用跨域字体)
- 使用
drawImage
将image/video
画面绘制到canvas - script
跨域资源共享(CROS)
CORS标准新增了一组HTTP首部字段,允许服务器声明哪些请求源有权限访问哪些资源。另外,规范要求,那些可能导致服务器产生隐患的HTTP请求方法(除GET之外的请求),浏览器必须首先使用OPTIONS
方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务端返回允许响应后,才发起实际的HTTP请求。在预检请求的返回中,服务端也可以通知客户端,是否需要携带身份凭证(cookies和HTTP认证相关数据)。
跨域共享实践
这里,我们通过XHR对象介绍三个跨域资源共享的实例。
1. 简单请求
简单请求的特征:
- 不会触发预检请求
- 使用以下方法之一
- GET
- HEAD
- POST
Fetch 规范定义,不得自定义下面header之外的其他首部字段
- Accept
- Accept-Language
- Content-Language
- Conetent-Type
- Viewport-width
Content-Type的值为下列之一
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
请求中任意
XMLHttpRequestUpload
对象均没有注册任何事件监听器- 请求中没有使用
ReadableStream
对象
客户端和服务端之间使用CORS首部字段处理跨域权限:
查看一个跨域请求实例:
服务端返回的 Access-Control-Allow-Origin: *
表明该资源可以被任意域名访问,不收跨域限制。
2.预检请求
预检请求要求必须首先使用OPTIONS
方法发起一个预检请求到服务端,来获知服务端是否允许该实际请求。
预检请求的满足条件:
- 使用以下任意一种HTTP方法
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
- 人为设置以下集合之外的首部字段。该集合为:
- Accept
- Accept-Language
- Content-Language
- Conetent-Type
- Viewport-width
- Content-Type 的值不属于以下之一:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- 请求中任意
XMLHttpRequestUpload
对象注册多个事件监听器 - 请求中使用了
ReadableStream
对象
OPTIONS 请求
POST 请求
执行过程:
浏览器首先检查到,JS发起的请求需要被预检。Request Method:OPTIONS
表示这个是一个预检请求,该方法不会对服务器资源产生影响,预检请求中同时携带了下面两个首部字段:
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
首部字段Access-Control-Request-Method
通知服务端,实际请求将使用POST
方法。
首部字段Access-Control-Request-Headers
通知服务端,实际请求将携带Content-Type
字段。
服务端根据预检头信息,判断实际请求是否被允许。
附带凭证的请求
通常情况下,浏览器发送跨域请求时不会发送身份凭证信息,如果要发送凭证信息的话,就要在对XHR对象设置特殊的标志位。
本例中,我们对XHR对象的withCredentials
标置设置为true,从而可以向跨域的服务器发送Cookies信息。
附带身份凭证的请求不得设置通配符
对于带凭证的请求,服务端不可将Access-Control-Allow-Origin
值设置为“*”。
这是因为请求的首部中携带Cookie信息,如果设置为“*”,请求将会失败。将Access-Control-Allow-Origin
值设置为请求方的host,则请求执行成功。
HTTP跨域请求首部字段
1.Origin
Origin
表明了首部字段预检请求或实际请求的源端。
Origin: <origin>
2. Access-Control-Request-Method
Access-Control-Request-Method
首部字段用于预检请求。作用是通知实际请求使用的HTTP方法。
Access-Control-Request-Method:<method>
3.Access-Control-Request-Headers
Access-Control-Request-Headers
收不自动用于预检请求。做用户通知实际请求中携带的首部字段通知给服务端。
Access-Control-Request-Headers:<field-name>
HTTP跨域响应首部字段
1.Access-Control-Allow-Origin
Access-Control-Allow-Origin: <origin> | *
orgin参数允许指定外域URI访问指定资源。对于不需要携带身份凭证的请求,服务端可以指定该字符为通配符,它允许来自所有域的访问。
如果,允许接收指定一个具体的域请求时
Access-Control-Allow-Origin: http://google.com
服务端指定的时一个具体的域而不是“*”,那么响应的首部中Vary
字段值必须包含 Origin 。这将告诉客户端:服务器对不同源站返回不同的内容。
2.Access-Control-Expose-Headers
服务端将浏览器允许访问的头放入白名单,例如:
Access-Control-Expose-Headers: x-header
这样,浏览器就可以根据服务端的响应头白名单进行访问。
3.Access-Control-Max-Age
指明该预检请求可以被缓存多久。
4.Access-Control-Allow-Credentials
当浏览器的credentials
设置为true时,是否允许浏览器读取response内容。
Access-Control-Allow-Credentials: true
5.Access-Control-Allow-Methods
指明浏览器实际允许使用的HTTP方法。
Access-Control-Allow-Methods: <methods>
6. Access-Control-Allow-Headers
指明浏览器实际允许携带的首部字段。
Access-Control-Allow-Headers: <filed-name>