使用阿里云sts鉴权模式实践(php),抛去复杂的官方sdk,自由裸奔
背景
因为最近需要遇到客户端直接使用hackCode方式引入子账号,存在账号泄露问题,遂寻找一种对客户端影响较少也能保证账号安全的解决方法,所以就有了本文。
关于STS
- 阿里云 STS(Security Token Service)是阿里云提供的一种临时访问权限管理服务。
- 通过 STS 服务,您所授权的身份主体(RAM 用户、RAM 用户组或 RAM 角色)可以获取一个自定义时效和访问权限的临时访问令牌。STS令牌持有者可以通过以下方式访问阿里云资源:
- 通过编程方式访问被授权的阿里云服务 API。
- 登录阿里云控制台操作被授权的云资源。
*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 术语表
术语 | 说明 |
---|---|
RAM Role | 一种虚拟的RAM用户。 |
Role Arn | Role ARN 是角色的全局资源描述符,用来指定具体角色。每个角色都有一个唯一的全局资源描述符。格式:acs:ram::$accountID:role/$roleName |
Trusted Entity | 角色的受信主体是指可以扮演角色的实体用户身份。创建角色时必须指定受信主体,角色只能被受信的主体扮演。受信主体可以是受信的阿里云云账号,或者受信阿里云服务。 |
Assume Role | 扮演角色是实体用户获取角色身份的安全令牌的方法。一个实体用户通过调用 AssumeRole 的 API 可以获得角色的安全令牌,使用安全令牌可以访问云服务 API。 |
STS鉴权模式
OSS可以通过阿里云STS (Security Token Service) 进行临时授权访问。阿里云STS是为云计算用户提供临时访问令牌的Web服务。通过STS,您可以为第三方应用或子用户(即用户身份由您自己管理的用户)颁发一个自定义时效和权限的访问凭证。
STS鉴权模式具有以下优势:
- 无需透露您的长期密钥(AccessKey)给第三方应用,只需生成一个访问令牌并将令牌交给第三方应用。您可以自定义这个令牌的访问权限及有效期限。
- 无需关心权限撤销问题,访问令牌过期后访问权限会自动失效。
以APP应用为例,交互流程如图所示(来自阿里云官方文档):
以上(来自阿里云文档)只是为让大家先对STS有初步的了解,以及应用场景,下面进入正题
--------------------------------分割线-----------------------------
如何通过STS获取安全令牌呢?
1.先上代码
class STS{ protected $url = 'https://sts.aliyuncs.com'; protected $accessKeySecret = '1234567890qwertyuioasdfghj'; protected $accessKeyId = 'LT11234567898'; protected $roleArn = 'acs:ram::$accountID:role/$roleName';//指定角色的 ARN ,角色策略权限 protected $roleSessionName = 'client1';//用户自定义参数。此参数用来区分不同的 token,可用于用户级别的访问审计。格式:^[a-zA-Z0-9\.@\-_]+$ protected $durationSeconds = '1800';//指定的过期时间 protected $type = 'xxx';//方便调用时获取不同的权限 public function __construct($type) { $this->type = $type; $this->setRoleArn(); } 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'); $res = CurlHandle::httpPost($this->url, $param);//curl post请求 if ($res) { return self::_render($res); } else { return []; } } private static function _render($res) { $res = json_decode($res, true); if (empty($res['Credentials'])) { return []; } else { return [ 'accessKeySecret' => $res['Credentials']['AccessKeySecret'] ?? '', 'accessKeyId' => $res['Credentials']['AccessKeyId'] ?? '', 'expiration' => $res['Credentials']['Expiration'] ?? '', 'securityToken' => $res['Credentials']['SecurityToken'] ?? '', ]; } } 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 type == '123') {//根据入参使用不同的策略,当然这里还可以有其他写法兼容更多的策略的情况 $this->roleArn = 'acs:ram::123456789098:role/=$roleName'; } }}
2.返回结果
{ "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服务了。
使用STS鉴权具体步骤
- 在阿里云后台开通一个RAM子账号
- 新建角色(表示某种操作权限),授权相应的策略
-
通过调用STS的AssumeRole接口,传递RAM子账号的AccessKeyId、AccessKeySecret以及指定角色的 ARN ,角色策略权限 roleArn 获取临时授权
AccessKeyId": "STS.xxxxxxxxxxxxx****",//访问密钥标识"AccessKeySecret": "xxxxxxxxxx****",//访问密钥"Expiration": "2019-04-09T11:52:19Z",//失效时间"SecurityToken": "********"//安全令牌
-
然后…emmm…把想法写成代码逻辑。
-
附:阿里云对RAM和STS具体介绍文档地址:https://help.aliyun.com/document_detail/31931.html?spm=a2c4g.11186623.2.10.34c51388aBbFjR#concept-nsb-brz-5db