无依赖接入阿里STS认证权限

1 权限解读

  • 主账号的永久key和secrect请求,泄漏就不安全。
  • 子账号的永久key和secrect请求,泄漏同样不安全,但能分配更小的权限。
  • 子账号+RAM角色,动态获取临时key和secrect,即STS,请求更安全、且能给前端直接使用。

2 环境

  • endpoint:部分api只在特定endpoint上能请求。
  • 创建子账号:后端记住子账号的key和secrect。
  • 创建角色:记住角色ID;往角色上分配需要使用的权限。

3 代码

  • 不依赖官方sdk进行签名加密。
  • 可以此调用其它阿里api。
  • 生成待加密signature时,使用的是更公认的rawurlencode,而不是urlencoderawurlencode使用的是更新的标准,而urlencode是旧的标准。
class ContractOnline
{
    private $redisClient;
    private $aliConfig = [
        'key' => '子账号-key',
        'secret' => '子账号-secret',
        // STS凭证请求地址
        '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;
    }

    /**
     * redis连接
     * @return Redis
     */
    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);
    }

    /**
     * 发起post请求
     * @param string $url
     * @param array $data
     * @return array
     */
    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];
    }

    /**
     * 阿里云公共:构建公共的参数、签名
     * @param string $action  // 服务api名
     * @param string $version // 服务版本号
     * @param string $method // 所需的角色arn
     * @param array $otherParams // 不同接口下,独特的参数
     * @return array
     */
    private function buildParams($action = "", $version = "", $method = "GET", $otherParams = [])
    {
        // 文档:https://help.aliyun.com/zh/sdk/product-overview/rpc-mechanism?spm=api-workbench.api_explorer.0.0.1fce6b25W8Ei4H
        $params = [
            'AccessKeyId' => $this->aliConfig['key'],
            'Action' => $action, // api对应的标识
            'Version' => $version, // api对应的版本号
            'Format' => 'JSON',
            'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'), // 格林尼治时间
            'SignatureMethod' => 'HMAC-SHA1', // 加密方式,当前固定HMAC-SHA1
            'SignatureVersion' => '1.0',  // 加密版本,当前固定为1.0
            'SignatureNonce' => uniqid(), // 请求序列号,不重复即可
        ];
        $params = array_merge($params, $otherParams);
        // 1.参数按键字母字典排序,并构建请求参数
        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);

        // 2.生成签名
        $stringToSign = "{$method}&". $specialUrlEncode("/") ."&" . $specialUrlEncode($sortedQueryString);
        $signature = base64_encode(hash_hmac('sha1', $stringToSign, $this->aliConfig['secret'] . '&', true));
//        $signature = $specialUrlEncode($signature);
        $params['Signature'] = $signature;
        return $params;
    }

    /**
     * 阿里:RAM用户获取角色临时凭证STS,以临时使用角色上的权限
     */
    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']);
            }
            // 提取数据:提取key、secrect和token
            $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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值