本示例讲解如何在服务端通过PHP代码完成签名,然后通过表单直传数据到OSS。

说明
本示例无法实现分片上传与断点续传。

背景

采用JavaScript客户端直接签名(参见JavaScript客户端签名直传)有一个严重的安全隐患:OSS AccessKey暴露在前端页面,这是非常不安全的做法。因此,OSS提供了服务端签名后直传的方案。

Demo

您可以通过样例体验服务端签名后直传效果:PC浏览器测试样例

原理介绍

服务端签名后直传的逻辑图如下:



流程如下:

  1. 用户发送上传Policy请求到应用服务器。
  2. 应用服务器返回上传Policy和签名给用户。
  3. 用户使用Plupload直接上传数据到OSS。

步骤 1:下载并安装Plugload

Plupload是一款简单易用且功能强大的文件上传工具, 支持多种上传方式,包括html5、flash、silverlight,、html4。它会智能检测当前环境,选择最适合的上传方式,并且会优先采用Html5方式。请参见Plupload官网进行下载和安装。

步骤 2:下载应用服务器代码

步骤 3:修改配置文件

本示例采用PHP编写。将下载包解压后,修改以下文件:
  • php/get.php文件:
    $id= '<yourAccessKeyId>';
     $key= '<yourAccessKeySecret>';
     $host = 'http://post-test.oss-cn-hangzhou.aliyuncs.com
    • $id:您的AccessKeyId
    • $key:您的AessKeySecret
    • $host:格式为BucketName.Endpoint,例如post-test.oss-cn-hangzhou.aliyuncs.com
      说明
      关于Endpoint的介绍,请参见Endpoint(访问域名)
  • upload.js文件

    将变量severUrl改成服务器部署的地址,例如http://abc.com:8080/oss-h5-upload-js-php/get.php

步骤 4:设置CORS

HTML表单直接上传到OSS会产生跨域请求。为了浏览安全,需要为Bucket设置跨域规则(CORS),支持Post方法。

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



说明
在低版本IE浏览器,Plupload会以Flash方式执行。您需要设置crossdomain.xml ,设置方法请参见OSS Web直传—使用Flash上传

步骤 5:体验服务端签名后直传

  1. 将应用服务器代码zip包解压到Web根目录下。
  2. 在Web浏览器中输入<Web应用服务器地址>/oss-h5-upload-js-php/index.html,例如http://abc.com:8080/oss-h5-upload-js-php/index.html
  3. 选择一个或多个文件进行上传。
  4. 上传成功后,通过控制台查看上传结果。

