ThinkPHP

PHP实现阿里云OSS的STS认证(ThinkPHP6框架)

文 / 管理员 来源 / 原创 阅读 / 1227 3年前

背景


        最近做了一个项目,要实现需求客户端上传图片或者视频到阿里云OSS,使用STS认证方式(类似于客户端向服务端要认证签名),在做OSS的STS认证时发现关于php实现这块的技术播客写的普遍笼统,于是决定写一遍相对完整的。

关于STS


        阿里云 STS(Security Token Service)是阿里云提供的一种临时访问权限管理服务。

        通过 STS 服务,您所授权的身份主体(RAM 用户、RAM 用户组或 RAM 角色)可以获取一个自定义时效和访问权限的临时访问令牌。STS令牌持有者可以通过以下方式访问阿里云资源:

      1. 通过编程方式访问被授权的阿里云服务 API。

      2. 登录阿里云控制台操作被授权的云资源。
        *RAM (Resource Access Management) 是阿里云为客户提供的用户身份管理与资源访问控制服务。

为什么要使用RAM和STS


        RAM和STS需要解决的一个核心问题是如何在不暴露主账号的AccessKey的情况下安全的授权别人访问。因为一旦主账号的AccessKey暴露出去的话会带来极大的安全风险,别人可以随意操作该账号下所有的资源,盗取重要信息等。

        RAM提供一种长期有效的权限控制机制,通过分出不同权限的子账号,将不同的权限分给不同的用户,这样一旦子账号泄露也不会造成全局的信息泄露。但是,由于子账号在一般情况下是长期有效的,因此,子账号的AccessKey也是不能泄露的。

        相对于RAM提供的长效控制机制,STS提供的是一种临时访问授权。通过STS可以返回临时的AccessKey和Token,这些信息可以直接发给临时用户用来访问OSS/VOD服务等。一般来说,从STS获取的权限会受到更加严格的限制,并且拥有时间限制,因此这些信息泄露之后对于系统的影响也很小。

STS 访问点


用于 API 访问的 STS 接入点:https://sts.aliyuncs.com。

STS鉴权模式


        OSS可以通过阿里云STS (Security Token Service) 进行临时授权访问。阿里云STS是为云计算用户提供临时访问令牌的Web服务。通过STS,您可以为第三方应用或子用户(即用户身份由您自己管理的用户)颁发一个自定义时效和权限的访问凭证。
        STS鉴权模式具有以下优势:

      • 无需透露您的长期密钥(AccessKey)给第三方应用,只需生成一个访问令牌并将令牌交给第三方应用。您可以自定义这个令牌的访问权限及有效期限。

      • 无需关心权限撤销问题,访问令牌过期后访问权限会自动失效。

        以APP应用为例,交互流程如图所示(来自阿里云官方文档):

以下的这个官方文档先看一下,可能理解起来会更好一点


快速搭建移动应用直传服务 https://help.aliyun.com/document_detail/31920.html 

什么是STS     https://help.aliyun.com/document_detail/28756.html

STS临时授权访问OSS  https://help.aliyun.com/document_detail/100624.html

Browser.js SDK中快速使用OSS服务  https://help.aliyun.com/document_detail/32069.html



#直接步入正题,上代码

HTML代码

<div class="layui-form-item">
    <div class="layui-word-aux">缩略图和视频只有一个生效,默认视频的优先级更高,需同时上传</div>
    <label class="layui-form-label">视频预览</label>
    <div class="layui-input-block">
        <div class="layui-upload-drag xcvideo">
            {notempty name="$data.video"}
            <video controls="controls" height="200"><source src="{$data.video}" type="video/mp4"/></video>
            {else /}
            <i class="layui-icon"></i>
            <p>视频预览~暂无视频</p>
            {/notempty}
        </div>
    </div>
    <style>
        .ui-upload input {position: absolute;font-size: 100px;right: 0;top: 0;opacity: 0;filter: alpha(opacity=0);cursor: pointer }
    </style>
    <label class="layui-form-label" style=" padding:0;margin-top: 5px; border-style:none">
        <a class="layui-btn ui-upload" id="ui-upload" style="width: 100%;"><input type="file" />视频上传</a>
    </label>

    <div class="layui-input-block" style="margin-top: 5px;">
        <input type="text" name="video" value="{$data.video}" id="video_input" class="layui-input" placeholder="手动上传或输入网址">
    </div>
</div>

js代码

