服务端签名后直传的原理大致如下:
- 用户发送上传Policy请求到应用服务器。
- 应用服务器返回上传Policy和签名给用户。
- 用户直接上传数据到OSS。
后端功能代码
创建 alioss直传文件获取签名方法并设置回调
获取签名类
<?php
namespace app\index\controller;
use think\Controller;
use think\Request;
class Alioss
{
public function AliossPolicy(Request $request)
{
$oss = array();
$oss = config('aliyun_oss');//alioss 配置参数
$data = $request->get();//url中get方式传来的信息 可根据实际情况进行获取
$id= $oss['KeyId'];
$key= $oss['KeySecret'];
$host = 'https://'.$oss['Bucket'].'.'.$oss['Endpoint'];
$callbackUrl = 'https://'.$_SERVER['HTTP_HOST'].DS.'index/index/callback';//回调地址
$callback_param = array('callbackUrl'=>$callbackUrl,
'callbackBody'=>'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
'callbackBodyType'=>"application/x-www-form-urlencoded");
$callback_string = json_encode($callback_param);
$base64_callback_body = base64_encode($callback_string);
$now = time();
$expire = 10; //设置该policy超时时间是10s. 即这个policy过了这个有效时间,将不能访问
$end = $now + $expire;
$expiration = $this->gmt_iso8601($end);
$dir = '/uploads/'.date('Ym',time()).'/';//文件在oss中保存目录
//最大文件大小.用户可以自己设置
$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['callback'] = $base64_callback_body;
$response['dir'] = $dir;
return json($response);
}
private function gmt_iso8601($time) {
$dtStr = date("c", $time);
$mydatetime = new \DateTime($dtStr);
$expiration = $mydatetime->format(\DateTime::ISO8601);
$pos = strpos($expiration, '+');
$expiration = substr($expiration, 0, $pos);
return $expiration."Z";
}
}
回调类
<?php
namespace app\index\controller;
use think\Controller;
use think\Request;
class Alioss
{
public function callback(Request $request)
{
// 1.获取OSS的签名header和公钥url header
$authorizationBase64 = "";
$pubKeyUrlBase64 = "";
/*
* 注意:如果要使用HTTP_AUTHORIZATION头,你需要先在apache或者nginx中设置rewrite,以apache为例,修改
* 配置文件/etc/httpd/conf/httpd.conf(以你的apache安装路径为准),在DirectoryIndex index.php这行下面增加以下两行
RewriteEngine On
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]
* */
if (isset($_SERVER['HTTP_AUTHORIZATION']))
{
$authorizationBase64 = $_SERVER['HTTP_AUTHORIZATION'];
}
if (isset($_SERVER['HTTP_X_OSS_PUB_KEY_URL']))
{
$pubKeyUrlBase64 = $_SERVER['HTTP_X_OSS_PUB_KEY_URL'];
}
if ($authorizationBase64 == '' || $pubKeyUrlBase64 == '')
{
header("http/1.1 403 Forbidden");
exit();
}
// 2.获取OSS的签名
$authorization = base64_decode($authorizationBase64);
// 3.获取公钥
$pubKeyUrl = base64_decode($pubKeyUrlBase64);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $pubKeyUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
$pubKey = curl_exec($ch);
if ($pubKey == "")
{
//header("http/1.1 403 Forbidden");
exit();
}
// 4.获取回调body
$body = file_get_contents('php://input');
// 5.拼接待签名字符串
$authStr = '';
$path = $_SERVER['REQUEST_URI'];
$pos = strpos($path, '?');
if ($pos === false)
{
$authStr = urldecode($path)."\n".$body;
}
else
{
$authStr = urldecode(substr($path, 0, $pos)).substr($path, $pos, strlen($path) - $pos)."\n".$body;
}
// 6.验证签名
$ok = openssl_verify($authStr, $authorization, $pubKey, OPENSSL_ALGO_MD5);
if ($ok == 1)
{
$param = $request->param();
//根据返回的信息 $param 根据需要保存自数据库
}
else{
exit();
}
}
}
前端javascript功能代码
//policyurl 获取签名的地址
var policyurl = '/index/index/policy',
accessid = '',
accesskey = '',
host = '',
policyBase64 = '',
signature = '',
callbackbody = '',
filename = '',
key = '',
expire = 0,
now = timestamp = Date.parse(new Date()) / 1000;
function send_request() {
autoname = random_string(10);
var xmlhttp = null;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else if (window.ActiveXObject) {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
if (xmlhttp != null) {
xmlhttp.open("GET", policyurl + autoname, false);
xmlhttp.send(null);
return xmlhttp.responseText
} else {
alert('浏览器不支持此操作');
}
};
function upload(){
now = timestamp = Date.parse(new Date()) / 1000;
if (expire < now + 3) {
body = send_request()
var obj = eval("(" + body + ")");
host = obj['host']
policyBase64 = obj['policy']
accessid = obj['accessid']
signature = obj['signature']
expire = parseInt(obj['expire'])
callbackbody = obj['callback']
key = obj['dir']
}
var file = $("#selectfiles")[0].files[0];//
var suffix = get_suffix(file.name);
//组装发送数据
var request = new FormData();
request.append('name', file.name);
request.append("key", key + autoname + suffix);//文件名字,可设置路径
request.append("policy", policyBase64);//policy规定了请求的表单域的合法性
request.append("OSSAccessKeyId", accessid);//Bucket 拥有者的Access Key Id。
request.append("success_action_status", '200');// 让服务端返回200,不然,默认会返回204
request.append("callback", callbackbody);//回调,非必选,可以在policy中自定义
request.append("Signature", signature);//根据Access Key Secret和policy计算的签名信息,OSS验证该签名信息从而验证该Post请求的合法性
request.append('file', file);//需要上传的文件 file
$.ajax({
url: host,
data: request,
processData: false,//默认true,设置为 false,不需要进行序列化处理
cache: false,//设置为false将不会从浏览器缓存中加载请求信息
async: false,//发送同步请求
contentType: false,//避免服务器不能正常解析文件---------具体的可以查下这些参数的含义
dataType: 'json',//不涉及跨域 写json即可
type: 'post',
success: function (callbackHost, request) { //callbackHost:success,request中就是 回调的一些信息,包括状态码什么的
var fileurl = callbackHost.fullurl; //上传完成后的文件oss地址
},
error: function (returndata) {
alert("upload failed");
}
});
}
function random_string(len) {
len = len || 32;
var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
var maxPos = chars.length;
var pwd = '';
for (i = 0; i < len; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos));
}
return Date.parse(new Date()) / 1000 + '_' + pwd;
}
function get_suffix(filename) {
pos = filename.lastIndexOf('.')
suffix = ''
if (pos != -1) {
suffix = filename.substring(pos)
}
return suffix;
}