前端面试跨域问题小记

前端面试的问题中细分一下类别还是不少,今天主要对跨域这一块做一个整理、小记。

跨域也是实战中相当相当常见的问题,面试一般是考概念和解决方法。

总结下来有这么一些东西:

  • 什么是跨域问题?
  • 浏览器的同源策略是什么?
  • JSONP解决跨域
  • CORS解决跨域
  • 反代解决跨域

什么是跨域问题

跨域问题说得简单一些是我们在站点A的页面去请求站点B的API时,出于浏览器安全策略的问题,我们没有办法对API进行跨域调用。此举主要是避免加载到一些其他地方的恶意资源,但也对开发带来了跨域这个问题。

严格来说并不一定只有网页和接口域名不同的时候才算跨域,端口不同也算跨域。

跨域问题主要是出现在AJAX调用接口上,使用HTML标签加载资源一般是没有限制的。

什么是同源策略

同源的定义(来自MDN):

  • 如果两个页面的协议,端口(如果有指定)和主机都相同,则两个页面具有相同的源。我们也可以把它称为“协议/主机/端口 tuple”,或简单地叫做“tuple". ("tuple" ,“元”,是指一些事物组合在一起形成一个整体,比如(1,2)叫二元,(1,2,3)叫三元)

需要注意的是,IE的同源策略没有考虑端口号,而且存在授信范围,比如公司域名,这是在同源策略限制外的。

当页面要跨域加载资源的时候,请求会受到如下约束(来源MDN):

  • 通常允许跨域写操作(Cross-origin writes)。例如链接(links),重定向以及表单提交。特定少数的HTTP请求需要添加 preflight。
  • 通常允许跨域资源嵌入(Cross-origin embedding)。之后下面会举例说明。
  • 通常不允许跨域读操作(Cross-origin reads)。但常可以通过内嵌资源来巧妙的进行读取访问。例如可以读取嵌入图片的高度和宽度,调用内嵌脚本的方法,或availability of an embedded resource.

简单来说,同源策略就是浏览器默认只会允许页面从同源的路径加载资源,对于跨域加载路径资源会有限制。

JSONP跨域

最常见的跨域解决方案,对于jQuery在请求的时候把dataType从“json”改成“jsonp”就可以向目标URL发送JSONP请求。

JSONP在跨域的时候只支持GET请求,不支持POST,但是它兼容性好而且使用简单,同时可以兼容IE。

JSONP实际上是利用<script>标签跨域加载资源不受限制的“特性”(当然也可以说是漏洞)来实现用GET请求加载资源,在页面内<script>标签的src会指向一个不同源的API,该API会返回一个JSON格式的数据,但是这个数据是经过包装的,实际上是一行JS。

一般来说返回的内容是调用一个callback,而这个callback会被预先定义在页面内,被这个加载的JS调用。如果是使用jQuery,这个callback就相当于success函数。

JSONP发送的请求并不是XMLHTTPRequest,而是相当于正常加载一个JS脚本。

CORS跨域

现在的跨域问题解决方案,对于XMLHttpRequest或者Fetch发起的请求,服务器可以添加一个请求头 —— “Access-Control-Allow-Origin”来告诉浏览器这个API允许被什么外域调用。

如果请求头如下(来源MDN):

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

指接口可被任意外域调用。

为了避免服务器可能会对跨域请求做出用户预期外的回复,进而造成用户数据受到影响,一部分跨域请求会被划分为“需预检的请求”,或者说“非简单请求”。

简单请求指HTTP的请求方法是HEAD、GET、POST之一,且HTTP投的信息不超出以下字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type (仅限application/x-www-form-urlencoded、multipart/form-data、text/plain)

特别注意Content-Type没有application/json和application/xml。

对于需预检的请求,这类请求在发送真正的跨域请求前会向服务器发送一个OPTIONS请求,验证服务器是否能够允许这样的跨域调用,如果服务器不允许,则真实的跨域请求不会被发起。

CORS还可以用Access-Control-Allow-Methods和Access-Control-Allow-Headers限定请求方法和请求头。但是需要注意,服务器CORS配置不当会导致不合理的跨域调用漏洞。

反代解决跨域

在Web服务器上解决跨域问题的方法,通过反代把外域反向代理成域内的某个路径,以规避跨域调用的限制。

补充:XMLHttpRequest

XMLHttpRequest(XHR),调用API最最常用的东西,在AJAX中被大量使用。

前端开发者需要知道这个东西到底是什么,因为它是AJAX的底层,而且也要知道怎么用原生代码去构造、使用它,而不是使用类似jQuery等。

这个东西主要是用来和服务器交互,允许页面在不影响用户操作的情况下更新页面局部内容,获取某些数据,并且不要求页面刷新。

一个原生的AJAX请求如下所示:
GET:

1
2
3
4
5
6
7
8
var xhr = new XMLHttpRequest();
xhr.open('get', URL);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText);
}
}

POST:

1
2
3
4
5
6
7
8
9
var xhr = new XMLHttpRequest();
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.open('post', URL);
xhr.send(BODY);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText);
}
}

把上面的内容封装成一个函数就能够得到一个简易的原生AJAX。

参考资料:
MDN - XMLHttpRequest