阅读801 返回首页    go 阿里云


服务端签名后直传__Web端直传实践_最佳实践_对象存储 OSS-阿里云

背景

请参考 Web端直传实践 中的背景介绍。

采用JS客户端直接签名有一个很严重的安全隐患。就是OSS AccessId/AccessKey暴露在前端页面。可以随意拿到AccessId/AccessKey,这是非常不安全的做法。 本文将此例子进化,签名及上传policy从后端php代码取。

请求逻辑是:

  1. 客户端要上传图片时,到应用服务器取上传的policy及签名。
  2. 客户端拿到签名直接上传到OSS。

示例

用户电脑浏览器测试样例:点击这里打开示例程序

用手机测试该上传是否有效。二维码:可以用手机(微信、QQ、手机浏览器等)扫一扫试试(这个不是广告,只是上述网址的二维码。这为了让大家看一下这个实现能在手机端完美运行)。

代码下载

点击这里:oss-h5-upload-js-php.zip

例子采用后端签名,语言是用PHP。

其他语言的用法:

  1. 下载对应的语言示例。
  2. 修改示例代码,如设置监听的端口等,然后运行。
  3. 在oss-h5-upload-js-php.zip里面的upload.js, 将里面的变量severUrl改成第二步部署的地址。如severUrl = http:/1.2.3.4:8080或者serverUrl=https://abc.com/post/

原理

上传采用OSS PostObject方法, 用pupload在浏览器构造PostObject请求,发往OSS。签名在服务端实现,即在(PHP)完成,相同道理,服务端可以以JAVA、.NET、Ruby、GO、python等语言编写,核心逻辑就是构造Post签名。本例子提供了JAVA、PHP例子。

请求逻辑是:

  1. 网页通过JS 向服务端请求签名。
  2. JS获取到签名后,通过pupload 上传到OSS。

快速使用

只要以下两步,就能实现文件快速通过网页上传到OSS。

  1. 设置成自己的id、key、bucket。
    设置方法:修改php/get.php, 将变量$id设成AccessKeyId, $key设置成AccessKeySecret, $host设置成:bucket+endpoint

    说明:关于endpoint,请参见 基本概念介绍

    1. $id= 'xxxxxx';
    2. $key= 'xxxxx';
    3. $host = 'https://post-test.oss-cn-hangzhou.aliyuncs.com
  2. 为了浏览安全,必须为bucket设置CORS,参照下文。

以下讲解一下核心逻辑。

设置成随机文件名

有时候要把用户上传的文件,设置成随机文件名,后缀保持跟客户端文件一致。例子里面,通过两个radio来区分, 如果想在上传时,就固定设置成随机文件名,可以将函数改成如下:

  1. function check_object_radio() {
  2. g_object_name_type = 'random_name';
  3. }

如果想在上传时,固定设置成用户的文件,可以将函数改成:

  1. function check_object_radio() {
  2. g_object_name_type = 'local_name';
  3. }

设置上传目录

上传的目录是由服务端(即PHP)指定的,这样的好处就是安全。 这样就能控制每个客户端只能上传到指定的目录,做到安全隔离。想要修改上传目录地址成abc/(必须以’/‘结尾),可以修改代码里面的php/get.php

  1. $dir = 'abc/';

设置上传文件过滤条件

