[nCloud] Object Storage 사용을 위한 PHP용 코드

[nCloud] Object Storage 사용을 위한 PHP용 코드

네이버 클라우드 플랫폼의 ‘Object Storage’ 를 사용하면서 콘솔로 관리를 진행하는 것이 아닌 PHP를 사용하는 방법을 블로그에 기록해둔다.

AWS의 S3와 같은 방식의 사용자 인증 방식을 채택하고 있는데 이 예제들이 가이드 문서에는 PHP 예제가 없었다.

PHP로 구현을 하면서 SignatureDoesNotMatch라는 오류 내용을 지속해서 보게 되는 상황이 왔고 결국은 해결했다.

아래의 내용은 네이버 클라우드 플랫폼에서 기본적으로 제공하는 Object Storage 사용자 가이드의 [인증 관리] 문서와 구글 검색을 기반으로 만들어졌다.

따라서, 만들고자 하면 누구나 만들 수 있기에 부담 없이 공개를 한다.

나중에 혹시 까먹을 나를 위해, 그리고 Object Storage를 PHP를 사용해서 관리하려는 다른 사람들을 위해 작성한 Class 파일 내용을 올린다.

listObjectparameter로 넘겨주는 값이 필수로 있어야 작동해서 아래 사진과 같이 사용 예제를 올린다. 코드는 사진 바로 밑에 이어서 첨부.

Object Storage listObject

class ObjectStorage
{
    //const CHARSET_NAME = "UTF-8"; //PHP에서 기본 언어설정이 UTF-8로 되어있으므로 변수로 사용하지 않음
    //const HMAC_ALGORITHM = "HmacSHA256"; //PHP에서 hash_hmac 함수를 제공하므로 사용하지 않음
    const HASH_ALGORITHM = "sha256";
    const AWS_ALGORITHM = "AWS4-HMAC-SHA256";

    const DATE_FORMAT = "Ymd";
    const TIME_FORMAT = "Ymd\THis\Z";

    const SERVICE_NAME = "s3";
    const REQUEST_TYPE = "aws4_request";
    const UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";

    const REGION_NAME = "kr-standard";
    const ENDPOINT = "https://kr.object.ncloudstorage.com";
    const HOST = "kr.object.ncloudstorage.com";
    const ACCESS_KEY = "ACCESS_KEY";
    const SECRET_KEY = "SECRET_KEY";

    public static function getSign($string, $key)
    {
        return hash_hmac(self::HASH_ALGORITHM, $string, $key, true);
    }

    public static function getHash($msg)
    {
        $encode = hash(self::HASH_ALGORITHM, $msg);
        return $encode;
    }

    public static function createStandardizedQueryParameters($request_parameters)
    {
        if ($request_parameters != null) {
            ksort($request_parameters);
            return http_build_query($request_parameters);
        } else {
            return null;
        }
    }

    public static function getSignedHeader($headers)
    {
        $signed_headers = [];
        foreach($headers as $key => $value) {
            $signed_headers[] = strtolower($key);
        }
        $signed_headers = implode(";", $signed_headers);

        return $signed_headers;
    }

    public static function getCanonicalHeader($headers)
    {
        $canonical_headers = [];

        foreach($headers as $key => $value) {
            $canonical_headers[] = strtolower($key) . ":" . $value;
        }
        $canonical_headers = implode("\n", $canonical_headers);
        return $canonical_headers;
    }

    public static function getCanonicalRequest($method, $request_path, $request_parameters, $headers)
    {
        $standardHeader = self::getCanonicalHeader($headers);
        $signedHeader = self::getSignedHeader($headers);

        $canonical_request = [];
        $canonical_request[] = $method;
        $canonical_request[] = $request_path;
        $canonical_request[] = $request_parameters;
        $canonical_request[] = $standardHeader;
        $canonical_request[] = "";
        $canonical_request[] = $signedHeader;
        $canonical_request[] = $headers['X-Amz-Content-Sha256'];
        $canonical_request = implode("\n", $canonical_request);

        return $canonical_request;
    }

    public static function getScope($datestamp)
    {
        return $datestamp . '/' . self::REGION_NAME . '/' . self::SERVICE_NAME . '/' . self::REQUEST_TYPE;
    }

    public static function getStringToSign($timestamp, $scope, $canonicalRequest)
    {
        $stringToSign = [];
        $stringToSign[] = self::AWS_ALGORITHM;
        $stringToSign[] = $timestamp;
        $stringToSign[] = $scope;
        $stringToSign[] = self::getHash($canonicalRequest);
        $stringToSign = implode("\n", $stringToSign);

        return $stringToSign;
    }

    public static function getSignatureKey($date, $stringToSign)
    {
        $secret = "AWS4" . self::SECRET_KEY;
        $keyDate = self::getSign($date, $secret);
        $keyString = self::getSign(self::REGION_NAME, $keyDate);
        $keyService = self::getSign(self::SERVICE_NAME, $keyString);
        $keySigning = self::getSign(self::REQUEST_TYPE, $keyService);

        $signature = hash_hmac(self::HASH_ALGORITHM, $stringToSign, $keySigning);

        return $signature;
    }

    public static function getAuthorizationHeader($headers, $signature, $credentialScope)
    {
        $signedHeaders = self::getSignedHeader($headers);

        $authorization = [
            'Credential=' . self::ACCESS_KEY . '/' . $credentialScope,
            'SignedHeaders=' . $signedHeaders,
            'Signature=' . $signature
        ];

        $authorization = self::AWS_ALGORITHM . ' ' . implode(',', $authorization);

        return $authorization;
    }

