浅析跨域

190 阅读5分钟

一. 同源策略

1. 什么是同源

源: 在任何一个网页,输入window.origin 或 location.origin 可以得到当前的源。

image.png

源=协议+域名+端口号 **同源:**如果两个url的协议,域名,端口号完全一致,这两个url就是同源的。

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

2. 同源策略

是浏览器做的一个规定:如果JS运行在源A里,那就只能获取源A里的数据,不能获取其他别的源的数据。不允许跨域

运行在哪个域名里,就只能访问这个域名里的数据。

那么浏览器为什么要这样规定?为了保护用户的隐私

如果没有同源策略,不同源的两个网站可以随意获取对方的数据,一个钓鱼网站也可以获取qq.com网站的数据,比如好友列表,就泄露了用户的隐私。

那为什么钓鱼网站可以访问到QQ空间里的数据呢?因为无法区分发送者是谁,QQ空间里的JS和黑客网站里的JS发出去的请求几乎没有区别,除了referer。referer表示这个请求是从哪发过来的。

image.png

但是,检查referer还是不够安全。

所以,为了保护用户隐私,浏览器设置了严格的同源策略,不同源的页面之间,不允许互相访问数据。

3. 验证

  • 分别建立两个目录:qq-com模拟QQ空间,jingxi-com模拟黑客网站,都各自有自己的server.js。有自己不同的端口号,表示不同源
  • qq-com : 首页 /index.html,执行的脚本文件/qq.js,模拟的好友数据/friends.json jingxi-com :首页/index.html,执行的脚本文件/jingxi.js qq-com 里,/index.html里引用qq.js,在qq.js里AJAX请求好友数据/friends.json,可以正常得到数据。 jingxi-com 里,/index.html里引用jingxi.js,在jingxi.js里AJAX请求"qq.com:8888/friends.jso…",发现请求可以成功发出去,但是得不到数据。

image.png 从'jingxi.com:9999'源里发出去的请求另一个源'jingxi.com:8888/friends.jso…' 的数据,被CORS阻挡了。 所以这就验证了,不同源的页面之间,是不能互相访问数据的。

这里我们设置了本地域名映射。本来两个源的IP都是localhost,为了让他们看起来像是不同的域名,我们修改host文件,分别让127.0.0.1映射到qq.com,127.0.0.1也映射到jingxi.com,这样我们就可以使用qq.com和jingxi.com这两个不一样的域名访问。

4. 疑问:

  • 为什么一定要域名,端口号完全一致才是同源?因为在历史上,即使是父域名和子域名的关系,也可能是不同的公司在使用;不同的端口号,也是不同的公司。由于IP也可以共用,一样的IP也有可能是不同的网站在使用。所以必须要完全一致。
  • 为什么我们在页面里引用CSS,JS,图片可以跨域使用?因为我们引用这些只是把他们放到页面里执行,只是把他们引用过来,并不知道他的内容。而同源策略限制的是数据的访问。如果是在AJAX里请求那个JS路径,就会受到限制。前提是没有写那句话。

二. CORS

理论和现实是有差距的。理论上浏览器不支持跨域。但是现实里,有时需要跨域。请问怎么跨域呢?一种解决方法就是CORS(Cross-origin resource sharing)跨域资源共享。

如果qq.com的/friends.json这个文件,想把数据共享给jingxi.com,只需要在qq.com的后台服务端,添加一个响应头。

response.setHeader("Access-Control-Allow-Origin", "http://jingxi.com:9999");

表示允许后边指定的源访问。

更多的可以在工作中了解。

三. JSONP

IE6 7 8 9不支持CORS,所以如果要兼容IE,就不能使用CORS。

既然我访问不了json数据,我可以在页面引用JS文件。然后把数据放到这个JS文件里。

步骤:

  1. 在qq.com里建一个friends.js文件,先用占位符替代。然后在后台的server.js里,添加/friends.js路由,即:如果请求这个文件,依次读friends.js的字符串,和friends.json的数据,用字符串的replace方法把占位符替换成真正的数据。
  2. 在jingxi.com里,通过jingxi.js动态引用friends.js文件到页面。创建script标签,加src属性,插入body。
  3. 在friends.js里,可以使用变量赋值得到数据window.xxx={{data}}。也可以执行函数window.xxx({{data}}),只要有人引用了firends.js,就执行这个函数。
  4. 我想在jingxi.com访问数据,所以在jingxi.js里定义window.xxx函数,函数体可以是输出数据。这样在friends.js文件被引用时,这个函数就会被调用。

我们可以发现,我们在jingxi.js里定义的window.xxx函数其实就是回调,我自己写了这个函数,但是不用,等friends.js调用。

//friends.js
window['{{xxx}}']({{data}})
//jingxi.js
const random = "jingxiJSONPName" + Math.random(); //0~1的随机小数
console.log(random);
window[random] = data => {
  console.log(data);
};
const script = document.createElement("script");
script.src = `http://qq.com:8888/friends.js?functionName=${random}`;
script.onload = () => {
  script.remove();
};
document.body.appendChild(script);
//qq-com/server.js
else if (path === "/friends.js") {
    //不支持CORS
    if (request.headers["referer"].indexOf("http://jingxi.com:9999") === 0) {
      response.statusCode = 200;
      response.setHeader("Content-Type", "text/javascript;charset=utf-8");
      const string = fs.readFileSync("./public/friends.js").toString();
      const data = fs.readFileSync("./public/friends.json").toString();
      const string2 = string
        .replace("{{data}}", data)
        .replace("{{xxx}}", query.functionName);
      response.write(string2);
      response.end();
    } else {
      response.statusCode = 404;
      response.end();
    }
}

优化:

  1. JSONP通过包装一个JS文件,别人引用JS就可以获取到资源。那不就是谁都能访问了?怎么像CORS那样指定谁能访问呢?可以通过检查Referer,如果请求来自这个网址,就可以响应。
  2. 函数的名字xxx能不能随机生成,不写死?通过生成随机数,在script标签里加一个查询字符串,让服务端通过query得到这个查询字符串。

JSONP的名字,是指在JSON数据外边包了一层padding。和JSON没有关系,因为数据也可以是其他形式。

四. 什么是JSONP?

我们在跨域的时候,由于当前浏览器不支持CORS,所以就需要另外一种方法来跨域。我们通过创建一个script标签,请求另外一个网站的一个JS文件,这个JS文件里通过执行一个回调,来获取我们想得到的数据。这个回调函数的名字是我们随机生成的,是一个随机数,我们常常以callback这个参数把函数名传到后台,后台得到这个名字再传给我们,然后执行这个函数。

JSONP的优点,兼容 IE,可以跨域。

缺点:由于JSONP是用script标签引用JS的,所以没有AJAX那么精确,它读不到状态码,也不知道响应头,只知道执行成功还是失败。并且,由于是script标签,只能发GET请求,不支持POST。