有的时候需要设置上传的过滤条件,如可以设置上传只能上传图片,上传文件的大小,不能有重复上传等。这时可以利用filters参数。

  1. var uploader = new plupload.Uploader({
  2. ……
  3. filters: {
  4. mime_types : [ //只允许上传图片和zip文件
  5. { title : "Image files", extensions : "jpg,gif,png,bmp" },
  6. { title : "Zip files", extensions : "zip" }
  7. ],
  8. max_file_size : '400kb', //最大只能上传400kb的文件
  9. prevent_duplicates : true //不允许选取重复文件
  10. },

设置过滤条件,原理是利用plupload 的属性filters来设置。
上述值的设置含义:

  • mime_types:限制上传的文件后缀
  • max_file_size: 限制上传的文件大小
  • prevent_duplicates: 限制不能重复上传

注意:filters过滤条件不是必须的。如果不想设置过滤条件,只要把该项注释即可。

获取上传后的文件名

如果要知道文件上传成功后的文件名,pupload 会调用FileUploaded事件。 如下:

  1. FileUploaded: function(up, file, info) {
  2. if (info.status == 200)
  3. {
  4. document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = 'upload to oss success, object name:' + get_uploaded_object_name(file.name);
  5. }
  6. else
  7. {
  8. document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = info.response;
  9. }
  10. }

可以利用如下函数,得到上传到oss的文件名,其中file.name记录了上传本地文件的名字。

  1. get_uploaded_object_name(file.name)

上传签名

javaScript最主要是从后端取到policyBase64、accessid、signature这三个变量。 往后端取这三个变量核心代码如下:

  1. phpUrl = './php/get.php'
  2. xmlhttp.open( "GET", phpUrl, false );
  3. xmlhttp.send( null );
  4. var obj = eval ("(" + xmlhttp.responseText+ ")");
  5. host = obj['host']
  6. policyBase64 = obj['policy']
  7. accessid = obj['accessid']
  8. signature = obj['signature']
  9. expire = parseInt(obj['expire'])
  10. key = obj['dir']

现在解析一下xmlhttp.responseText(以下仅为示例,并不一定要求是以下的格式,但是必须有signature、accessid、policy这三个值)。

  1. {"accessid":"6MKOqxGiGU4AUk44",
  2. "host":"https://post-test.oss-cn-hangzhou.aliyuncs.com",
  3. "policy":"eyJleHBpcmF0aW9uIjoiMjAxNS0xMS0wNVQyMDoyMzoyM1oiLCJjxb25kaXRpb25zIjpbWyJjcb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXItZGlyXC8iXV19",
  4. "signature":"I2u57FWjTKqX/AE6doIdyff151E=",
  5. "expire":1446726203,"dir":"user-dir/"}
  • accessid: 指的用户请求的accessid。注意仅知道accessid, 对数据不会有影响。
  • host: 指的是用户要往哪个域名发往上传请求。
  • policy:指的是用户表单上传的策略policy,是经过base64编码过的字符串。
  • signature:是对上述第三个变量policy签名后的字符串。
  • expire:指的是当前上传策略失效时间,这个变量并不会发送到OSS,因为这个已经指定在policy里面,这个变量的含义,后面讲。

现在分析一下policy的内容,将其解码后的内容是:

  1. {"expiration":"2015-11-05T20:23:23Z",
  2. "conditions":[["content-length-range",0,1048576000],
  3. ["starts-with","$key","user-dir/"]]

这里有一个关键的地方,PolicyText指定了该Policy 上传失效的最终时间。即在这个失效时间之前,都可以利用这个policy上传文件,所以没有必要每次上传,都去后端取签名。为了减少后端的压力,这里的设计思路是:初始化上传时,每上传一个文件后,取一次签名。然后再上传时,将当前时间跟签名时间对比,看签名时间是否失效了。如果失效了,就重新取一次签名,如果没有失效就不取。这里就用到了变量expire。核心代码如下:

  1. now = timestamp = Date.parse(new Date()) / 1000;
  2. [color=#000000]//可以判断当前expire是否超过了当前时间,如果超过了当前时间,就重新取一下,3s 做为缓冲[/color]
  3. if (expire < now + 3)
  4. {
  5.    .....
  6.    phpUrl = './php/get.php'
  7.    xmlhttp.open( "GET", phpUrl, false );
  8.    xmlhttp.send( null );
  9.    ......
  10. }
  11. return .

上面policy 的内容增加了starts-with,用来指定此次上传的文件名必须是user-dir开头(这个字符串,用户可以自己指定)。

增加这个内容的背景是:在很多场景下,一个应用一个bucket,不同用户的数据,为了防止数字覆盖,每个用户上传到OSS的文件都可以有特定的前缀。那么问题来了,用户获取到这个policy后,在失效期内都能修改上传前缀,从而上传到别人的目录下。为了解决这个问题,可以设置应用服务器在上传时就指定用户上传的文件必须是某个前缀。这样如果用户拿到了policy也没有办法上传到别人的前缀上。保证了数据的安全性。

跨域安全设置

注意:一定要保证bucket属性CORS设置支持POST方法。因为这个HTML直接上传到OSS,会产生跨域请求。必须在bucket属性里面设置允许跨域。

设置如下图:

注意在IE低版本浏览器,pupload会以flash方式执行。必须设置crossdomain.xml ,设置方法可以参考:点击这里

总结

这个例子能做到网页端上传时,网页端向服务端请求签名,然后直接上传,不用对服务端产生压力。而且安全可靠。但是这个例子有一个特点,就是用户上传了多少文件,用户上传了什么文件,用户后端程序并不能马上知道,如果想实时知晓用户上传了什么文件,可以采用上传回调。请参考 服务端签名直传并设置上传回调

最后更新:2016-12-19 14:21:56

  上一篇:go JavaScript客户端签名直传__Web端直传实践_最佳实践_对象存储 OSS-阿里云
  下一篇:go 服务端签名直传并设置上传回调__Web端直传实践_最佳实践_对象存储 OSS-阿里云