跨域

127 阅读2分钟

一、同源策略

1.1同源

源:控制台打出window.origin或者location.origin,源=协议+域名+端口号

如果两个url的协议、域名、端口号一致,那么这两个url同源

https://baidu.comhttps://www.baidu.com不同源

1.2源策略

浏览器规定如果JS运行在源A里面,只能获取源A的数据,不能获取源B的数据,即不允许跨域,这是浏览器故意涉及的,目的是为了保护用户的隐私

二、跨域

2.1CORS

浏览器默认不同源之间不能互相访问数据,但是如果要共享数据,需要在响应头提前声明

Access-Control-Allow-Origin:http://xxx.com  //指定xxx.com可以访问

2.2JSONP

2.2.1流程

定义:跨域的时候,当前浏览器不支持CORES(例如 IE),不能访问JSON(数据),于是请求JS文件,JS文件会执行回调,回调会需要的数据

//源A服务器设置如果访问的是friends.js文件
if (path === "/friends.js") {  
    if (request.headers["referer"].indexOf("http://frank.com:9990") === 0) {
      response.statusCode = 200;
      response.setHeader("Content-Type", "text/javascript;charset=utf-8");
      const string = fs.readFileSync("./public/friends.js").toString() //读取JS文件内容
      const data = fs.readFileSync("./public/friends.json").toString();//读取数据
      const string2 = string.replace("{{data}}", data)//将JS文件内容替换成JSON数据
      response.write(string2);
      response.end();
    } 

friends.js文件内容:

window.xxx({{data}}) //定义一个函数,{{data}}是占位符,表示会被替换

然后浏览器或者说源B就可以通过script标签获取friends.js文件

//动态添加script标签
const script = document.createElement('script')
script.src='http://xxx.com/friends.js'
document.body.appendChild(script)

window.xxx =(data)=>{ //window.xxx是一个回调,等待源A调用把数据传给源B
    console.log(data)
}

2.2.2通过ref限制可以跨域的域名

if (path === "/friends.js") {
    //如果请求来源的域名为frank.com则执行请求
    if (request.headers["referer"].indexOf("http://frank.com:9990") === 0) {
      response.statusCode = 200;
      response.setHeader("Content-Type", "text/javascript;charset=utf-8");
      const string = fs.readFileSync("./public/friends.js").toString() //读取JS文件内容
      const data = fs.readFileSync("./public/friends.json").toString();//读取数据
      const string2 = string.replace("{{data}}", data)
      response.write(string2);
      response.end();
    } else {//如果请求来源的域名不为frank.com则返回404
      response.statusCode = 404;
      response.end();
    }
}

2.2.3回调的名字随机生成

  • 源B的xxx(函数名)替换成随机数,通过查询参数的形式将这个随机数(函数名)传给源A
//动态添加script标签
const random = 'JSONPCallbackName'+Math.random()
const script = document.createElement('script')
script.src='http://xxx.com/friends.js?callback=${random}' //查询参数发送函数名
document.body.appendChild(script)
script.onload=()=>{
    script.remove() //移除页面中的script标签
}

window.[random] =(data)=>{ //window.xxx是一个回调,等待源A调用把数据传给源B
    console.log(data)
}
  • 源A接受查询参数
if (path === "/friends.js") {
    if (request.headers["referer"].indexOf("http://frank.com:9990") === 0) {
      response.statusCode = 200;
      response.setHeader("Content-Type", "text/javascript;charset=utf-8");
      const string = `window['{{xxx}}']({{data}})`
      const data = fs.readFileSync("./public/friends.json").toString();
      const string2 = string.replace("{{data}}", data).replace('{{xxx}}', query.callback);//讲函数名xxx替换成查询参数的functionName
      response.write(string2);
      response.end();
    } else {
      response.statusCode = 404;
      response.end();
    }
  }

2.2.4封装JSONP

function jsonp(url) {
  return new Promise((resolve, reject) => {
    const random = "JSONPCallbackName" + Math.random(); //声明一个随机的函数名
    window[random] = data => {//声明函数
      resolve(data);//成功的回调
    };
    const script = document.createElement("script");//动态添加script标签
    script.src = `${url}?callback=${random}`;
    script.onload = () => {
      script.remove(); 
    };
    script.onerror = () => {//失败的回调
      reject();
    };
    document.body.appendChild(script);
  });
}

jsonp("http://xxx.com/friends.js").then(data => {
  console.log(data);
});

2.2.5JSONP的优点

  • 兼容IE
  • 可以跨域,即使请求的JSONP不是当前的域名,而是另外的域名,也可以成功的跨域

2.2.6JSONP的缺点

  • 由于是script标签,不知道状态码是什么,也不知道响应头,只知道成功或是失败
  • 由于是script标签,只能发送get请求,不能发送post请求