[聚合文章] 跨域的几种解决方案

JSONP 2017-04-10 14 阅读

浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。

所谓的同源策略指的是三个相同:

  • 协议相同
  • 域名相同
  • 端口相同

例如:

https://www.kmac007.com/2017/index.html 这个网址,协议是https,域名是kmac007.com,端口是80(默认端口可以省略)

对于如下网址:

同源策略的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

跨域以及跨域的几种实现方式

跨域指的是,突破同源策略,不同源之间进行数据传输或通信。

跨域有如下几种实现方式:

  1. JSONP
  2. document.domain
  3. window.postMessage
  4. CORS:跨域资源共享(Cross-origin resource sharing)

JSONP

在JS中我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。由于script标签不受同源策略的限制,可以通过script标签向服务器请求数据。而JSONP就是通过这个特性来实现的。

JSONP的原理是,在网页中动态添加一个script标签,src指向接口地址,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据包裹在一个指定名字的回调函数里传回来。

如: script标签的src指向 ‘kmac007.com/getData?callback=jsonp’,以jsonp(data)形式的返回数据,调用页面上的jsonp函数从而获取到了data

实现示例

//前端部分:
  function addScriptTag(src){
    var script = document.createElement('script');
    script.src = src;
    document.body.appendChild(script);
  }

  window.onload = function(){
    addScriptTag('http://kmac007.com/getData?callback=jsonp');
  }

  function jsonp(data){
    console.log(data.name)
  }

服务器收到这个请求后,会将数据放在回调函数的参数位置返回:

//后端部分:
app.get('/getData', function(req, res){
  var data = {
    "name": "dk",
    "age": "23"
  }
  var cb = req.query.callback
  if (cb) {
    res.send(cb + '(' + JSON.stringify(data) + ')')
  } else {
    res.send(data)
  }
})
/*
 返回:json({"name":"dk","age":"23"})
*/

优点

  1. 很好的解决了跨域通信的问题,用户传递一个callback参数给服务器,服务器根据callback返回特定的callback包裹的数据,客户端可以随意定制自己的的函数处理返回的数据。
  2. 兼容性非常好,在非常古老的浏览器上也能很好的实现

缺点

  1. JSONP只支持get请求而不支持其他HTTP请求,参数的形式只能做url拼接,后台取值会比较繁琐
  2. 存在安全隐患,动态插入 <script> 标签其实就是一种脚本注入。

document.domain

Cookie是服务器写入浏览器的一小段消息,只有同源的网页才能共享。但是两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享Cookie。

举例来说, A网页是 http://www1.kmac007.com/a.html ,B网页是 html://www2.kmac007.com/b.html ,只要设置相同的document.domain,两个网页就可以共享Cookie。

document.domain = 'kmac007.com'

现在A网页中设置一个Cookie

document.cookie = "test=1"

网页B中就可以读取这个Cookie

var allCookie = document.cookie

window.postMessage

由HTML5引入的API,postMEssage()方法允许来自不同源的脚本采用异步方式进行有限通信。这个API为 window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收信息的窗口源(origin),即“协议+域名+端口”。也可以设为*,表示不限制域名,向所有窗口发送。

即父窗口与子窗口互相发送消息,通过message事件,监听对方的消息,实现跨域。

父窗口:

$('.main input').addEventListener('input', function(){
  console.log(this.value);
  window.frames[0].postMessage(this.value, '*');
  //向子窗口发送信息
})

window.addEventListener('message', function(e){
  $('.main input').value = e.data
  console.log(e.data);
  //监听子窗口发送信息的变化
});

function $(id){
  return document.querySelector(id);
}

子窗口:

$('#input').addEventListener('input', function(){
	window.parent.postMessage(this.value, '*');
  //向父窗口发送信息
})

window.addEventListener('message',function(e){
		$('#input').value = e.data
    console.log(e.data);
    //监听父窗口的发送的信息变化
});

function $(id){
	return document.querySelector(id);
}

CORS

CORS是一个W3C标准,全称是“跨域资源共享”(Cross-origin resource sharing)。

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS需要浏览器和服务器同时支持。目前所有浏览器都支持该功能,IE浏览器不能低于IE10。

实现功能非常简单,只需要由服务器发送一个响应标头即可。它是通过客户端+服务端协作声明的方式来确保请求安全的。服务端会在HTTP请求头增加一系列HTTP请求参数(例如Acess-Control-Allow-Origin等),来限制哪些域的请求和哪些类型可以接受,而客户端在发起请求时必须声明自己的源(Origin),否则服务器将不予处理,如果客户端不作声明,请求甚至会被浏览器直接拦截到不了服务端。服务端收到HTTP请求后会进行域的比较,只有同域的请求才会处理。

浏览器将CORS请求分为两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要满足以下两大条件,就属于简单请求:

(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

凡是不同时满足上面两个条件,就属于非简单请求。

浏览器对这两种请求的处理方式是不一样的。

  1. 简单请求

    对于简单请求,浏览器直接发出CORS请求。具体来说就是在头信息中,增加一个Origin字段。而Origin字段说明本次请求来自哪个源(协议+域名+端口)。服务器根据这个值,决定是否同意这次请求。如果Origin指定的源,不在许可范围内,浏览器会返回一个正常的HTTP回应。如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息 。如:

Access-Control-Allow-Origin: http://api.kmac007.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: showMes
Content-Type: text/html; charset=utf-8
  1. 非简单请求

    非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或Content-Type字段的类型是application/json。

    非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求。浏览器先询问服务器,当前网页所在的域名是否在服务器的虚空名单之中,一次可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就会报错。

实现实例:

//前端部分
<body>
  <buttonid="btn">点我</button>
  <script>
varbtn =document.getElementById("btn")
 btn.addEventListener('click',function(){
varxhr =newXMLHttpRequest()
 xhr.onreadystatechange = function(){
if(xhr.readyState ===4&& xhr.status ===200) {
varnewLi =document.createElement('li')
 newLi.innerText = xhr.responseText
document.body.appendChild(newLi)
 }
 }
 xhr.open('get','http://b.kmac007.com:8080/getData',true)
 xhr.send()
 })
</script>
</body>
//后端部分
app.get('/getData',function(req,res){
  var data = "are u ok"
  res.header("Access-Control-Allow-Origin","*")
  // 加入响应头Access-Control-Allow-Origin
  res.send(data)
})

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。