深度解析跨域

534 阅读7分钟

深度解析跨域

什么叫跨域?

跨域,指的是浏览器不能执行其他网站的脚本。 它是由浏览器的同源策略造成的,是浏览器施加的安全限制。 所谓同源策略是指,协议、端口、域名都相同。

造成跨域的两种策略

浏览器的同源策略会导致跨域,这里同源策略又分为以下两种:

  • DOM同源策略:禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。
  • XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。

为什么要有跨域限制

浏览器执行脚本时,会检查脚本是否同源,如果不是,则不会被执行。

两个域名不能跨过域名来发送数据和请求数据,否则就是不安全的,这种不安全也就是CSRF(跨站请求伪造)攻击。

如果没有AJAX同源策略,相当危险,我们发起的每一次HTTP请求都会带上请求地址对应的cookie,CSRF 攻击是利用用户的登录态发起恶意请求。

如何解决跨域

  1. JSONP
  2. CORS
  3. 服务器代理
  4. document.domain跨子域
  5. location.hash跨域
  6. postMessage

JSONP

JSONP的产生:

  1. AJAX直接请求普通文件存在跨域无权限访问的问题,不管是静态页面也好.

  2. 不过我们在调用js文件的时候又不受跨域影响,比如引入jquery框架的,或者是调用相片的时候

  3. 凡是拥有src这个属性的标签都可以跨域例如<script> <img> <iframe>

  4. 如果想通过纯web端跨域访问数据只有一种可能,那就是把远程服务器上的数据装进js格式的文件里.

  5. 而json又是一个轻量级的数据格式,还被js原生支持

  6. 为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback 参数给服务端。

基于script跨域

<script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script>
<script>
    function jsonp(data) {
    	console.log(data)
	}
</script> 

在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的,这时候就需要自己封装一个 JSONP,以下是简单实现:

function jsonp(url, jsonpCallback, success) {
  let script = document.createElement('script')
  script.src = url
  script.async = true
  script.type = 'text/javascript'
  window[jsonpCallback] = function(data) {
    success && success(data)
  }
  document.body.appendChild(script)
}
jsonp('http://xxx', 'callback', function(value) {
  console.log(value)
})

基于jQuery跨域

$.ajax({
    url:'http://domain/api',
    ype : 'get',
    dataType : 'text',
    success:function(data){
        alert(data);
    },
    error:function(data){
        alert(2);
    }        
});

通过iframe来跨子域

eg: a.study.cn/a.html 请求 b.study.cn/b.html

a.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
        <script type="text/javascript">
            document.domain = 'study.cn';
            function test() {
                alert(document.getElementById('a').contentWindow);
            }
        </script>
</head>
<body>
    <iframe id='a' src='http://b.study.cn/b.html' onload='test()'>
</body>
</html>

b.html

<!DOCTYPE html>
<html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
        <script type="text/javascript">
        document.domain = 'study.cn';
        </script>
    </head>
    <body>
        我是b.study.cn的body
    </body>
</html>

修改document.domain的方法只适用于不同子域的框架(父类与子类)间的交互。

CORS

支持CORS请求的浏览器一旦发现ajax请求跨域,会对请求做一些特殊处理,对于已经实现CORS接口的服务端,接受请求,并做出回应。

有一种情况比较特殊,如果我们发送的跨域请求为“非简单请求”,浏览器会在发出此请求之前首先发送一个请求类型为OPTIONS的“预检请求”,验证请求源是否为服务端允许源,这些对于开发这来说是感觉不到的,由浏览器代理。

总而言之,客户端不需要对跨域请求做任何特殊处理。

浏览器对跨域请求区分为“简单请求”与“非简单请求”

简单请求

(1) 请求方法是以下三种方法之一:

 HEAD

 GET

 POST

(2)HTTP的头信息不超出以下几种字段:

 Accept

 Accept-Language

 Content-Language

 Last-Event-ID

 Content-Type:
     application/x-www-form-urlencoded、 multipart/form-data、text/plain

不满足这些特征的请求称为“非简单请求”,例如:content-type=applicaiton/json , method = PUT/DELETE...

浏览器判断跨域为简单请求时候,会在Request Header中添加 Origin (协议 + 域名 + 端口)字段 , 它表示我们的请求源,CORS服务端会将该字段作为跨源标志。

CORS接收到此次请求后 , 首先会判断Origin是否在允许源(由服务端决定)范围之内,如果验证通过,服务端会在Response Header 添加 Access-Control-Allow-Origin、Access-Control-Allow-Credentials等字段。

必须字段:
Access-Control-Allow-Origin:表示服务端允许的请求源,*标识任何外域,多个源 , 分隔

可选字段
Access-Control-Allow-Credentials:false 表示是否允许发送Cookie,设置为true
                                 同时,ajax请求设置withCredentials = true,浏览
                                 器的cookie就能发送到服务端

Access-Control-Expose-Headers:调用getResponseHeader()方法时候,能从header中获
                               取的参数

总结:简单请求只需要CORS服务端在接受到携带Origin字段的跨域请求后,在response header中添加Access-Control-Allow-Origin等字段给浏览器做同源判断

进行非简单请求时候, 浏览器会首先发出类型为OPTIONS的“预检请求”,请求地址相同 , CORS服务端对“预检请求”处理,并对Response Header添加验证字段,客户端接受到预检请求的返回值进行一次请求预判断,验证通过后,主请求发起。

例如:发起 content-type=application/json 的非简单请求,这时候传参要注意为json字符串

这里可以看到,浏览器连续发送了两个jsonp.do请求 , 第一个就是“预检请求”,类型为OPTIONS,因为我们设置了content-type这个属性,所以预检请求的Access-Control-Expose-Headers必须携带content-type,否则预检会失败。

预检通过后,主请求与简单请求一致。

总结:非简单请求需要CORS服务端对OPTIONS类型的请求做处理,其他与简单请求一致

通过上面叙述,我们得知借助CORS我们不必关心发出的请求是否跨域,浏览器会帮我们处理这些事情,但是服务端需要支持CORS,服务端实现CORS的原理也很简单,在服务端完全可以对请求做上下文处理,已达到接口允许跨域访问的目的。

服务器代理

浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。

document.domain

该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。

只需要给页面添加 document.domain = 'test.com' 表示二级域名都相同就可以实现跨域。

location.hash跨域

location.hash方式跨域,是子框架具有修改父框架src的hash值,通过这个属性进行传递数据,且更改hash值,页面不会刷新。但是传递的数据的字节数是有限的。

postMessage

页面和新开的窗口的数据交互。

  • 多窗口之间的数据交互。
  • 页面与所嵌套的iframe之间的信息传递。
  • window.postMessage是一个HTML5的api,允许两个窗口之间进行跨域发送消息。

这个应该就是以后解决dom跨域通用方法了,具体可以参照MDN。

// 发送消息端
window.parent.postMessage('message', 'http://test.com')
// 接收消息端
var mc = new MessageChannel()
mc.addEventListener('message', event => {
  var origin = event.origin || event.originalEvent.origin
  if (origin === 'http://test.com') {
    console.log('验证通过')
  }
})

参考:

跨域——CORS详解

跨域的那些事儿