    public static function signature($httpMethod, $requestPath, &$headers, $request_parameters = null)
    {
        /* 한국 시간
        $timestamp = date(self::TIME_FORMAT);
        $datestamp = date(self::DATE_FORMAT);
        */

        // UTC 시간으로 설정해야 작동함
        $timestamp = gmdate(self::TIME_FORMAT);
        $datestamp = gmdate(self::DATE_FORMAT);

        $headers["X-Amz-Date"] = $timestamp;
        ksort($headers);

        $canonicalRequest = self::getCanonicalRequest($httpMethod, $requestPath, $request_parameters, $headers);
        $credentialScope = self::getScope($datestamp);
        $stringToSign = self::getStringToSign($timestamp, $credentialScope, $canonicalRequest);

        $signature = self::getSignatureKey($datestamp, $stringToSign);

        $signature = self::getAuthorizationHeader($headers, $signature, $credentialScope);

        return $signature;
    }

    public static function putObject($bucket_name, $object_name, $source_file_path, $request_parameters)
    {
        $content = file_get_contents($source_file_path);
        $method = "PUT";
        $content_acl = "public-read";
        $file_size = filesize($source_file_path);
        $file_info = new \finfo(FILEINFO_MIME_TYPE);
        $mime_type = $file_info->buffer($content);

        $request_headers = array(
            "Host" => self::HOST,
            "Content-Type" => $mime_type . "; charset=UTF-8",
            "Content-Length" => $file_size,
            "X-Amz-Acl" => $content_acl,
            "X-Amz-Content-Sha256" => hash(self::HASH_ALGORITHM, $content),
        );

        $request_path = "/";
        if ($bucket_name != null)
            $request_path .= $bucket_name . "/";
        if ($object_name != null)
            $request_path .= $object_name;

        $request_parameters = self::createStandardizedQueryParameters($request_parameters);

        $authorization = self::signature($method,  $request_path, $request_headers, $request_parameters);

        $curl_headers = [ 'Authorization: ' . $authorization];
        foreach($request_headers as $key => $value) {
            $curl_headers[] = $key . ": " . $value;
        }

        $request_url = self::ENDPOINT . $request_path;
        $ch = curl_init($request_url);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_VERBOSE, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
        $result = curl_exec($ch);

        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        return $http_code;
    }

    public static function getObject($bucket_name, $object_name)
    {
        $method = "GET";

        $request_headers = array(
            "Host" => self::HOST,
            "X-Amz-Content-Sha256" => self::UNSIGNED_PAYLOAD
        );

        $request_path = "/";
        if ($bucket_name != null)
            $request_path .= $bucket_name . "/";
        if ($object_name != null)
            $request_path .= $object_name;

        ksort($request_headers);
        $authorization = self::signature($method,  $request_path, $request_headers);

        $curl_headers = [ 'Authorization: ' . $authorization];
        foreach($request_headers as $key => $value) {
            $curl_headers[] = $key . ": " . $value;
        }

        $request_url = self::ENDPOINT . $request_path;
        $ch = curl_init($request_url);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_VERBOSE, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        $result = curl_exec($ch);

        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        return $http_code;
    }

    public static function deleteObject($bucket_name, $object_name, $request_parameters)
    {
        $method = "DELETE";

        $request_headers = array(
            "Host" => self::HOST,
            "X-Amz-Content-Sha256" => self::UNSIGNED_PAYLOAD
        );

        $request_path = "/";
        if ($bucket_name != null)
            $request_path .= $bucket_name . "/";
        if ($object_name != null)
            $request_path .= $object_name;

        $request_parameters = self::createStandardizedQueryParameters($request_parameters);

        $authorization = self::signature($method,  $request_path, $request_headers, $request_parameters);

        $curl_headers = [ 'Authorization: ' . $authorization];
        foreach($request_headers as $key => $value) {
            $curl_headers[] = $key . ": " . $value;
        }

        $request_url = self::ENDPOINT . $request_path;
        $ch = curl_init($request_url);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_VERBOSE, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        $result = curl_exec($ch);

        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        return $http_code;
    }

    public static function listObject($bucket_name, $object_name, $request_parameters = null)
    {
        $method = "GET";

        $request_headers = array(
            "Host" => self::HOST,
            "X-Amz-Content-Sha256" => self::UNSIGNED_PAYLOAD
        );

        $request_path = "/";
        if ($bucket_name != null)
            $request_path .= $bucket_name . "/";
        if ($object_name != null)
            $request_path .= $object_name;

        $request_parameters = self::createStandardizedQueryParameters($request_parameters);

        ksort($request_headers);
        $authorization = self::signature($method,  $request_path, $request_headers, $request_parameters);

        $curl_headers = [ 'Authorization: ' . $authorization];
        foreach($request_headers as $key => $value) {
            $curl_headers[] = $key . ": " . $value;
        }

        $request_url = self::ENDPOINT . $request_path . "?" . $request_parameters;
        $ch = curl_init($request_url);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_VERBOSE, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        $result = curl_exec($ch);

        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        return $http_code;
    }
}

 

1 comments On [nCloud] Object Storage 사용을 위한 PHP용 코드

Leave a reply:

Your email address will not be published.

Site Footer