保护私人版权,尊重他人版权。转载请注明出处并附带页面链接
背景
因为最近需要遇到客户端直接使用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有初步的了解,以及应用场景,下面进入正题
1 | --------------------------------分割线----------------------------- |
如何通过STS获取安全令牌呢?
- 先上代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105class 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 < $length; $i++) {
$str .= $strPol[rand(0, $max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
}
return $str;
}
protected function setRoleArn()
{
if ($this->type == '123') {//根据入参使用不同的策略,当然这里还可以有其他写法兼容更多的策略的情况
$this->roleArn = 'acs:ram::123456789098:role/=$roleName';
}
}
} - 返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13{
"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 获取临时授权
1
2
3
4"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