2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SalesforceのApexでREST APIを使用してS3に署名バージョン4でアクセスする方法

Posted at

SalesforceのApexを使用して、REST APIでAmazon S3に署名バージョン4を使用してアクセスする方法の日本語記事があまりなかったので、メモとして残しておく。

前提条件

  • AWSアカウントを所有しており、IAMユーザーを作成し、アクセスキーとシークレットキーを取得していること
  • S3バケットのアクセス権やSalesforceの設定が適切にされていること

実装

以下の公式ドキュメントの手順にしたがって実装。詳細は以下を参照。
署名付き AWS API リクエストを作成する

secret, secretKey, regionはクラス変数等で定義しておく

手順1: 正規リクエストを作成する

private String generateCanonicalRequest(String method, String filename, String query, Map<String, String> headers, String hashedPayload) {
    // HTTPMethod
    String canonicalRequest = method + '\n';
    // CanonicalURI
    canonicalRequest += '/' + filename + '\n';
    // CanonicalQueryString
    canonicalRequest += query + '\n';

    List<String> headersList = new List<String>(headers.keySet());
    headersList.sort();
    // CanonicalHeaders
    for (String header : headersList) {
        String headerContent = header.toLowerCase() + ':' + headers.get(header).trim();
        canonicalRequest += headerContent + '\n';
    }
    canonicalRequest += '\n';
    // SignedHeaders
    for (String header : headersList) {
        canonicalRequest += header.toLowerCase() + ';';
    }
    canonicalRequest = canonicalRequest.removeEnd(';');
    canonicalRequest += '\n';
    // HashedPayload
    canonicalRequest += hashedPayload;

    return canonicalRequest;
}

手順2: 正規リクエストのハッシュを作成する

以下の手順3で実行

手順3: 署名文字列を作成する

private String generateStringToSign(String algorithm, String requestDateTime, String credentialScope, String canonicalRequest) {
    // Algorithm
    String stringToSign = algorithm + '\n';
    // RequestDateTime
    stringToSign += requestDateTime + '\n';
    // CredentialScope
    stringToSign += credentialScope + '\n';
    // HashedCanonicalRequest
    stringToSign += EncodingUtil.convertToHex(Crypto.generateDigest('SHA-256', Blob.valueOf(canonicalRequest)));

    return stringToSign;
}

手順4: 署名を計算する

private Blob generateSigningKey(String date, String service) {
    Blob dateKey = Crypto.generateMac('HMACSHA256', Blob.valueOf(date), Blob.valueOf('AWS4' + this.secret));
    Blob dateRegionKey = Crypto.generateMac('HMACSHA256', Blob.valueOf(this.region), dateKey);
    Blob dateRegionServiceKey = Crypto.generateMac('HMACSHA256', Blob.valueOf(service), dateRegionKey);
    Blob signingKey = Crypto.generateMac('HMACSHA256', Blob.valueOf('aws4_request'), dateRegionServiceKey);

    return signingKey;
}

手順4.5: 署名の作成

手順5で使用する

private Blob generateSignature(String stringToSign, Blob signingKey) {
    return Crypto.generateMac('HMACSHA256', Blob.valueOf(stringToSign), signingKey);
}

手順5: リクエストヘッダーに署名を追加する

private String generateAuthorizationHeader(String credentialScope, String algorithm, Map<String, String> headers, Blob signature, String requestDateTime) {
    String signedHeaders = '';
    
    List<String> headersList = new List<String>(headers.keySet());
    headersList.sort();
    for (String header : headersList) {
        signedHeaders += header.toLowerCase() + ';';
    }
    signedHeaders = signedHeaders.removeEnd(';');

    String authorizationHeader = algorithm + ' ';
    authorizationHeader += 'Credential=' + this.secretKey + '/' + credentialScope + ', ';
    authorizationHeader += 'SignedHeaders=' + signedHeaders + ', ';
    authorizationHeader += 'Signature=' + EncodingUtil.convertToHex(signature).toLowerCase();

    return authorizationHeader;
}

手順6: HTTPリクエストを実行する

GETメソッド

