复杂场景下的跨域图片上传

最近在做一个前后端完全分离模式下,ueditor编辑器多图上传的优化,遇到一些似是而非的坑。

之前采用的跨域方案是用postMessage,大致先将图片上传事件发回接口所在的同域工程,再将得到的上传结果返回给静态域名下的编辑器,实现编辑器代码与后端工程完全分离。

这次场景有点不同,由于主体入口文件被后端工程直接引用,但ueditor的很多弹窗页面都是引用于静态域名下的页面(包括多图片上传模块),这造成了单图上传OK,多图上传模块跨域的情况。这次的思路是想通过CORS直接跨域访问接口,下面把主要问题记录如下:

1、跨域iframe之间的js代码相互调用

由于一般公司的主域都是相同的,可以通过设置domain为相同的主域来实现(两边都要设置)

// 弹窗跨域设置
document.domain = window.location.hostname.match(/(\w+.com)$/)[0]; // 正则获取到主域名

2、iframe页面的跨域访问拦截

在做demo测试时,用form表单实现跨域图片提交,无意中发现了一个后端安全拦截策略,代码如下:

// form所在的iframe的域名是:http://www.xx.com:8000

当执行提交时,控制台若显示'X-Frame-Options' to 'deny'之类的提示时,后研究发现iframe页面的请求被后台框架的iframe安全策略拦截,而原因是由于当前请求用iframe下form提交的,而后台框架正好做了DENY或默认(即是拒绝)的安全策略。

解决方案:
1、将form表单提交换成下文(3)中的ajax/formData的方法
2、修改后台安全配置,如java,需要修改spring安全配置文件:src/main/resources/spring-security.xml

policy有三个值:DENY(默认值), SAMEORIGIN(同域), ALLOW-FROM(允许指定值,chrome貌似支持性不好)



// 或下面这样设置,但实际测试感觉两者效果差不多

当设置ALLOW-FROM时,由于chrome支持性不好,控制台会出现'ALLOW-FROM DENY' is not a recognized directive. The header will be ignored(无法识别指令,头信息被忽略),效果同上面的SAMEORIGIN。

注意的是:如果是ajax形式的提交,则不受上面任何策略的影响(包括DENY)。

3、CORS跨域后台设置

response.setHeader("Access-Control-Allow-Origin", "http://www.xx.com:8000");
response.setHeader("Access-Control-Allow-Credentials", "true");

一般情况下,设置上面的头后,cors跨域基本上能成功了。

需要注意的是,当设置了Credentials,上面的Origin值不能再为*了,需要指定一个具体的值。

4、OPTIONS请求preflight(预检)

在跨域时,若出现options类型的请求,大概情形分以下两种:

1.) 非简单跨域请求
http简单请求包括GET、POST、HEAD三种,像PUT、DELETE、OPTIONS就属于非简单请求,另外要注意的是,当POST请求时,Content-Type必须是text/plain、application/x-www-form-urlencoded、 multipart/form-data中的一个值,若为application/json时,则不属于简单请求,会出现options预检。

2.) 自定义HTTP头部
当前端在跨域时通过xhr.setRequestHeader自定义了一个头,在发起正式的请求前,会产生一个OPTIONS预检请求,该请求中会有两个字段:Access-Control-Request-Method和Access-Control-Request-Headers,分别描述了使用的方法和自定义了哪些头信息。

举个粟子,如下(前端使用了delete,自定义头Test和非简单请求application/json):

$.ajax({  
       ... 
       type: 'DELETE',  // 此处大小写都可以,但后台的Access-Control-Allow-Methods里一定要大写
       beforeSend: function (xhr) {  
           xhr.setRequestHeader('Test', 'myValue');   // 添加了自定义头
       }, 
       contentType: 'application/json',  // contentType内容非简单请求

发起options预检请求后,可以从Request Headers中可以看出使用的方法和自定义了哪些头:

Access-Control-Request-Headers: content-type,test
Access-Control-Request-Method: DELETE

则后端java添加相应的允许:

response.AddHeader("Access-Control-Allow-Origin": "*");
response.AddHeader("Access-Control-Allow-Methods", "DELETE");  // 此处值一定要大写,OPTIONS本身值不用加
response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Test");  // 此处值大小写无所谓

后端有没有设置成功或有没有进入方法里可以从options预检请求的Response Headers里看出。

Access-Control-Allow-Headers:content-type, test
Access-Control-Allow-Methods:DELETE

5、通过ajax/formData批量上传

// 通过FormData将文件转成二进制数据
var formData = new FormData();
formData.append('imageFile', file);
$.ajax({
    // UE.Editor.prototype.apiDomain把java的接口域名挂在UE上读取了
    url: UE.Editor.prototype.apiDomain + '/upload',     
    type: 'POST',
    data: formData, // 上传formdata封装的数据
    dataType: 'JSON',
    cache: false, // 不缓存
    processData: false, // jQuery不要去处理发送的数据
    xhrFields: {
        withCredentials: true // 前端设置是否带cookie
    },
    contentType: false, // jQuery不要去设置Content-Type请求头
    success: function(res) {
        console.log(res);
    },
    error(res) {
        console.log(res);
    }
});
注意:processData,contentType都需设置false,不然jquery会做处理;另外若接口不支持多文件上传,可定时循环发送图片。

6、图片预览临时URL的生成

实现图片预览有两种方法:

方法一:
FileReader,使用的base64方式实现


方法二:
通过html5的window.URL.createObjectURL方法,使用的是blob的方式实现。

使用方法:window.URL.createObjectURL(blob || file);
兼容写法:window[window.webkitURL ? 'webkitURL' : 'URL'].createObjectURL.(blob || file);


当图片临时url被替换成正式url时,记得销毁临时url,方法:
window.URL.revokeObjectURL(url);

你可能感兴趣的:(iframe,跨域,图片上传)