1 同源策略
1.1 定义
- 源 1、window.origin 或 location.origin 可以得到当前源;
2、源 = 协议 + 域名 + 端口号,三者完全一致,则为同源; 例:baidu.com 、 www.baidu.com 不同源
- 同源策略 1、浏览器规定,如果 JS 运行在源 A 里,那么就只能获取源 A 的数据,不能获取源 B 的数据,即不允许跨域。
2、同源策略:不同源的页面之间,不准相互访问数据,请求能发送成功,但是浏览器不会给数据。
3、举例:假设 frank.com/index.html 引用了 cdn.com/1.js ,那么就说 1.js 运行在源 frank.com 里,虽然 1.js 从 cdn.com 下载,但是跟 cdn.com 没有关系。所以 1.js 就只能获取 frank.com 的数据,不能获取 1.frank 或者 qq.com 的数据。这是浏览器故意设计的功能。
1.2 原因
-
保护用户隐私 1、源 A 里的 JS 和源 B 里的 JS,发的请求几乎没有区别(referer有区别, console.log(response.headers['referer']) ),如果后台开发者没有检查 referer,那么源 B 的 JS 就能偷取源 A 的数据。
-
为什么可以跨域使用 CSS、JS 和图片等? 因为同源策略限制的是数据访问,我们引用 CSS、JS 和图片的时候,其实并不知道内容,我们只是在引用。
2 CORS
2.1 什么是简单请求
简单请求不会触发 CORS 预检请求,需满足所有下属条件,才是“简单请求”:
-
使用下列方法之一:
-
除了被用户代理自动设置的首部字段(例如
Connection,User-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:AcceptAccept-LanguageContent-LanguageContent-Type(需要注意额外的限制)
-
Content-Type的值仅限于下列三者之一:text/plainmultipart/form-dataapplication/x-www-form-urlencoded
-
请求中的任意
XMLHttpRequest对象均没有注册任何事件监听器;XMLHttpRequest对象可以使用XMLHttpRequest.upload属性访问。 -
请求中没有使用
ReadableStream对象。
2.2 简单请求
- 源 B 在响应头里添加
Access-Control-Allow-Origin: http://源 A即可。
2.3 复杂请求
- 响应 OPTIONS 预检请求,源 B 在响应中添加如下的响应头:
Access-Control-Allow-Origin: https://源 A
Access-Control-Allow-Methods: POST, GET, OPTIONS, PATCH
Access-Control-Allow-Headers: Content-Type
- 响应实际请求,如 POST 请求,源 B 在响应中添加
Access-Control-Allow-Origin头。 - 如果需要携带身份信息,JS 中需要在 AJAX 里设置
xhr.withCredentials = true。此时,源 B 的上述三个响应头都需要有具体的内容,不能是*。
3 JSONP
3.1 JSONP 是什么?
1、场景:在跨域时(在网页 A 访问 网页 B 的数据),浏览器不支持 CORS ;
2、是什么:当前网页 A 创建一个 script 标签,这个标签去请求网页 B 的 js 文件,js 文件会执行一个回调(在当前网页 A 上调用一个全局函数运行,函数名需要和网页 B 中定义的函数名相同),回调里面有我们需要的数据;回调的名字随机生成(避免回调名字和其它的函数名重合),以 callback 参数传给后台,后台会把这个函数返回给我们并执行;
// 网页 A 的 js 代码
function jsonp(url){
return new Promise((resolve,reject)=>{
const random = 'frankJSONPCallbackName' + Math.random() // 生成随机的回调函数的名字,避免与其它函数名重复;
window[random]= (data)=>{
resolve(data) //成功,处理回调函数的数据
}
const script = document.createElement('script') //创建 script 标签
script.src = `${url}?callback=${random}` //将回调名字传给后台,名字默认为 callback
script.onload = ()=>{
script.remove() //将生成的 script 标签移除,避免 html 结构臃肿
}
script.onerror = ()=>{
reject() //失败
}
document.body.appendChild(script)
})
}
jsonp('http://qq.com:8888/friends.js')
.then((data)=>{
console.log(data)
})
// 网页 B 的 JS 代码
window['{{funcName}}']({{data}}) //占位符
//网页 B 的 后台代码
const string1 = fs.readFileSync('./public/friends.json').toString() //获取网页 B 的数据
const string2 = fs.readFileSync('./public/friends.js').toString() //获取网页 B 的 js 文件
const data = string2.replace('{{data}}',string1).replace('{{funcName}}',query.callback) //用数据替换占位符
response.write(data)
response.end()
3、JSONP 与 JSON 没有关系,也可以读取 xml 等其它的数据:
window.xxx = `<xml></xml>`
4、通过 js 去拿另一个网站的数据是合法的,浏览器无法限制;
3.2 优点
1、兼容 IE ;
2、可以跨域;
3.3 缺点
1、由于 JSONP 是 script 标签,只支持 get 请求,不能发送 post 请求;
2、读取不到 ajax 那么精确的状态,不知道状态码是多少,也不知道响应头,只知道成功或失败类型。