最近重新看了一下OSS的文件上传,看到了阿里云提供临时访问权限管理服务,通过获取自定义时效和访问权限的临时身份凭证,安全令牌(STS Token),解决了账号泄露问题,同时对客户端影响较少也能保证账号安全的解决方法。
先看一下官方文档:
在客户端直接上传文件到OSS
使用STS临时访问凭证访问OSS,
这里主要介绍STS的获取步骤
------------------------------------------------分割线------------------------------------------------
**首先我们根据文档步骤操作(具体自己看文档): **
左侧导航栏—>选择权限管理—>权限策略—>创建权限策略—>单击脚本编辑
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"oss:*"
],
"Resource": [
"acs:oss:*:*:mybucket/*"
]
}
]
}
关于权限策略中包含版本号(Version)和授权语句(Statement),以及授权语句中包含的授权效力(Effect)、操作(Action)、资源(Resource)以及限制条件(Condition,可选项)等更多信息
(具体文档参看:OSS的RAM Policy的常见示例)
<?php
/**
* @Author: [FENG] <1161634940@qq.com>
* @Date: 2024-05-05T17:03:00+08:00
* @Last Modified by: [FENG] <1161634940@qq.com>
* @Last Modified time: 2024-06-30 13:03:15
*/
namespace feng;
/**
* 阿里云STS接口
*/
class Alists
{
protected $url = 'https://sts.aliyuncs.com';
protected $accessKeyId = ''; // RAM 用户accessKeyId
protected $accessKeySecret = ''; // RAM accessKeySecret
protected $roleArn = 'acs:ram::$accountID:role/$roleName';//指定角色的 ARN ,角色策略权限
protected $roleSessionName = 'ceshi';//用户自定义参数。此参数用来区分不同的 token,可用于用户级别的访问审计。格式:^[a-zA-Z0-9\.@\-_]+$
protected $durationSeconds = '1800';//指定的过期时间
public function __construct($type = 'xxx')
{
$this->setRoleArn($type);
}
public function sts()
{
$action = 'AssumeRole';//通过扮演角色接口获取令牌
date_default_timezone_set('UTC');
$param = array(
'Format' => 'JSON',
'Version' => '2015-04-01',
'AccessKeyId' => $this->accessKeyId,
'SignatureMethod' => 'HMAC-SHA1',
'SignatureVersion' => '1.0',
'SignatureNonce' => $this->getRandChar(8),
'Action' => $action,
'RoleArn' => $this->roleArn,
'RoleSessionName' => $this->roleSessionName,
'DurationSeconds' => $this->durationSeconds,
'Timestamp' => date('Y-m-d') . 'T' . date('H:i:s') . 'Z'
//'Policy'=>'' //此参数可以限制生成的 STS token 的权限,若不指定则返回的 token 拥有指定角色的所有权限。
);
$param['Signature'] = $this->computeSignature($param, 'POST');
$response = http_request($this->url, 'POST', $param);//curl post请求
$result = json_decode($response, true);
if (isset($result['Credentials'])) {
$utc_time = strtotime($result['Credentials']['Expiration']);
date_default_timezone_set('PRC');
return [
'accessKeySecret' => $result['Credentials']['AccessKeySecret'] ?? '',
'accessKeyId' => $result['Credentials']['AccessKeyId'] ?? '',
'expiration' => date('Y-m-d H:i:s', $utc_time) ?? '',
'securityToken' => $result['Credentials']['SecurityToken'] ?? '',
];
} else {
return [];
}
}
public function params()
{
$config['dir'] = 'ceshi/'.date('Y').date('m').date('d').'/'; // 用户上传文件时指定的前缀。
$config['host'] = 'https://mybucket.oss-cn-hangzhou.aliyuncs.com'; // 请求地址
$config['callback_url'] = ''; // 回调地址(可不传)
$sts = $this->sts();
if ($sts) {
$config['key_id'] = $sts['accessKeyId'];
$config['key_secret'] = $sts['accessKeySecret'];
$config['sts_token'] = $sts['securityToken'];
}
$callback_param = array(
'callbackUrl' => $config['callback_url'],
'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
'callbackBodyType' => "application/x-www-form-urlencoded"
);
$callback_string = base64_encode(json_encode($callback_param));
$expire = strtotime('+30 seconds');
//最大文件大小.用户可以自己设置
$condition = array(0=>'content-length-range', 1=>0, 2=>1048576000);
$conditions[] = $condition;
// 表示用户上传的数据,必须是以$dir开始,不然上传会失败,这一步不是必须项,只是为了安全起见,防止用户通过policy上传到别人的目录。
$start = array(0=>'starts-with', 1=>'$key', 2=>$config['dir']);
$conditions[] = $start;
$arr = [
'expiration' => date('Y-m-d', $expire) . 'T' . date('H:i:s', $expire) . 'Z',
'conditions' => $conditions
];
$base64_policy = base64_encode(json_encode($arr));
$signature = base64_encode(hash_hmac('sha1', $base64_policy, $config['key_secret'], true));
$response = [
'accessid' => $config['key_id'],
'sts_token' => $config['sts_token'], // 这个参数是设置用户上传文件时指定的前缀。
// 'callback' => $callback_string, //上传成功回调地址
'host' => $config['host'],
'dir' => $config['dir'], // 这个参数是设置用户上传文件时指定的前缀。
'policy' => $base64_policy,
'signature' => $signature,
];
echo json_encode(['code'=>1,'msg'=>'获取数据成功','data'=>$response]);
exit;
}
protected function computeSignature($parameters, $setMethod)
{
ksort($parameters);
$canonicalizedQueryString = '';
foreach ($parameters as $key => $value) {
$canonicalizedQueryString .= '&' . $this->percentEncode($key) . '=' . $this->percentEncode($value);
}
$stringToSign = $setMethod . '&%2F&' . $this->percentencode(substr($canonicalizedQueryString, 1));
$signature = $this->getSignature($stringToSign, $this->accessKeySecret . '&');
return $signature;
}
public function getSignature($source, $accessSecret)
{
return base64_encode(hash_hmac('sha1', $source, $accessSecret, true));
}
protected function percentEncode($str)
{
$res = urlencode($str);
$res = preg_replace('/\+/', '%20', $res);
$res = preg_replace('/\*/', '%2A', $res);
$res = preg_replace('/%7E/', '~', $res);
return $res;
}
public function getRandChar($length)
{
$str = null;
$strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
$max = strlen($strPol) - 1;
for ($i = 0; $i < $length; $i++) {
$str .= $strPol[rand(0, $max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
}
return $str;
}
protected function setRoleArn($type)
{
if ($type == 'xxx') {//根据入参使用不同的策略,当然这里还可以有其他写法兼容更多的策略的情况
$this->roleArn = 'acs:ram::*:role/*';
}
}
}
$re = (new \feng\Alists())->params(); // STS获取web直传所需数据
返回结果
{
"Credentials": {
"AccessKeyId": "STS.xxxxxxxxxxxxx****",//访问密钥标识
"AccessKeySecret": "xxxxxxxxxx****",//访问密钥
"Expiration": "2019-04-09T11:52:19Z",//失效时间
"SecurityToken": "********"//安全令牌
},
"AssumedRoleUser": {
"arn": "acs:sts::123456765456****:assumed-role/AdminRo步骤六:使用临时访问凭证上传文件至OSSle/client",
"AssumedRoleUserId":"1234567121****:alice"
},
"RequestId": "xxxxxxxxxxxxxxxx"
}
这里主要介绍使用WEB直传,获取到的临时账户与子账户用法一致;
重要提示:使用formData中(append的顺序也会影响上传失败)
否则会报You have no right to access this object because of bucket acl.
错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>OSS web直传(STS版本)</title>
</head>
<body>
<input type="text" id="input_id" value="">
<!-- <input type="file" id="fileInput" style="display: none;" /> -->
<button class="ossupload"
data-url="http://shangcheng.test/Alists/params"
data-extensions="jpeg,jpg,png,mp4"
data-input-id="input_id">上传文件</button>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.1.1/jquery.js"></script>
<script type="text/javascript">
/**
* <button
* class="ossupload" // 选择器
* data-url="http://shangcheng.test/Alists/params" // 进行接口验签
* data-extensions="jpeg,jpg,png,mp4" // 允许的文件类型
* data-input-id="input_id"> // 填充input
* 上传文件
* </button>
*/
$(document).on('click', '.ossupload', function() {
var that = this;
// 创建一个input元素
var fileInput = document.createElement("input");
fileInput.type = "file"; // 设置input类型为text
fileInput.click();
fileInput.addEventListener('change', function() {
// 获取文件并上传逻辑
const file = this.files[0];
if (file == undefined) {
alert("请选择文件。");
return false;
}
api_url = that.getAttribute("data-url") ? that.getAttribute("data-url") : ''; // 进行接口验签
extensions = that.getAttribute("data-extensions") ? that.getAttribute("data-extensions") : 'jpeg,jpg,png,mp4'; // 允许的文件类型
input_id = that.getAttribute("data-input-id") ? that.getAttribute("data-input-id") : '';
// 检查文件大小
var maxSize = 1024 * 1024 * 10; // 设置最大文件大小(这里设为1MB)
if (file.size > maxSize) {
alert("文件大小不能超过10MB。");
return false;
}
// 使用AJAX上传文件或其他上传逻辑
var file_name = file.name;
var suffix = file_name.split('.').pop();
// 检查文件类型
if (extensions.split(',').indexOf(suffix) === -1) {
alert("只能上传" + extensions + "后缀文件。");
return false;
}
//发送ajax请求我方php后端获取上传OSS时必要的参数信息
$.ajax({
type: "GET", //提交方式
url: api_url, //路径
dataType: "json",
success: function(res) { //返回数据根据结果进行相应的处理
res = res.data;
//上返回的参数使用formData中(append的顺序也会影响上传失败)
const formData = new FormData();
formData.append('ossAccessKeyId', res.accessid);
if (res.sts_token) formData.append('x-oss-security-token', res.sts_token); // STS
if (res.callback) formData.append('callback', res.callback);
formData.append('policy', res.policy);
formData.append('signature', res.signature);
formData.append('success_action_status', 200); // 成功后返回的操作码
formData.append('key', res.dir + file_name); // 文件信息放最后
formData.append('file', file);
//接收到服务端返回的签名参数,开始通过另一个Ajax请求来上传文件到OSS
//成功获取签名后上传文件到阿里云OSS
$.ajax({
type: 'POST', //提交方式
url: res.host, //路径
dataType: 'json',
processData: false,
cache: false,
async: true, //这里要设置异步上传,才能成功调用myXhr.upload.addEventListener('progress',function(e){}),progress的回掉函数
contentType: false,
//关键是要设置contentType 为false,不然发出的请求头 没有boundary
//该参数是让jQuery去判断contentType
data: formData, //要发送到OSS数据,使用我这个ajax的格式可避开跨域问题。
xhr: function() {
var myXhr = $.ajaxSettings.xhr();
if (myXhr.upload) { //检查上传的文件是否存在
myXhr.upload.addEventListener('progress', function(e) {
var loaded = e.loaded; //已经上传大小情况
var total = e.total; //附件总大小
var percent = Math.floor(100 * loaded / total) + "%"; //已经上传的百分比
that.innerHTML = '<span>已上传:' + percent + "</span>";
// console.log("已经上传了:"+percent);
}, false); // for handling the progress of the upload
}
return myXhr;
},
success: function(res2) { //返回数据根据结果进行相应的处理
if (res2.status == "ok" && document.getElementById(input_id)) {
document.getElementById(input_id).value = res.host + '/' + res.dir + file_name
}
console.log(res2); //返回success:ok 说明你就上传成功了
// setTimeout(function(){
// that.innerHTML = '上传文件';
// }, 2000)
}
});
}
});
})
})
</script>
</html>
参考文档:
1、使用阿里云sts鉴权模式实践(PHP)
2、在客户端直接上传文件到OSS
3、使用STS临时访问凭证访问OSS
4、Browser.js快速入门,SDK中快速使用OSS服务
本文为冯奎原创文章,转载无需和我联系,但请注明来自冯奎博客fengkui.net
最新评论