PHP使用阿里云STS临时访问凭证访问OSS

背景

最近重新看了一下OSS的文件上传,看到了阿里云提供临时访问权限管理服务,通过获取自定义时效和访问权限的临时身份凭证,安全令牌(STS Token),解决了账号泄露问题,同时对客户端影响较少也能保证账号安全的解决方法。

先看一下官方文档:
在客户端直接上传文件到OSS
使用STS临时访问凭证访问OSS
这里主要介绍STS的获取步骤

Dingtalk_20240628174111.jpg

------------------------------------------------分割线------------------------------------------------

**首先我们根据文档步骤操作(具体自己看文档): **

步骤一:创建RAM用户

步骤二:为RAM用户授予请求AssumeRole的权限

步骤三:创建RAM角色

步骤四:为RAM角色授予上传文件的权限

左侧导航栏—>选择权限管理—>权限策略—>创建权限策略—>单击脚本编辑

{
    "Version": "1",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "oss:*"
            ],
            "Resource": [
                "acs:oss:*:*:mybucket/*"
            ]
        }
    ]
}

关于权限策略中包含版本号(Version)和授权语句(Statement),以及授权语句中包含的授权效力(Effect)、操作(Action)、资源(Resource)以及限制条件(Condition,可选项)等更多信息
(具体文档参看:OSS的RAM Policy的常见示例

步骤五:使用RAM用户扮演RAM角色获取临时访问凭证(PHP)

<?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"
}

步骤六:使用临时访问凭证上传文件至OSS

这里主要介绍使用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服务

冯奎博客
请先登录后发表评论
  • latest comments
  • 总共0条评论