阅读 78

跨域 CORS JSONP

同源策略

浏览器故意设置的一个功能限制

源:在任何一个网页输入 window.originlocation.origin 可以得到当前源,源 = 协议 + 域名 + 端口号

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

例如:

  • https://qq.comhttps://www.baidu.com 不同源
  • https://baidu.comhttps://www.baidu.com 不同源
  • 必须完全一致才同源

同源策略定义:浏览器规定,如果 JS 运行在源 A 里,那么就只能获取源 A 的数据不能获取源B的数据,即不允许跨域

同源策略使不同源的页面之间,不准互相访问数据

举例:(省略 http 协议)

  • 假设 wbs.com/index.html 引用了 cdn.com/1.js
  • 那么就说 1.js 运行在源 wbs.com 里
  • 这跟 cdn.com 没有关系,虽然 1.js 从它那下载
  • 所以 1.js 就只能获取 wbs.com 的数据
  • 不能获取 1.wbs.com 或者 qq.com 的数据

浏览器这么做的目的是为了保护用户隐私

修改 host

管理员身份运行记事本,点击文件=>打开=>C盘=>windows=>System32=>drivers=>etc=>所有文件

为什么 a.qq.com 访问 qq.com 也算跨域?

  • 因为历史上,出现过不同公司共用域名
  • a.qq.com 和 qq.com 不一定是同一个网站
  • 浏览器谨慎起见,认为这是不同的源

为什么不同端口也算跨域?

  • 原因同上,一个端口一个公司。
  • 安全链条的强度取决于最弱一环,任何安全相关的问题都要谨慎对待

为什么两个网站的P是一样的,也算跨域?

  • 原因同上,IP 可以共用。

为什么可以跨域使用 CSS、JS 和图片等?

  • 同源策略限制的是数据访问,我们引用 CSS、JS 和图片的时候,其实并不知道其内容,我们只是在引用。

如何做到跨域呢

  • CORS
  • JSONP

问题根源

  • 浏览器默认不同源之间不能互相访问数据
  • 但是 http://localhost:8888http://localhost:9999 其实都是我的网站
  • 我就是想要两个网站互相访问,浏览器为什么阻止

步骤: 创建目录

  • qq-com 里有一个 server.js,用来模拟 QQ 空间
  • wbs-com 里有一个 server.js,用来模拟黑客网站

qq-com 目录下

  • /inedx.html 是首页
  • /qq.js 是 JS 脚本文件
  • /friends.json 是模拟的好友数据
  • 端口监听为 8888,访问 http://localhost:8888

wbs-com 目录下

  • /inedx.html 是首页
  • /qq.js 是 JS 脚本文件
  • 端口监听为 9999,访问 http://localhost:9999

CORS

  • 如果要共享数据,就要提前声明
  • 那怎么声明呢?
  • http://localhost:8888 在响应头里写 http://localhost:9999 可以访问
  • 具体语法:
  • Access-Control-Allow-Origin

具体做法:

  • http://localhost:8888同意自己的friends.json 可以被源 http://localhost:9999 的 JS 请求并获得响应
  • 那么源 http://localhost:8888就要在自己的服务器(server.js)的 /friends.json 这个路由里设置响应头,允许 http://localhost:9999 请求。
  • 响应头为: response.setHeader('Access-Control-Allow-Origin','http://localhost:9999')

最后,可以看看 CORS MDN文档

JSONP

  • 虽然数据不能互相访问,但是 JS 可以随意引用
  • 虽然不能访问http://localhost:8888/friends.json
  • 但是可以引用http://localhost:8888/friends.js

http://localhost:9999 访问 http://localhost:8888 步骤:

  1. http://localhost:8888friends.json 数据写到 public 目录下的 friends.js 中间
  • 在 pubilc 目录下新建 friends.js 文件,写下面代码
window.xxx = {{data}}    //{{data}} 是占位符

widow.xxx({{data}})      //另一种方法,用一个函数
复制代码
  • server.js 中添加路由 /friends.js,把 public/friends.js 的占位符替换成 public/friends.json 的数据内容,最后响应体内容就为替换后的 public/friends.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()    //拿到 JS 的内容
  const data = fs.readFileSync("./public/friends.json").toString()    //拿到 JSON 的数据
  const string2 = string.replace("{{data}}", data)                    //把 JSON 数据填到 JS 内容
  response.write(string2)
  response.end()
}
复制代码
  1. wbs.com 可以引用 qq.com 的 JS

