关于plupload跨域文件上传

现在基于Vue的前后端分离项目已经数不胜数,特别是在移动端中的应用。

在开发项目的过程中也遇到过许多问题,例如在前后端分离当中避免不了的跨域问题,关于 CROS(Cross-Origin Resource Sharing) 的介绍就不再这里进行赘述,不了解的赶紧去补充一下相关知识吧。

难得的空闲终于来了,今天记录一下之前遇到的关于 Plupload 的跨域问题。

区分用户身份的问题

对于POST跨域传递数据,服务端只需设置好 Access-Control-Allow-Origin 相应头即可。但是,如果需要区分不同用户,那就必须要携带Cookies,但是先看看下面这句:

1
Fetch 与 CORS 的一个有趣的特性是,可以基于  HTTP cookies 和 HTTP 认证信息发送身份凭证。一般而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。

查看来源

注意这一句:对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息

也就是说,Plupload 在这种跨域、同时是POST的情况下,是没办法发送身份凭证信息的,那么问题就来了,如果相关后端接口需要对用户权限进行判断(例如根据用户权限控制是否允许用户上传),那该如何是好?

方案一:通过参数传递 PHPSESSID

既然不能携带Cookies,那为什么不能通过 GET 或者 POST 携带 PHPSESSID 传参实现呢?

思路很简单:

  1. 通过 Javascript 获取 Cookies 中的 PHPSESSID
  2. 通过 Request Body 或者 Request Params 将 PHPSESSID 传入服务端,服务端再切换当前会话,下面是在PHP实现的示范代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
// 以下代码中通过 `$_GET` 以及 `$_SESSION` 并不提倡,为安全性考虑请结合开发框架进行参数过滤

// 如果框架或者php.ini开启了 session autostart,那么需要先提交并关闭当前会话
session_commit();

// 切换到指定ID
session_id($_GET['PHPSESSID']);

// 再次开启 session
session_start();

// 切换成功,调试输出
var_dump($_SESSION);
缺点
  1. 如果PHPSESSID设置为httponly,那么将无法通过Javascript获取到,如果需要强制使用则必须关闭,那么将以牺牲安全性为代价
  2. 同时采用该方案还要求前端和后端必须在相同父域的情况下,否则例如 web.comapi.com 这样部署将无法采用该方案
  3. 或者类似于微信小程序的模式,用户凭据通过Request Params或者Request Body传递,不再有Cookies的概念,但对于已经有一定体系结构的项目,意味着前端或者后端都有较大的改动,所以也不是特别推荐。

严格来说这第一个甚至算不上一个方案,牺牲安全性、过程繁杂,emmm……

方案二:在CROS请求中携带Cookies

还是回到上方提到的链接:附带身份凭证的请求

Fetch 与 CORS 的一个有趣的特性是,可以基于 HTTP cookies 和 HTTP 认证信息发送身份凭证。

利用这个特性,即可解决文章开头所述的问题,至于Browser和Server只需要控制一下各自响应头即可,本文重点要说的是,在 Plupload 中该如何处理。

在这里直接贴上项目WIKI链接:https://github.com/moxiecode/plupload/wiki/Required-Features

其中 send_browser_cookies 就是我们需要的,不多说,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const uploader = new plupload.Uploader({
browse_button: '#upload-btn',
url: '/api/upload',
required_features: {
send_browser_cookies: true
},

// https://github.com/moxiecode/plupload/wiki/Options#filters
filters: {
mime_types: [
{ title: 'Adobe Photoshop Files', extensions: 'psd,tif,tiff' },
],

// 用来限定上传文件的大小,如果文件体积超过了该值,则不能被选取。值可以为一个数字,单位为b,也可以是一个字符串,由数字和单位组成,如'200kb'
max_file_size: '200MB',

// 是否允许选取重复的文件,为true时表示不允许,为false时表示允许,默认为false。如果两个文件的文件名和大小都相同,则会被认为是重复的文件
prevent_duplicates: true,
},
init: {
FilesAdded(up, files) {
// your cods
}
}
})

关键点就是 send_browser_cookies

注意事项
  1. 对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为 *这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为 *,请求将会失败。而将 Access-Control-Allow-Origin 的值设置为 http://foo.example,则请求将成功执行。
缺点

暂时还没想到,对于方案二的缺点欢迎大家留言提出

写在末尾

其实CROS跨域非常容易实现并解决,本文主要说的便是 withCredentials 携带Cookies的问题。

同时附赠在使用 vue-resource 的携带Cookie的方式:

1
2
3
4
5
6
7
8
9
10
11
12
// 利用 Interceptors 特性在全局进行配置
import VueResource from 'vue-resource';
Vue.use(VueResource);
Vue.http.interceptors.push(request => {
request.withCredentials = true
request.url = `${domain}${request.url}`
});

// 使用
uploadFile(file) {
this.$http.post('http://api.domain.com/upload', { file })
}

axios 中如何配置欢迎各位提供代码,这边没有实际测试就不贴代码了,不过参考链接请看,看完就知道该怎么做了:https://github.com/axios/axios#config-defaults

参考链接

Plupload
HTTP访问控制(CORS)