AJAX(四)--跨域

205 阅读4分钟

源(origin)

  • 源 = 协议 + 域名 + 端口号
  • 控制台输入window.origin或者location.origin

同源

  • 如果两个URL : 协议 + 域名 + 端口号 完全一致就是同源
  • 浏览器规定如果JS脚本运行在源A里,那么就只能获取源A的数据不能获取源B的数据,即不允许跨域
  • 同源策略是为了保护隐私,浏览器规定的

黑客套路

  • 黑客常常把JS的AJAX脚本包装在HTML文件中,然后包上伪装域名,你点开一个很相似的钓鱼网站,JS脚本开始运行
  • 如果没有没有同源策略,钓鱼网站的JS脚本就会盗用你的信息
  • 为什么黑客不在自己电脑上去申请你的信息呢?

以QQ空间为例,你需要输入账号密码,QQ服务端才会给你发送数据,黑客没办法知道你的密码,只能在你输入密码之后诱骗你打开钓鱼网站

问题根源

  • 在你输完密码,服务器同意给你发信息,打开钓鱼网站之后,你的请求和黑客的请求几乎没有区别(除了referer)
  • referer是http请求里的字段(写明了请求来源)
  • 总之,浏览器为了用户隐私,设置了严格的同源策略
  • 请求会成功,但是浏览器不会把东西给你的

同源策略

  • 在浏览器中,<script>、<img>、<iframe>、<link>等标签都可以加载跨域资源,而不受同源限制
  • 但浏览器会限制脚本中发起的跨域请求。比如,使用 XMLHttpRequest 对象和Fetch发起 HTTP 请求就必须遵守同源策略。
  • 同源策略限制的是数据访问,我们引用CSS,JS和图片的时候,其实并不知道其内容,我们只是在引用。

CORS跨域

  • 如果要共享数据,需要提前声明,那怎么声明呢?
  • 浏览器说,被请求方在响应头里写xxx可以访问,即可(服务端的操作)
  • 具体语法Access-Control-Allow-Origin: http://foo.example
  • response.setHeader('Access-Control-Allow-Origin',"http://localhost:9990")
  • 如果要求所有网站,可以访问request.headers[referer]

JSONP跨域

IE不支持,采用其他办法

  • 虽然不允许跨域,我们还是可以通过script引用任何网站的js
  • 把/friends.json的数据内容,写到/friends.js里面,其他源引用/friends.js了,他想要的内容也在里头了
  • 服务端代码如下:用json代替js中的占位符
else if (path === "/friends.js") {
    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 content = string.replace("{{data}}", data);
    //替换friends.js里的占位符
    response.write(content);
    response.end();
  • friend.js代码如下
window.xxx = {{data}}
  • 黑客网站请求friend.js文件得到content,"Content-Type"为"text/javascript,相当于得到一个占位符被替换后的js文件,只要黑客使用xxx就可以知道内容,xxx是一个json字符串
  • js文件有个占位即可,服务端把它和数据结合起来,在上面js文件把占位符赋值给了xxx,实际情况中我们经常使用函数

请求方代码

请求方即上面的黑客

const script = document.createElement("script");
script.src = "origin/friends.js";
script.onload = () => {
  console.log(window.xxx);
};
document.body.appendChild(script);

改良后的请求方代码

被请求方的friend.js代码

window.xxx({{data}})

请求方的代码

window.xxx = (data)=>{
    console.log(data)
}
const script = document.createElement("script");
script.src = "origin/friends.js";
document.body.appendChild(script);

代码逻辑:

  1. 请求方JS脚本中定义一个函数,参数为要请求的数据,之后插入script标签,
  2. 被请求方的js文件调用该函数(参数变成占位符待服务端替换)
  3. 服务端接受请求端scipt标签的请求,返回content字符串(替换占位符后的),调用了xxx函数
  4. xxx就是典型的回调函数

JSONP优化

用随机数给函数取名巧用查询参数引入随机数函数名(其实大家在查询参数里不用functionName,而是callback,也可以把friend.js直接写到服务端这样就不用开新文件了
请求方脚本

let random = Math.random()
window[random] = (data)=>{
    console.log(data)
}
const script = document.createElement("script");
script.src = `origin/friends.js?functiontionName=${random}`;
script.onload = () => {
    script.remove()
}
document.body.appendChild(script);

被请求方服务端

else if (path === "/friends.js") {
    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 content = string.replace("{{data}}", data).replace("{{xxx}}",query.function);
    //替换friends.js里的占位符
    response.write(content);
    response.end();

被请求方的friend.js代码

window.{{xxx}} ({{data}})

Promise封装JSONP

let jsonp = function(url) {
  return new Promise((resolve, reject) => {
    const random = Math.random();
    window[random] = data => {
      resolve(data);
    };
    const script = document.createElement("script");
    script.src = `${url}?callback=${random}`;
    script.onload = () => {
      script.remove();
    };
    reject.onerror() = () => {
      reject();
    };
    document.body.appendChild(script);
  });
};

JSONP缺点

  • script标签读不到状态码,状态不精确
  • 只能发GET

总结

  • JSONP无法指定某个网站可以访问,所有网站都可以创建一个JS脚本:定义一个函数,然后插入script标签,这样就能拿到数据
  • JSONP可以做referer检查来指定源request.headers[referer] indexOf url
  • CORS:很灵活只需在服务端指定谁可以拿数据直接用AJAX请求即可
  • CORS不兼容时我们使用JSONP,我们请求一个js文件里面执行一个回调,回调里就有数据,回调名字随机的以查询参数的形式传到后台