public HTTPResponse getObject(String filename) {
    String method = 'GET';
    String service = 's3';
    String algorithm = 'AWS4-HMAC-SHA256';
    Datetime now = Datetime.now();
    String date = now.formatGMT('yyyyMMdd');
    String requestDateTime = now.formatGMT('yyyyMMdd\'T\'HHmmss\'Z\'');
    String credentialScope = date + '/' + this.region + '/' + service + '/aws4_request';

    String bucketName = '';
    String hostName = '';
    String endpointUrl = 'https://' + bucketName + '.' + hostName + '/' + filename;

    // リクエストペイロードのハッシュを生成
    String hashedPayload = EncodingUtil.convertToHex(Crypto.generateDigest('SHA-256', Blob.valueOf('')));

    // ヘッダーを定義
    Map<String, String> headers = new Map<String, String>{
        'host' => bucketName + '.' + hostName,
        'x-amz-date' => requestDateTime,
        'x-amz-content-sha256' => hashedPayload
    };

    // 正規リクエストを作成
    String canonicalRequest = generateCanonicalRequest(method, filename, '', headers, hashedPayload);
    // 署名文字列を作成
    String stringToSign = generateStringToSign(algorithm, requestDateTime, credentialScope, canonicalRequest);
    // 署名を計算
    Blob signingKey = generateSigningKey(date, service);
    // 署名の作成
    Blob signature = generateSignature(stringToSign, signingKey);
    // リクエストヘッダーを作成
    String authorizationHeader = generateAuthorizationHeader(credentialScope, algorithm, headers, signature, requestDateTime);

    HttpRequest req = new HttpRequest();
    req.setMethod(method);
    req.setEndpoint(endpointUrl);
    for (String header : headers.keySet()) {
        req.setHeader(header, headers.get(header));
    }
    req.setHeader('Authorization', authorizationHeader);

    Http http = new Http();
    HTTPResponse res = http.send(req);

    return res;
}

PUTメソッド

基本的にはGETメソッドと同様のため、変更箇所以外は省略

テキストファイルをアップロードする場合

- public HTTPResponse getObject(String filename) {
-     String method = 'GET';
+ public void putTextObject(String filename, String body) {
+     String method = 'PUT';
-     String hashedPayload = EncodingUtil.convertToHex(Crypto.generateDigest('SHA-256', Blob.valueOf('')));
+     String hashedPayload = EncodingUtil.convertToHex(Crypto.generateDigest('SHA-256', Blob.valueOf(body)));
+     req.setHeader('Content-Length', String.valueOf(body.length()));
+     req.setHeader('Content-Encoding', 'UTF-8');
+     req.setHeader('Content-type', ContentType('csv'));
+     req.setHeader('Connection', 'keep-alive');
+     req.setHeader('ACL', 'public-read');
+     req.setBodyAsBlob(Blob.valueOf(body));
-     return res;

イメージファイルをアップロードする場合

- public HTTPResponse getObject(String filename) {
-     String method = 'GET';
+ public void putImageObject(String filename, Blob body) {
+     String method = 'PUT';
-     String hashedPayload = EncodingUtil.convertToHex(Crypto.generateDigest('SHA-256', Blob.valueOf('')));
+     String hashedPayload = EncodingUtil.convertToHex(Crypto.generateDigest('SHA-256', body));
+     req.setHeader('Content-Length', String.valueOf(EncodingUtil.base64Encode(body).length()));
+     req.setHeader('Content-type', ContentType('jpeg'));
+     req.setHeader('Connection', 'keep-alive');
+     req.setHeader('ACL', 'public-read');
+     req.setBodyAsBlob(body);
-     return res;

オブジェクトコピーの場合

- public HTTPResponse getObject(String filename) {
-     String method = 'GET';
+ public HTTPResponse copyObject(String fromFilename, String filename) {
+     String method = 'PUT';
-     Map<String, String> headers = new Map<String, String>{
-         'host' => bucketName + '.' + hostName,
-         'x-amz-date' => requestDateTime,
-         'x-amz-content-sha256' => hashedPayload
-     };
+     Map<String, String> headers = new Map<String, String>{
+         'host' => bucketName + '.' + hostName,
+         'x-amz-date' => requestDateTime,
+         'x-amz-content-sha256' => hashedPayload
+         'x-amz-copy-source' => '/' + bucketName + '/' + fromFilename
+     };

参考文献

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?