了解一点跨域的知识吧

198 阅读4分钟

何为同源

  • window.originlocation.origin可以得到当前源
  • 源 = 协议 + 域名 + 端口号

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

注意https://baidu.comhttps://www.baidu.com不同源,完全一致才算同源

什么是跨域

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源

同源策略

不同源页面之间,不准相互访问数据

浏览器规定

  • 如果JS运行在源A里,那么就只能获取源A的数据
  • 不能获取源B的数据,即不允许跨域

举例:

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

这是浏览器的功能,浏览器故意这样设计的,目的是保护用户隐私

如果没有同源策略

以QQ空间为例

  • 源为https://user.qzone.qq.com
  • 假设,当前用户已经登录
  • 假设,AJAX请求/friends.json可获取用户好友列表

这时,黑客来了

  • 黑客给你分享了一个网站https://qzone-qq.com
  • 这是一个钓鱼网站
  • 你点开这个网页,它也请求你的好友列表
  • https://user.qzone.qq.com/friends.json
  • 这么一来,你的好友列表就被黑客偷偷偷走了

这里问题的根源就是

  • 无法区分发送者
  • QQ空间页面里的JS和黑客网站里的JS
  • 发的请求几乎没有区别(referer有区别)
  • 如果后台开发者没有检查referer,那么就完全没区别
  • 所有,没有同源策略,任何页面都能偷QQ空间的数据

但我们会说,那检查referer不就好了?

  • 我们应该明确安全原则:安全链条的强度取决于最弱一环
  • 万一该网站的后端开发工程师粗心失误了呢
  • 所以浏览器主动预防了这种偷数据的行为

怎么跨域

但如果有两个网站,xxx.com:8888yyy.com:9999,它们之间已经达成PY交易,可以相互读取资源,那我们就需要跨域

CORS

浏览器说,如果你要共享数据,需要提前声明

怎么声明?

  • 首先,我们在yyy.com的JS里发送AJAX请求

  • const request = new XMLHttpRequest()
    request.open('GET','http://xxx.com:8888/friends.json')
    request.onreadystatechange = () =>{
        if(request.readyState === 4 && request.status ===200){
            console.log(request.response)
        }
    }
    
    request.send()
    
  • 之后,xxx.com在响应头里写yyy.com可以访问,即可

  • response.setHeader('Access-Control-Allow-Origin','http://yyy.com:9999')
    

具体语法,请看MDN-->跨源资源共享(CORS)

JSONP

CORS可谓一个十分完美的方法,但是我们的老朋友IE,它并不支持CROS,所以逼得我们聪明的程序员前辈们研究出了一个方案——JSONP

(注意:JSONP和JSON没啥关系)

现在面临的问题是什么?

  • 没有CORS,怎么跨域
  • 我们知道数据不能随便访问( xxx.com:8888/friends.json )
  • 但是JS是可以随意引用的,所以我们曲线救国,去引用 xxx.com:8888/friends.js
  • 再让JS包含数据
  • 问题得以解决

步骤:

  • xxx.com创建一个/friends.js,内容为一个函数window.xxx({{data}}),{{data}}为占位符

  • 接着,在后台里添加条件

  • //代码1
    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()//得到真的的数据
          const string2 = string.replace('{{data}}',data)//将占位符替换为真正的数据
          response.write(string2)
          response.end()
        }
    
  • yyy.com用script标签引用/friends.js,并先定义好函数window.xxx,如

  • window.xxx = (data)=>{
        console.log(data)
    }
    
  • 接着,/friends.js执行函数window.xxx

  • 然后,yyy.com就可以通过window.xxx获取数据了

  • 所以,window.xxx实际上就是一个回调

这就是JSONP的大致设计思路

但这么一来,这个JS不就所有网站都能访问了吗?

所以,我们需要referer检查,在后台添加条件:

if(request.headers["referer"].indexOf("http://yyy.com:9999")===0){
    //代码1
}else{
    response.statusCode = 404;
    response.end()
}

但这样仍然不安全,如果yyy.com被攻击了,那xxx.com不也就完了吗?
所以其实应该加上更严格的限制(这里就不做总结了)

当然,上述的方法还有很多优化的地方(如优化函数名为随机数,将JSONP封装等),但本文初衷只是搞懂JSONP的思路即可,所以不再总结

简单回答JSONP是什么?

JSONP我们在跨域的时候由于当前浏览器不支持CORS,我们必须使用另外一种方式跨域

于是

  • 我们请求一个JS文件
  • 这个JS文件会执行一个回调,回调里面就有我们的数据
  • 回调的名字可以随机生成,以callback当参数传给后台
  • 后台会把这个函数再次返回给我们并执行

JSONP的优点

  • 兼容IE
  • 可以跨域

JSONP的缺点

  • 由于他是script标签,所以读不到ajax那么精确的状态码,也不知道响应头,只能通过监听onload、onerror来知道是成功还是失败。
  • 由于是script标签,所以只能发GET请求,不支持POST