全部产品
存储与CDN 数据库 安全 应用服务 数加·人工智能 数加·大数据基础服务 互联网中间件 视频服务 开发者工具 解决方案 物联网
对象存储 OSS

服务端签名后直传

更新时间:2017-11-15 15:33:41

背景

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

后端上传签名逻辑图如下:

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

  2. 客户端拿到签名直接上传到OSS。

后端上传签名示例

示例下载:

  • 电脑浏览器测试样例:单击下载

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

代码下载:

单击下载:oss-h5-upload-js-php.zip

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

其他语言的用法:

  1. 下载对应的语言示例。

  2. 修改示例代码,如设置监听的端口等,然后运行。

  3. 在oss-h5-upload-js-php.zip里面的upload.js, 将里面的变量serverUrl改成第二步部署的地址。如serverUrl = http://1.2.3.4:8080或者serverUrl=http://abc.com/post/

服务端构造Post签名原理

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

  1. 网页通过JS向服务端请求签名。

  2. JS获取到签名后,通过plupload 上传到OSS。

实现方法

  1. 设置成自己的id、key、bucket。

    修改php/get.php:

    • 将变量$id设成AccessKeyId

    • $key设置成AccessKeySecret

    • $host设置成:bucket+endpoint

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

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

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

    具体操作步骤请参见 设置跨域访问。设置如下图:

    设置跨域

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

核心逻辑详解

设置随机文件名

有时候要把用户上传的文件,设置成随机文件名,后缀保持跟客户端文件一致。例子里面,通过两个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/(必须以’/‘结尾)。

  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过滤条件不是必须的。如果不想设置过滤条件,只要把该项注释即可。

获取上传后的文件名

如果要知道文件上传成功后的文件名,可以用plupload调用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":"http://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也没有办法上传到别人的前缀上。保证了数据的安全性。

总结

本文档示例主要讲述网页端上传时,网页端向服务端请求签名,然后直接上传,不用对服务端产生压力。而且安全可靠。但是这个例子有一个特点,就是用户上传了多少文件,用户上传了什么文件,用户后端程序并不能马上知道,如果想实时知晓用户上传了什么文件,可以采用上传回调。本例子无法实现分片与断点。

本文导读目录