核心代码解析

  • 设置成随机文件名

    如果想在上传时固定设置成随机文件名,后缀保持跟客户端文件一致,可以将函数改为:

    function check_object_radio() {
        g_object_name_type = 'random_name';
    }
  • 设置成用户的文件名

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

    function check_object_radio() {
        g_object_name_type = 'local_name';
    }
  • 设置上传目录

    上传的目录由服务端(即PHP)指定, 每个客户端只能上传到指定的目录,实现安全隔离。下面的代码是将上传目录改成abc/,注意目录必须以正斜线(/)结尾。

    $dir = 'abc/';
  • 设置上传过滤条件

    您可以利用Plupload的属性filters设置上传的过滤条件,如设置只能上传图片、上传文件的大小、不能有重复上传等。

    var uploader = new plupload.Uploader({
        ……
        filters: {
            mime_types : [ //只允许上传图片和zip文件
            { title : "Image files", extensions : "jpg,gif,png,bmp" },
            { title : "Zip files", extensions : "zip" }
            ], 
            max_file_size : '400kb', //最大只能上传400KB的文件
            prevent_duplicates : true //不允许选取重复文件
        },
    • mime_types:限制上传的文件后缀
    • max_file_size:限制上传的文件大小
    • prevent_duplicates:限制不能重复上传
      说明
      filters过滤条件不是必须的。如果不想设置过滤条件,只要把该项注释即可。
  • 获取上传后的文件名

    如果要知道文件上传成功后的文件名,可以用Plupload调用FileUploaded事件获取,如下所示:

    FileUploaded: function(up, file, info) {
                if (info.status == 200)
                {
                    document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = 'upload to oss success, object name:' + get_uploaded_object_name(file.name);
                }
                else
                {
                    document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = info.response;
                }
        }

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

    get_uploaded_object_name(file.name)
  • 上传签名

    JavaScript可以从服务端获取policyBase64、accessid、signature这三个变量,获取这三个变量的核心代码如下:

    phpUrl = './php/get.php'
            xmlhttp.open( "GET", phpUrl, false );
            xmlhttp.send( null );
            var obj = eval ("(" + xmlhttp.responseText+ ")");
            host = obj['host']
            policyBase64 = obj['policy']
            accessid = obj['accessid']
            signature = obj['signature']
            expire = parseInt(obj['expire'])
            key = obj['dir']
    xmlhttp.responseText解析如下:
    说明
    以下仅为示例,并不要求必须是相同的格式,但是必须有accessid、policy、signature这三个值。
    {"accessid":"6MKOqxGiGU4AUk44",
    "host":"http://post-test.oss-cn-hangzhou.aliyuncs.com",
    "policy":"eyJleHBpcmF0aW9uIjoiMjAxNS0xMS0wNVQyMDoyMzoyM1oiLCJjxb25kaXRpb25zIjpbWyJjcb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXItZGlyXC8iXV19",
    "signature":"I2u57FWjTKqX/AE6doIdyff151E=",
    "expire":1446726203,"dir":"user-dir/"}
    • accessid:用户请求的accessid。
    • host:用户要往哪个域名发送上传请求。
    • policy:用户表单上传的策略(Policy),是经过base64编码过的字符串。
    • signature:对变量policy签名后的字符串。
    • expire:上传策略失效时间,在PolicyText里指定。在失效时间之前,都可以利用此Policy上传文件,所以没有必要每次上传都去服务端获取签名。
      说明
      为了减少服务端的压力,设计思路是:初始化上传时,每上传一个文件后,获取一次签名。然后再上传时,比较当前时间与签名时间,看签名时间是否失效。如果失效了,就重新获取一次签名,如果没有失效,就使用之前的签名。这里就用到了变量expire,核心代码如下:
      now = timestamp = Date.parse(new Date()) / 1000;
      [color=#000000]//可以判断当前expire是否超过了当前时间,如果超过了当前时间,就重新取一次签名,缓冲时间为3[/color]
          if (expire < now + 3)
      {  
           ..... 
           phpUrl = './php/get.php'
           xmlhttp.open( "GET", phpUrl, false );
           xmlhttp.send( null );
           ......
      }
      return .

    解析Policy的内容如下:

    {"expiration":"2015-11-05T20:23:23Z",
    "conditions":[["content-length-range",0,1048576000],
    ["starts-with","$key","user-dir/"]]
    说明
    Policy的详细信息请参见Policy基本元素

    上面Policy中增加了starts-with,用来指定此次上传的文件名必须以user-dir开头,用户可自行指定此字符串。增加starts-with的原因是:在很多场景下,一个应用对应一个Bucket,为了防止数字覆盖,每个用户上传到OSS的文件都可以有特定的前缀。这样就存在一个问题,用户获取到这个Policy后,在失效期内都能修改上传前缀,从而上传到别人的目录下。为了解决这个问题,可以设置应用服务器在上传时就指定用户上传的文件必须是某个前缀。这样如果用户获取到了Policy也没有办法上传到别人的前缀上,从而保证了数据的安全性。

总结

本示例中,web端向服务端请求签名,然后直接上传,不会对服务端产生压力,而且安全可靠。但是这个示例有个问题,就是用户上传了多少文件,上传了什么文件,服务端并不能马上知道,如果想实时了解用户上传了什么文件,可以采用服务端签名直传并设置上传回调