1 权限解读
- 主账号的永久key和secrect请求,泄漏就不安全。
- 子账号的永久key和secrect请求,泄漏同样不安全,但能分配更小的权限。
- 子账号+RAM角色,动态获取临时key和secrect,即STS,请求更安全、且能给前端直接使用。
2 环境
- endpoint:部分api只在特定endpoint上能请求。
- 创建子账号:后端记住子账号的key和secrect。
- 创建角色:记住角色ID;往角色上分配需要使用的权限。
3 代码
- 不依赖官方sdk进行签名加密。
- 可以此调用其它阿里api。
- 生成待加密
signature
时,使用的是更公认的rawurlencode
,而不是urlencode
;rawurlencode
使用的是更新的标准,而urlencode
是旧的标准。
class ContractOnline
{
private $redisClient;
private $aliConfig = [
'key' => '子账号-key',
'secret' => '子账号-secret',
'sts' => [
'endpoint' => "sts.cn-guangzhou.aliyuncs.com",
"regionId" => "cn-guangzhou"
],
];
public function __construct()
{
$this->redisClient = $this->redisConnect();
}
private function commonConfig($key = "")
{
require(dirname(dirname(__FILE__)).'/Common/config/redis.php');
$redisConfig = $config;
require(dirname(dirname(__FILE__)).'/Common/config/config.php');
$config['redis'] = $redisConfig;
return $key ? $config[$key] : $config;
}
protected function redisConnect()
{
$redisConfig = $this->commonConfig('redis');
$redis = new Redis();
$redis->connect($redisConfig['host'], $redisConfig['port']);
$redis->auth($redisConfig['password']);
$redisConfig['db'] = isset($redisConfig['db'])?$redisConfig['db']:0;
$redis->select($redisConfig['db']);
return $redis;
}
private function successJson($data = [], $status = 1, $msg = "")
{
$res = [
'status' => $status,
'data' => $data,
'msg' => $msg,
];
exit(json_encode($res));
}
private function failedJson($msg = "", $status = 0, $data = [])
{
$this->successJson($data, $status, $msg);
}
private function curlPost($url = "", $data = [], $method = "POST", $header = [])
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSL_VERIFYPEER => false,
]);
!empty($header) && curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
if (strtoupper($method) == "POST") {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
} else if ($method == "GET") {
$url = $url . "?" . http_build_query($data);
}
curl_setopt($ch, CURLOPT_URL, $url);
$response = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($response === false) {
return ['status' => 0, 'msg' => $err];
}
return ['status' => 1, 'data' => $response];
}
private function buildParams($action = "", $version = "", $method = "GET", $otherParams = [])
{
$params = [
'AccessKeyId' => $this->aliConfig['key'],
'Action' => $action,
'Version' => $version,
'Format' => 'JSON',
'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
'SignatureMethod' => 'HMAC-SHA1',
'SignatureVersion' => '1.0',
'SignatureNonce' => uniqid(),
];
$params = array_merge($params, $otherParams);
ksort($params);
$specialUrlEncode = function ($value) {
return str_replace(array("+", "*", "%7E"), array("%20", "%2A", "~"), rawurlencode($value));
};
$sortedQueryString = "";
foreach ($params as $key => $value) {
$sortedQueryString .= "&" . $specialUrlEncode($key) . "=" . $specialUrlEncode($value);
}
$sortedQueryString = substr($sortedQueryString, 1);
$stringToSign = "{$method}&". $specialUrlEncode("/") ."&" . $specialUrlEncode($sortedQueryString);
$signature = base64_encode(hash_hmac('sha1', $stringToSign, $this->aliConfig['secret'] . '&', true));
$params['Signature'] = $signature;
return $params;
}
private function getFaceSTS()
{
$rkey = "contract:ali:face-sts";
$expire = 3600;
$asData = [];
if (!$this->redisClient->exists($rkey)) {
$action = 'AssumeRole';
$version = '2015-04-01';
$params = [
'DurationSeconds' => $expire,
'RoleSessionName' => 'contract-online',
'RoleArn' => 'acs:ram::xxxxxxx:role/face-check',
'RegionId' => $this->aliConfig['sts']['regionId'],
];
$method = "GET";
$params = $this->buildParams($action, $version, $method, $params);
$url = 'https://' . $this->aliConfig['sts']['endpoint'];
$res = $this->curlPost($url, $params, $method);
if (!$res['status']) {
$this->failedJson($res['msg']);
}
$data = json_decode($res['data'], true);
if ($data['Message']) {
$this->failedJson($data['Message']);
}
$data['Credentials']['AccessKeyId'] && $asData['key'] = $data['Credentials']['AccessKeyId'];
$data['Credentials']['AccessKeySecret'] && $asData['secret'] = $data['Credentials']['AccessKeySecret'];
$data['Credentials']['SecurityToken'] && $asData['token'] = $data['Credentials']['SecurityToken'];
$this->redisClient->setex($rkey, $expire, json_encode($asData));
} else {
$res = $this->redisClient->get($rkey);
$asData = $res ? json_decode($res, true) : [];
}
return $asData;
}
}