文件上传OSS三部曲(二)

:-}

本文讲解如何讲解客户端文件上传aliOSS。
要点
签名在服务端(php)完成,然后直接通过表单上传到OSS 。
背景
每个用OSS的用户,都会用到上传。由于是网页上传,其中包括一些APP里面的html5页面,对上传的需求很强烈,很多人采用的做法是用户在浏览器/APP上传到应用服务器,然后应用服务器再把文件上传到OSS。

这种方法有三个缺点
第一:上传慢,先上传到应用服务器,再上传到OSS,网络传送多了一倍,而且OSS是采用BGP带宽,能保证各地各运营商的速度。
第二:扩展性不好,如果后续用户多了,应用服务器会成为瓶颈。
第三:费用高,因为OSS上传流量是免费的。如果数据直传到OSS,不走应用服务器。那么将能省下几台应用服务器。

相对于文件上传三部曲(一)的操作,我们提出改进方案

客户端用JS直接签名,然后上传到OSS

OSS的PostObject API细节可以参照(看不懂没有关系):
https://docs.aliyun.com/#/pub/oss/api-reference/object&PostObject
这里有一个很严重的安全隐患。就是OSS AccessId/AccessKey暴露在前端页面。可以随意拿到accessid/accesskey. 这是非常不安全的做法
将此例子进化,签名及上传policy从后端php代码取。
请求逻辑是:
1.客户端要上传图片时,到应用服务器取上传的policy及签名
2.客户端拿到签名直接上传到OSS

示例

直接用网页访问:http://oss-demo.aliyuncs.com/oss-h5-upload-js-php/index.html

用手机测试该上传是否有效。二维码:可以用手机(微信,QQ,手机浏览器等)扫一扫试试(这个不是广告,只是上述网址的二维码。这为了让大家看一下这个实现能在手机端完美运行。)
文件上传是上传到一个测试的公共 bucket , 会定时清理,所以不要传一些敏感及重要数据

原理

设置plupload 上传参数如下:

1
2
3
4
5
6
7
multipart_params: {
'key' : key + '${filename}' //后面会介绍到,key是应用服务器返回的,指定用户必须以这个前缀上传文件。
'policy': policyBase64,
'OSSAccessKeyId': accessid,
'success_action_status' : '200', //让服务端返回200,不然,默认会返回204
'signature': signature,
},

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

1
2
3
4
5
6
7
8
9
10
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(这个是我设计的范围,并不一定要求是以下的格式,但是必须有signature, accessid, policy这三个值

1
2
3
4
5
{"accessid":"6MKOqxGiGU4AUk44",
"host":"http://post-test.oss-cn-hangzhou.aliyuncs.com",
"policy":"eyJleHBpcmF0aW9uIjoiMjAxNS0xMS0wNVQyMDoyMzoyM1oiLCJjxb25kaXRpb25zIjpbWyJjcb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXItZGlyXC8iXV19",
"signature":"I2u57FWjTKqX\/AE6doIdyff151E=",
"expire":1446726203,"dir":"user-dir/"}

第一个变量accessid: 指的用户请求的accessid,注意单知道accessid, 对数据不会有影响。
第二个变量host: 指的是用户要往哪个域名发往上传请求。
第三个变量policy:指的是用户表单上传的策略policy, 是经过base64编码过的字符串
第四个变更signature:是对上述第三个变量policy签名后的字符串
第五个变量expire:指的是当前上传策略失效时间,这个变量,并不是用来发送到OSS,因为这个已经指定在policy里面,这个变量的含义,后面讲。

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

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

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

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

再看一下上面policy 的内容比上面增加了starts-with, 这个指定此次上传的文件名,必须是user-dir开头(这个字符串,用户可以自己指定)
 为什么要增加这个的含义是:很多场景,一个应用一个bucket,不同用户的数据,为了防止数字覆盖,每个人上传到OSS,可以有特定的前缀。那么问题来了,那用户获取到这个policy后,是不是在失效期内,都能修改上传前缀,从而上传到别人的目录呢?所以,应用服务器可以在上传时就指定让用户传文件时,必须是某个前缀。如果用户拿到了policy他也没有办法上传别人的前缀上。保证了数据的安全性。

代码下载:Touch Me



注意,下面实战讲解:
注意,下面实战讲解:
注意,下面实战讲解:
最近在做一个创业公司网站的新闻模块,新闻视频采用了这种直传的方式。
在这贴上我的代码好了
控制器:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
* 视频上传
* */
public function gmt_iso8601($time) {
$dStr = date('Y-m-d H:i:s',$time);
$expiration = str_replace(" ","T",$dStr);
return $expiration."Z";
}
public function ueditor(){
return view('admin.news.ueditor');
}
public function upload()
{
$id= 'xxxxxxxxxxxxx;
$key= 'CCCCCCCCCCCCCCCC';
$host = 'http://youself.oss-cn-beijing.aliyuncs.com';
$now = time();
$expire = 30; //设置该policy超时时间是30s. 即这个policy过了这个有效时间,不能访问
$end = $now + $expire;
$expiration = $this->gmt_iso8601($end);
$dir = 'news/video/';
//最大文件大小.用户可以自己设置
$condition = array(0=>'content-length-range', 1=>0, 2=>1048576000);
$conditions[] = $condition;
//表示用户上传的数据,必须是以$dir开始, 不然上传会失败,这一步不是必须项,只为了安全起见,防止用户通过policy上传到别人的目录
$start = array(0=>'starts-with', 1=>'$key', 2=>$dir);
$conditions[] = $start;
$arr = array('expiration'=>$expiration,'conditions'=>$conditions);
//echo json_encode($arr);
//return;
$policy = json_encode($arr);
$base64_policy = base64_encode($policy);
$string_to_sign = $base64_policy;
$signature = base64_encode(hash_hmac('sha1', $string_to_sign, $key, true));
$response = array();
$response['accessid'] = $id;
$response['host'] = $host;
$response['policy'] = $base64_policy;
$response['signature'] = $signature;
$response['expire'] = $end;
//这个参数是设置用户上传指定的前缀
$response['dir'] = $dir;
echo json_encode($response);
}

blade模板

1
2
3
4
5
6
7
8
@section('script')
//引入js
<script src='{{ asset('js/htmlToOSS/lib/plupload-2.1.2/js/plupload.full.min.js')}}'></script>" +
"<script src='{{ asset('js/htmlToOSS/upload.js')}}'></script>
@endsection
```
form表单:






上传文件名字保持本地文件名字
上传文件名字是随机文件名, 后缀保留

      <h4>如有新闻视频,在此飞快上传:</h4>
      <div id="ossfile">你的浏览器不支持flash,Silverlight或者HTML5!因此不能使用该浏览器进行视频文件上传!</div>

      <br/>

      <div id="container" style="margin-bottom: 20px;">
          <a id="selectfiles" href="javascript:void(0);" class='btn'>选择文件</a>
          <a id="postfiles" href="javascript:void(0);" class='btn'>开始上传</a>
      </div>

      <pre id="console"></pre>
      </td>
  </tr>
</table>

```

附录
OSS官链
OSS用户成长

Thanks