这段时间陆陆续续上了好几个微信小程序,功能上都会用到文件上传功能(头像上传、证件照上传等)。在APP上传文件到云端的正确姿势中,我们介绍了我们认为安全的上传流程:
即将密钥保存在服务器,客户端每次向服务器申请一个一次性的signature,然后使用该signature作为凭证来上传文件。一般情况下,向阿里云OSS上传内容,又拍云作为灾备。
随着大家安全意识的增强,这种上传流程几乎已经成为标准姿势。但是,把这个流程在应用到微信小程序却有很多细节需要调整。这里把踩过的坑记录一下,希望能让有需要的同学少走弯路。
微信小程序无法直接读取文件内容进行上传
在我们第一版的上传流程方案中,我们的cds 签名发放服务
只实现了阿里云 PutObject 接口的signature发放. PutObject 上传是直接将需要上传的内容以二进流的方式 PUT 到云储存。
但是,微信小程序提供的文件上传API wx.uploadFile 要求文件通过 filePath
提供:
另一方面,微信小程序的 JS API 当前还比较封闭,无法根据 filePath
读取到文件内容,因此也无法通过 wx.request
直接发起网络请求的方式来实现文件上传。
考虑到 wx.uploadFile 本质上是一个 multipart/form-data
网络请求的封装,因此我们只需要实现一个与之对应的签名发放方式接口。阿里云OSS对应的上传接口是 PostObject, 又拍云对应的是其 FORM API. 以阿里云OSS为例,cds 服务
生成signature 代码如下:
func GetDefaultOSSPolicyBase64Str(bucket, key string) string {
policy := map[string]interface{}{
"expiration": time.Now().AddDate(3, 0, 0).Format("2006-01-02T15:04:05.999Z"),
"conditions": []interface{}{
map[string]string{
"bucket": bucket,
},
[]string{"starts-with", "$key", key},
},
}
data, _ := json.Marshal(&policy)
return base64.StdEncoding.EncodeToString(data)
}
func GetOSSPostSignature(secret string, policyBase64 string) string {
h := hmac.New(sha1.New, []byte(secret))
io.WriteString(h, policyBase64)
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
小程序端代码如下:
//使用说明
/**
* 1、引入该文件:const uploadFile = require('../../common/uploadAliyun.js');
* 2、调用如下:
* uploadImg: function () {
const params = {
_success: this._success
}
uploadFile.chooseImg(params);
},
_success: function(imgUrl){
this.setData({
cover_url: imgUrl,
})
},
*/
const uploadFile = {
_fail: function(desc) {
wx.showToast({
icon: "none",
title: desc
})
},
_success: function() {},
chooseImg: function(sendData) {
//先存储传递过来的回调函数
this._success = sendData._success;
var that = this;
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ["album", "carmera"],
success: function (res) {
that.getSign(res.tempFilePaths[0]);
},
fail: function (err) {
wx.showToast({
icon: "none",
title: "选择图片失败" + err
})
}
})
},
//获取阿里上传图片签名
getSign: function (path) {
var that = this;
wx.request({
url: 'https://somewhere/v2/cds/apply_upload_signature',
method: 'POST',
data: {
"content_type": "image/jpeg",
"signature_type": "oss_post",
"business": "xiaochengxu",
"file_ext": '.jpeg',
"count": '1'
},
success: function (res) {
let getData = res.data.data[0];
that.startUpload(getData, path);
},
fail: function (err) {
that._fail("获取签名失败" + JSON.stringify(err))
}
})
},
//拿到签名后开始上传
startUpload: function (getData, path) {
var that = this;
this.uploadAliYun({
filePath: path,
dir: 'wxImg/',
access_key_id: getData.oss_ext_param.access_key_id,
policy_base64: getData.oss_ext_param.policy_base64,
signature: getData.signature,
upload_url: getData.upload_url,
object_key: getData.oss_ext_param.object_key,
content_url: getData.content_url.origin
})
},
uploadAliYun: function(params) {
var that = this;
// if (!params.filePath || params.filePath.length < 9) {
if (!params.filePath) {
wx.showModal({
title: '图片错误',
content: '请重试',
showCancel: false,
})
return;
}
const aliyunFileKey = params.dir + params.filePath.replace('wxfile://', '');
const aliyunServerURL = params.upload_url;
const accessid = params.access_key_id;
const policyBase64 = params.policy_base64;
const signature = params.signature;
wx.uploadFile({
url: aliyunServerURL,
filePath: params.filePath,
name: 'file',
formData: {
'key': params.object_key,
'policy': policyBase64,
'OSSAccessKeyId': accessid,
'Signature': signature
},
success: function (res) {
if (res.statusCode != 204) {
that._fail("上传图片失败");
return;
}
that._success(params.content_url);
},
fail: function (err) {
that._fail(JSON.stringify(err));
},
})
}
}
module.exports = uploadFile;
使用阿里云OSS域名上传失败
解决签名问题后,发现使用阿里云OSS提供的上传域名无法上传成功,在微信后台尝试添加合法域名的时候,惊奇的发现阿里云OSS的域名直接被微信小程序封禁了:
显然是两个神仙在打架,作为草民只能见招拆招。解决办法就是在阿里云OSS -> bucket -> 域名管理
绑定用户域名:
此外,由于微信小程序已经升级为uploadFile的链接必须是https, 因此还需要在绑定用户域名后设置 证书托管
。
他山之石,可以攻玉
既然微信能够封禁用阿里云OSS的上传域名,那么微信也可以封禁你自定义的域名。根据以往经验(对天发誓,我们不是有意为之,我们也是受害者……),微信封禁域名一般都是一锅端,即发现一个子域名存在违规内容,那么整个域名都会被封禁。因此,一方面要从技术角度对上传的内容及时检查是否合规(如黄图扫描),另一方面提前做好域名规划,将业务接口域名与自定义的文件上传域名分开,这样即使上传域名被一锅端了,不至于是业务完全不可用。