<!-- 阿里云OSS web端上传 START-->
<script type="text/javascript" src="/static/lib/ali-oss/dist/aliyun-oss-sdk.js"></script>
<script type="text/javascript" src="/static/admin/easyWeb/libs/jquery/jquery-3.2.1.min.js"></script>
<script type="text/javascript">
    //console.log("{:langSite('web_url')}{:url('Files/getStsToken')}")
    document.getElementById('ui-upload').addEventListener('change', function (e) {
        let file = e.target.files[0];
        let storeAs = Date.parse(new Date())/1000+file.name.substr(file.name.lastIndexOf("."),file.name.length);
        console.log(file.name + ' => ' + storeAs);
        // OSS.urlib是SDK内部封装的发送请求的逻辑,开发者可以使用任何发送请求的库向sts-server发送请求。
        OSS.urllib.request("{:url('Files/getStsToken')}", {method: 'GET'}, (err, response) => {
            if (err) {
                alert(err);
            }
            try {
                result = JSON.parse(response);
            } catch (e) {
                return alert('parse sts response info error: ' + e.message);
            }
            layer.load(); //上传loading
            let client = new OSS({
                accessKeyId: result.accessKeyId,
                accessKeySecret: result.accessKeySecret,
                stsToken: result.securityToken,
                // region表示您申请OSS服务所在的地域,例如oss-cn-hangzhou。
                region: result.region,
                bucket: result.bucket
            });
            // storeAs可以自定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至当前Bucket或Bucket下的指定目录。
            // file可以自定义为File对象、Blob数据以及OSS Buffer。
            client.multipartUpload(storeAs, file, {}).then(function (result) {
                layer.closeAll('loading');
                var res = result.res;
                //上传完毕回调
                if (res.status == 200){
                    var data_src = res.requestUrls[0].split("?");
                    $(".xcvideo").html('<video controls="controls" height="200"><source src="'+ data_src[0] +'" type="video/mp4"/></video>')
                    $("#video_input").val(data_src[0])
                } else{
                    layer.msg('上传失败',{icon:5,time:1500})
                }
            }).catch(function (err) {
                console.log(err);
            });
        });
    });
    
    $(function () {
        $("#video_input").bind("input propertychange",function(event){
            $(".xcvideo").html('<video controls="controls" height="200"><source src="'+$("#video_input").val()+'" type="video/mp4"/></video>')
        })
    })
</script>
<!-- 阿里云OSS web端上传 END-->


PHP代码

<?php

namespace app\admin\controller;

use think\facade\Db;
use think\facade\Request;


class Files extends Base
{
   protected $url;
   protected $accessKeyId;
   protected $accessKeySecret;
   protected $roleArn;             //指定角色的 ARN ,角色策略权限
   protected $roleSessionName;     //用户自定义参数。此参数用来区分不同的 token,可用于用户级别的访问审计。格式:^[a-zA-Z0-9\.@\-_]+$
   protected $durationSeconds;     //指定的过期时间
   protected $region;              //region表示您申请OSS服务所在的地域,例如oss-cn-hangzhou。
   protected $bucket;              //Bucket 表示访问域名带上文件路径,例如qdxcy
   
   public function __construct()
   {
      $this->url = 'https://sts.aliyuncs.com';
      $this->accessKeyId = env('oss_sts.accessKeyId');
      $this->accessKeySecret = env('oss_sts.accessKeySecret');
      $this->roleArn = env('oss_sts.roleArn');
      $this->roleSessionName = env('oss_sts.roleSessionName');
      $this->durationSeconds = env('oss_sts.durationSeconds');
      $this->region = env('oss_sts.region');
      $this->bucket = env('oss_sts.bucket');
   }
   
   #阿里云OSS web端上传 获取STS鉴权 START
   //获取stsToken
   public function getStsToken(){
      $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'
      );
      $param['Signature'] = $this->computeSignature($param, 'POST');
      $res = http_request($this->url, $param);//curl post请求
      if ($res) {
         $res = json_decode($res, true);
         if (empty($res['Credentials'])) {
            return [];
         }
         
         return json([
            'accessKeySecret' => $res['Credentials']['AccessKeySecret'] ?? '',
            'accessKeyId'     => $res['Credentials']['AccessKeyId'] ?? '',
            'expiration'      => $res['Credentials']['Expiration'] ?? '',
            'securityToken'   => $res['Credentials']['SecurityToken'] ?? '',
            'region'          => $this->region,
            'bucket'          => $this->bucket,
         ]);
      } else {
         return [];
      }
   }
   
   //消息签名
   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;
   }
   #阿里云OSS web端上传 获取STS鉴权 END

获取STS返回结果

{
    "Credentials": {
        "AccessKeyId": "STS.xxxxxxxxxxxxx****",//访问密钥标识
        "AccessKeySecret": "xxxxxxxxxx****",//访问密钥
        "Expiration": "2019-04-09T11:52:19Z",//失效时间
        "SecurityToken": "********"//安全令牌
    },
    "AssumedRoleUser": {
        "arn": "acs:sts::123456765456****:assumed-role/AdminRole/client",
        "AssumedRoleUserId":"1234567121****:alice"
        },
    "RequestId": "xxxxxxxxxxxxxxxx"
}

相信大家看完前面的解析、代码及返回结果后大概对怎么使用STS鉴权模式有一定了解;返回结果中对于客户端APP来讲需要用的有AccessKeyId、AccessKeySecret、Expiration、SecurityToken,客户端拿到这四个参数后将其传给阿里云ossSDK,vodSDK等便可以正常使用oss上传服务或者短视频vod服务了。

评论

共0条评论
  • 这篇文章还没有收到评论,赶紧来抢沙发吧~

站点声明:本站转载作品版权归原作者及来源网站所有,原创内容作品版权归作者所有,任何内容转载、商业用途等均须联系原作者并注明来源。

© 2020-2030 qdxcy.cn 版权所有京ICP备13045222号
相关侵权、举报、投诉及建议等,请发E-mail:2323946929@qq.com

友情链接: YzmCMS官方网站 YzmCMS官方论坛