1 跨域产生的原因
- 跨域浏览器同源策略限制的一类请求场景
- 什么是同源策略?
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制以下几种行为:
1.) Cookie、LocalStorage 和 IndexDB 无法读取
2.) DOM 和 Js对象无法获得
3.) AJAX 请求不能发送
2 跨域解决方案
1、 通过jsonp跨域
2、 document.domain + iframe跨域
3、 location.hash + iframe
4、 window.name + iframe跨域
5、 postMessage跨域
6、 跨域资源共享(CORS)
7、 nginx代理跨域
8、 nodejs中间件代理跨域
9、 WebSocket协议跨域
3 jsonp跨域实现
- JSONP:利用在页面中创建
- JSONP跨域的基本原理:由于script 标签不受浏览器同源策略的影响,允许跨域引用资源。因此,通过动态创建 script 标签,然后利用 src 属性进行跨域。
- JSONP的优点:不受同源策略的限制;兼容性好,在老的浏览器中可以运行;请求完毕后可以通过调用 callback 的方式回传结果。
- JSONP的缺点:只支持 GET 这种HTTP请求;jsonp在调用失败的时候不会返回各种HTTP状态码;安全性:callback传入的参数是在后端进行了一次拼接,这即代表存在注入的可能,如果后端设计不当,是有可能出现安全风险的。
// jsonp的封装
function jsonp(params) {
// 创建script标签并加入到head中
var callbackName = params.jsonp;
var head = document.getElementsByTagName('head')[0];
// 设置传递给后台的回调参数名
params.data['callback'] = callbackName;
var data = formatParams(params.data);
var script = document.createElement('script');
// 发送请求
script.src = params.url + '?' + data;
// 创建jsonp回调函数
window[callbackName] = function (res) {
head.removeChild(script);
clearTimeout(script.timer);
window[callbackName] = null;
params.success && params.success(res);
};
head.appendChild(script);
// 为了得知此次请求是否成功,设置超时处理
if (params.time) {
script.timer = setTimeout(function () {
window[callbackName] = null;
head.removeChild(script);
params.error && params.error({
message: '超时'
});
}, 500);
}
}
// 格式化参数
function formatParams(data) {
var arr = [];
for (var name in data) {
arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
};
// 添加一个随机数,防止缓存
arr.push('v=' + random());
return arr.join('&');
}
// 获取随机数
function random() {
return Math.floor(Math.random() * 10000 + 500);
}
jsonp({
url: url,
jsonp: 'jsonpCallback',
data: dataParams,
success: function (res) {
params.success && params.success();
},
error: function (error) {}
})
4 为什么要用img标签来进行跨域?
凡带src属性的,必能跨域,所以你可以看到还有种方案叫JSONP。但是src对这个事应该也挺无奈的,算是个历史遗留问题吧,不属于跨域问题的原始设计,就像修了堵墙还挖了个洞出来一样。
5 ping图片跨域
图片Ping是客户端向服务器的单向通信,因为src请求资源不属于同源策略,所以一般可以用来做埋点,比如监听网页的PV(Page View),UV(Unique Visitor)。通俗点讲就是曝光率。
6 canvas跨域图片转base64
/**
* 在线图片转换base64
* @param imgUrl 图片的url
*
*
* */
export function dealImage(imgUrl) {
// 一定要设置为let,不然图片不显示
let image = new Image();
// 解决跨域问题 必须后端设置支持跨域,后端不配合白搭
image.setAttribute('crossOrigin', 'anonymous');
const imageUrl = imgUrl;
// 加随机数,防止图片请求到缓存
image.src = imageUrl + '?v=' + Math.random()
// image.onload为异步加载
image.onload = () => {
var canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0, image.width, image.height);
var quality = 0.8;
// 这里的dataurl就是base64类型
// 使用toDataUrl将图片转换成jpeg的格式,不要把图片压缩成png,因为压缩成png后base64的字符串可能比不转换前的长!
const dataurl = canvas.toDataURL('image/jpeg', quality);
console.log('wbk dataurl :>> ', dataurl );
}
}
跨域时,如果资源服务器不允许的话,前端是不能获取到该资源的,也是为了安全考虑,可以尝试做一层代理,但有些服务器也会做防爬处理。
canvas 中的图片可能来自一些第三方网站。在资源服务器不允许的情况下,使用跨域的图片绘制时会污染画布,这是出于安全考虑。在“被污染”的画布中调用 toBlob() toDataURL() getImageData() 会抛出安全警告。
7 尝试使用img标签进行jsonp跨域请求获取在线图片操作(失败)
猜想原因,jsonp只支持get请求跨域,没法操作图片
猜想原因2,jsonp 需要后端进行配合
// jsonp的封装
function jsonp(params) {
// 创建script标签并加入到head中
var callbackName = params.jsonp;
var head = document.getElementsByTagName('head')[0];
// 设置传递给后台的回调参数名
params.data['callback'] = callbackName;
var data = formatParams(params.data);
var img = document.createElement('img');
// 发送请求
img.src = params.url + '?' + data;
// 创建jsonp回调函数
window[callbackName] = function (res) {
head.removeChild(img);
clearTimeout(img.timer);
window[callbackName] = null;
params.success && params.success(res);
};
head.appendChild(img);
// 为了得知此次请求是否成功,设置超时处理
if (params.time) {
img.timer = setTimeout(function () {
window[callbackName] = null;
head.removeChild(img);
params.error && params.error({
message: '超时'
});
}, 500);
}
}
// 格式化参数
function formatParams(data) {
var arr = [];
for (var name in data) {
arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
};
// 添加一个随机数,防止缓存
arr.push('v=' + random());
return arr.join('&');
}
// 获取随机数
function random() {
return Math.floor(Math.random() * 10000 + 500);
}