您所在的位置:首页 / 知识分享

关于跨域问题

2018.12.17

2518

首先,我们要了解什么是跨域请求。简单来说,当一台服务器资源从另一台服务器(不同的域名或者端口)请求一个资源时,就会发起一个跨域 HTTP 请求

一、什么是跨域请求
首先,我们要了解什么是跨域请求。简单来说,当一台服务器资源从另一台服务器(不同的域名或者端口)请求一个资源时,就会发起一个跨域 HTTP 请求。

举个简单的例子,http://example-a.com/index.html 这个 HTML 页面请求了 http://example-b.com/resource/image.jpg 这个图片资源时(发起 Ajax 请求,非 标签),就是发起了一个跨域请求。

在不做任何处理的情况下,这个跨域请求是无法被成功请求的,因为浏览器基于同源策略会对跨域请求做一定的限制。


二、浏览器同源策略
这就引出了浏览器的同源策略(Same-origin policy),同源策略限制了从同一个源加载的文档或者脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

什么是同源?同源需要同时满足三个条件:

请求的协议相同(例如同为 http 协议)
请求的域名相同(例如同为 www.example.com)
请求的端口相同(例如同为 80 端口)
第 2 点需要注意的是,必须是域名完全相同,比如说 blog.example.com 和 mail.example.com 这两个域名,虽然它们的顶级域名和二级域名(均为 example.com)都相同,但是三级域名(blog 和 mail)不相同,所以也不能算作域名相同。

如果不同时满足这上面三个条件,那就不符合浏览器的同源策略。

当然,也不是所有的交互都会被同源策略拦截下来,下面两种交互就不会触发同源策略:

1 跨域写操作(Cross-origin writes),例如超链接、重定向以及表单的提交操作,特定少数的 HTTP 请求需要添加预检请求(preflight);
2 跨域资源嵌入(Cross-origin embedding):
标签嵌入的跨域脚本;
标签嵌入的 CSS 文件;
标签嵌入图片;
和标签嵌入多媒体资源;
,的插件;
< object>, < embed>, < applet> 的插件;

@font-face 引入的字体,一些浏览器允许跨域字体(cross-origin fonts),一些需要同源字体(same-origin fonts);
< frame> 和 < iframe> 载入的任何资源,站点可以使用 X-Frame-Options 消息头来组织这种形式的跨域交互。

三、CORS(Cross-origin resource sharing,跨域资源共享)
虽然同源策略一定程度上保证了安全性,但是如果是一个正常的请求需要跨域该怎么做呢?

常见的方法有四种:

JSONP
<  iframe> 标签
CORS(Cross-origin resource sharing,跨域资源共享)
代理服务器
前两种方式本质上是利用浏览器同源策略的漏洞来进行跨域请求,不是推荐的做法,只能作为低版本浏览器的缓兵之计。

代理服务器的做法是让浏览器访问同源服务器,再由同源服务器去访问目标服务器,这样虽然可以避免跨域请求的问题,但是原本只需要一次的请求被请求了两次,无疑增加了时间的开销。

目前主流的方法是使用 CORS 的方式,这也是下面主要讲的内容。

3.1 什么是 CORS
CORS 其实是浏览器制定的一个规范,它的实现则主要在服务端,它通过一些 HTTP Header 来限制可以访问的域,例如页面 A 需要访问 B 服务器上的数据,如果 B 服务器上声明了允许 A 的域名访问,那么从 A 到 B 的跨域请求就可以完成。

对于那些会对服务器数据产生副作用的 HTTP 请求,浏览器会使用 OPTIONS 方法发起一个预检请求(preflight request),从而可以获知服务器端是否允许该跨域请求,服务器端确认允许后,才会发起实际的请求。在预检请求的返回中,服务器端也可以告知客户端是否需要身份认证信息。
3.2 简单请求(Simple requests)
某些请求不会触发 CORS 预检请求,我们称这样的请求为简单请求。

若请求满足下面所有条件,则该请求可视为简单请求:

GET, HEAD, POST 方法之一;
Header 仅有以下字段:
Accept
Accept-Language
Content-Language
Content-Type 为下面三者之一:
text / plain`
multipart / form-data
application / x-www.form-urlencoded
DPR
Downloadlink
Save-Data
Viewport-Width
Width
请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器,XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问;
请求中没有使用 ReadableStream 对象。

3.3 预检请求(Preflight Request)
和简单请求不同,「需预检的请求」要求必须先使用 OPTIONS 方法发送一个预检请求到服务器,以获知服务器是否允许该请求,或者是否需要携带身份认证信息。「预检请求」的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

当一个请求满足所有以下条件时,该请求需要首先发送预检请求。

使用了下面任一 HTTP 方法:PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH;
Header 中设置了除简单请求 Header 字段外的其他字段(见简单请求中的 Header 字段说明);
Content-Type 的值不属于下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
请求中的 XMLHttpRequestUpload 对象注册了任意多个事件监听器;
请求中使用了 ReadableStream 对象。

3.4 附带身份认证的请求
一般而言,对于跨域 XMLHTTPRequest 或者 Fetch 请求,浏览器不会发送身份凭证信息,如果需要发送身份凭证信息,需要把 XMLHTTPRequest 的 withCredentials 属性设置为 true。
通过把 withCredentials 设置为 true,从而向服务器发送一个携带 Cookies 的请求。因为这是一个简单的 GET 请求,所以浏览器不会发起预检请求,但是,服务端的响应中如果未携带 Access-Control-Allow-Credentials: true ,浏览器不会把响应内容返回给请求的发送者。

对于携带身份认证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为 *。

3.5 用于 CORS 的 Headers
下面列出所有用于 HTTP 请求和响应中的 Header 字段,具体的使用请查阅相关文档。

HTTP 请求 Headers:

Origin:表明预检请求或实际请求的源站,它不包含任何路径信息,只是服务器名称(URI);
Access-Control-Request-Method:用于预检请求,作用是将实际请求所使用 HTTP 方法告诉服务器;
Access-Control-Request-Headers:用于预检请求,作用是将实际请求所使用的 Header 字段告诉服务器;
HTTP 响应 Headers:

Access-Control-Allow-Origin:指定了允许访问该资源的外域 URI;
Access-Control-Expose-Headers:让服务器把允许浏览器访问的头放入白名单,这样浏览器就能使用 getResponseHeader 方法来访问了;
Access-Control-Max-Age:指定了预检请求的结果能够被缓存多久;
Access-Control-Allow-Credentials:指定了当浏览器的credentials设置为 true 时是否允许浏览器读取 response 的内容;
Access-Control-Allow-Headers:用于预检请求的响应。其指明了实际请求中允许携带的首部字段。

相关新闻

PHP如何读取一个大文件txt

2018.12.06

2944

文件已经一次性全部被载入到内存中并将文件的每一行保存到了一个php数组中,一次性载入这个202MB的文件file函数用了0.67秒钟、file_get_contents函数用了0.25秒钟(看起来file_get_content要比file靠谱的多