[教程] 文件上传 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-refe...
这里有一个很严重的安全隐患。就是OSS AccessId/AccessKey暴露在前端页面。可以随意拿到accessid/accesskey. 这是非常不安全的做法
将此例子进化,签名及上传policy从后端php代码取。
请求逻辑是:
1.客户端要上传图片时,到应用服务器取上传的policy及签名
2.客户端拿到签名直接上传到OSS
示例
直接用网页访问:http://oss-demo.aliyuncs.com/oss-h5-upload...
用手机测试该上传是否有效。二维码:可以用手机(微信,QQ,手机浏览器等)扫一扫试试(这个不是广告,只是上述网址的二维码。这为了让大家看一下这个实现能在手机端完美运行。)
文件上传是上传到一个测试的公共 bucket , 会定时清理,所以不要传一些敏感及重要数据
原理
设置plupload 上传参数如下:
multipart_params: {
'key' : key + '${filename}' //后面会介绍到,key是应用服务器返回的,指定用户必须以这个前缀上传文件。
'policy': policyBase64,
'OSSAccessKeyId': accessid,
'success_action_status' : '200', //让服务端返回200,不然,默认会返回204
'signature': signature,
},
js最主要是从后端取到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(这个是我设计的范围,并不一定要求是以下的格式,但是必须有signature, accessid, policy这三个值
{"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的内容,将其解码后的内容是:
{"expiration":"2015-11-05T20:23:23Z",
"conditions":[["content-length-range",0,1048576000],
["starts-with","$key","user-dir\/"]]
这里有一个关键的地方,PolicyText指定了该Policy 上传失效的最终时间。即在这个失效时间之前,都可以利用这个policy上传文件,所以没有必要每次上传,都去后端取签名。减少后端的压力。在这里我的设计是:初始化上传时,每上传一个文件后,取一次签名。然后再上传时,将当前时间跟签名时间对比,看是签名时间是否失效了。如果失效了,就重新取一次签名,如果没有失效就不取。这里就用到了第五个变量expire
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
注意,下面实战讲解:
注意,下面实战讲解:
注意,下面实战讲解:
最近在做一个创业公司网站的新闻模块,新闻视频采用了这种直传的方式。
在这贴上我的代码好了
控制器:
/*
* 视频上传
* */
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模板
@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表单:
<table class="table table-bordered table-striped table-hover">
<tr>
<td>
<div style="display:none">
<form name=theform>
<input type="radio" name="myradio" value="local_name" checked=true/> 上传文件名字保持本地文件名字
<input type="radio" name="myradio" value="random_name" /> 上传文件名字是随机文件名, 后缀保留
</form>
</div>
<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>
Thanks
666
内网传输也很快
OSS权限那一块实在繁琐