wbs.js 中动态创建 script 标签

const script = document.createElement('script')
script.src = 'http://localhost:8888/friends.js'
document.body.appendChild(script)
复制代码

或者在 wbs.com 目录下的 index.html 中引入 script 标签

<script src="http://localhost:8888/friends.js"></script>
复制代码

此时,wbs.com 就引用了 qq.comfriends.js 文件,会按照 friends.js 文件中的 JS 代码执行,在 wbs.js 中可以打印出得到的数据

那么,我们怎么知道 9999 拿到了 8888 的数据呢,监听 onload 事件

script.onload = () => {
  console.log(window.xxx);
};  //监听script标签的加载完成事件,当script标签生成完毕时,就执行函数打印出window.xxx


window.xxx = (data) =>{   //方法二,定义一个函数
  console.log(data)
}
复制代码

注意

  • 在这里 window.xxx 就是一个回调,wbs.js 定义了这个函数,让 friends.js 调用这个函数。
  • 并且数据也不一定是来源于 JSON,也可以来源于 xml,所以 JSONP 这个名字和 JSON 一点关系都没有

优化 1:指定谁可以访问

request.headersreferer 进行检查,所以我们对 server.js 中的路由 /friends.js 进行下面的改写

else if(path === '/friends.js'){
  if(request.headers['referer'].indexOf('http://localhost: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)
    response.write(string2)
    response.end()
  }else{
    response.statusCode = 404
    response.end()
    }
 }
复制代码

优化 2:window xxx 可以不写死吗,可能有多个接口

解决方法:定义一个 window 随机数

  1. wbs.com 中的 wbs.js 文件中写下面代码
const random = 'wbsJSONCallbackName' + Math.random(); //定义随机数 random
window[random] = (data) => {
  console.log(data);
};

const script = document.createElement("script");
script.src = `http://localhost:8888/friends.js?functionName=${random}`; //在这添加查询参数
document.body.appendChild(script);
复制代码
  1. qq.comfriends.js 中,把函数名先用占位符占用起来
window['{{xxx}}'] ({{data}});
复制代码
  1. qq.comserver.js 下的 /friends.js 这个路由中更新代码
else if (path === "/friends.js") {
    if (request.headers["referer"].indexOf("http://localhost: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();
    }
  }
复制代码

优化 3 :wbs.com 每次请求 JSONP 都会在自己的 html 中加一个 script 标签

所以我们就让 script 标签加载好就删除,所以在 wbs.com 中的 wbs.js 文件中更新代码

const random = 'wbsJSONCallbackName' + Math.random()
window[random] = (data) => {
    console.log(data)
}

const script = document.createElement('script')
script.src = `http://localhost:8888/friends.js?functionName=${random}`
script.onload = () => {
    script.remove() //更新了这里
}
document.body.appendChild(script)
复制代码

优化 4

qq.com 中的 friends.js 文件中只有一行代码,直接删除 firneds.js 文件,把其中的内容放入 wbs.comserver.js/friends.js 的路由中

else if (path === "/friends.js") {
    if (request.headers["referer"].indexOf("http://localhost:9999") === 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.functionName);
      response.write(string2);
      response.end();
    } else {
      response.statusCode = 404;
      response.end();
    }
  }
复制代码

封装 JSONP

wbs.js 中的代码如下

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

封装之后就可以就下面这一点点代码了

jsonp("http://localhost:8888/friends.js").then(data => {
  console.log(data);
});
复制代码

什么是 JSONP? 这么回答

  1. 回答 JSONP 是什么

我们在跨域的时候,由于当前浏览器(可恶的 IE)不支持CROS,或者因为某些原因不支持 CROS,那我们就必须用 JSONP 来跨域。于是请求一个 JS 文件,这个 JS 文件会执行一个回调,回调里面就有我们的数据。这个回调的名字是我们随机生成的随机数,我们把这个回调的名字以 callback 的参数传给后台,后台就会把这个函数返回给我们并在执行

  1. JSONP 的优点
  • 兼容 IE
  • 跨域
  1. JSONP 的缺点
  • 因为是 script 标签的引用,读不到 ajax 那么精确的状态,不知道状态码、响应头是什么,只知道成功和失败
  • 因为是 script 标签的引用,不支持 POST
文章分类
前端
文章标签