何为同源
window.origin或location.origin可以得到当前源源 = 协议 + 域名 + 端口号
如果两个url的源(协议、域名、端口号)完全一致,那么这两个url就是同源
注意https://baidu.com、https://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:8888和yyy.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