1 同源策略
1.1 同源
- window.origin或者location.origin可以得到当前源
- 源 = 协议 + 域名 + 端口号
- 如果有两个url的 协议、域名、端口号完全一致,那么这两个url就是同源的
- 在浏览器里打开页面则默认遵循同源策略
- 前端测试时可以使用 postman 等工具,或安装在chrome里安装插件进行测试(不遵循同源策略,仅测试开发时可以使用)。
1.2 同源策略定义
- 如果JS运行在源A里,那么就只能获取源A的数据
- 不能获取源B的数据,即不允许跨域
- 不同源的页面之间,不准互相访问数据
1.3 优缺点
优点
保证用户的隐私安全和数据安全
缺点
当前端需要访问另一个域名的后端接口时,会被浏览器阻止其获取相应。
eg:A 站点通过 AJAX 访问 B 站点的 /money 查询余额接口,请求可以发出,但响应会被浏览器屏蔽
1.4 疑问解答
- a.qq.com和qq.com跨域:历史上出现过不同公司共用域名,因此这两者不一定是同一个网站,浏览器谨慎起见,认为这是不同的源
- 不同端口算跨域:一个端口一个公司,因此不同端口可能属于不同公司,因此认为是不同的源
- 同IP跨域:原因同上,IP是可以共用的
- 跨域可以使用CSS、JS和图片等:同源策略限制的是数据访问,我们引用CSS、JS和图片时,其实并不知道其内容,只是在引用
1.5 跨域的解决方法
跨域问题根源:
- 浏览器默认不同源之间不能互相访问数据
解决方法:
- 方法1:CORS(不支持IE)
- 方法2:JSONP(script标签存在缺陷)
2 CORS
- 在server.js中进行设置,在需要跨域访问的文件设置中添加以下代码:
// 'http://xxxx:yyyy为设置允许访问这部分资源的网站地址及端口号
response.setHeader('Access-Control-Allow-Origin','http://xxxx:yyyy')
- referer
可以通过console.log(request.headers['referer'])
读取想要获取此资源的网站
存在问题
无法兼容IE6、7、8、9浏览器,因此如果需要兼容IE浏览器,则需要使用JSONP
3 JSONP
3.1 简介
3.1.1 概念
在进行跨域操作时,由于部分浏览器不支持CORS,因此我们必须使用JSONP来执行跨域操作,我们需要请求一个JS文件,JS文件中会执行一个回调,回调中包含我们所需的数,回调的名称是随机生成的随机数,我们将这个随机数名称以callback的参数传给后台,后台会将函数返回给我们并执行。
3.1.2 优缺点
优点
- 兼容IE
- 可以跨域
缺点
- 由于它是script标签,所以它无法读取到AJAX那么精确,接收不到状态码及响应头等
- 由于它是script标签,因此它只能发get请求,不支持post
3.2 原理
通过依靠域内js获取域内json的内容,而后使用域外js调用域内获取到内容的js,从而获得域内json的内容
3.3 使用方法(以获取json文件内容为例)
- 在json所在域内设置同域js文件用于获取json文件内容
- js文件内使用占位符预留json内容放置空间
- 在域内server.js文件中设置,使用域内js文件获取域内对应json文件内容
- 域外则可以通过引用域内js文件,获取域内json文件内容
3.4 实例
要求:
1.sherry.com需要获取qq.com域内friend.json的数据
2.为回调函数设置随机函数名,以避免函数名被占用
3.通过referer设置仅限http://sherry.com:9999
可以访问friend.json的内容
// qq.com内friend.json的内容
{
"name": "sherry",
"age": 18
}
// 在sherry.js定义随机函数,并打印数据,sherry.com可以通过window[random]获取数据
// window[random]是回调函数
const random = Math.random()
window[random] = (data) => {
console.log(data)
}
// qq.com内server.js设置
else if (path === '/friend.js') {
// referer判定是否为http://sherry.com:9999,是进行操作,不是则返回404
if (request.headers['referer'].indexOf('http://sherry.com:9999') === 0) {
response.statusCode = 200
response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
const string = `window[{{xxx}}] = {{data}}`
const data = fs.readFileSync('./public/friend.json').toString()
// 使用获取到的随机函数名query.functionName替换friend.js内的函数名占位符{{xxx}}
string2 = string.replace('{{data}}', data).replace('{{xxx}}', query.callback)
response.write(string2)
response.end()
} else {
response.statusCode = 404
response.end()
}
}
// sherry.com用于引用friend.js以获取friend.json内容的sherry.js设置
// 方法1:JS动态引用
const script = document.createElement('script')
// 通过functionName=${random}传入随机函数名
const script = document.createElement('script')
script.src = `http://qq.com:8888/friend.js?functionName=${random}`
// 执行完毕后删除多余的script,保证页面整洁
script.onload = () => {
script.remove()
}
document.body.appendChild(script)
// 方法2:HTML静态引用
// 在sherry.js对应的index.html页面设置
<script src="http://qq.com:8888/friend.js"></script>
// 在sherry.js中设置,用以监听内容是否获取成功
console.log(window.xxx)
代码封装jsonp() :
function jsonp(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();
};
script.onerror = () => {
reject();
};
document.body.appendChild(script);
});
}
// jsonp的使用
jsonp('http://qq.com:8888/friend.js')
.then((data) => {
console.log(data)
})
3.4 缺陷及解决
任意网站都可以通过引用JS获取域内数据,可以通过referer检查来避免这个缺陷
// 检查是否是允许的网站,是则放行,不是则返回404
if (request.headers['referer'].indexOf('http://sherry.com:9999') === 0) {
response.statusCode = 200
response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
const string = fs.readFileSync('./public/friend.js').toString()
const data = fs.readFileSync('./public/friend.json').toString()
string2 = string.replace('{{data}}', data)
response.write(string2)
response.end()
} else {
response.statusCode = 404
response.end()
}