Apex

ApexでAmazon S3へファイルを送るときの注意点

取引先に添付したファイルを、手作業でS3にアップロードするのは面倒です。Apexのコールアウトなら自動化できますが、認証情報の置き場所と署名の作り方でつまずきます。ここを順番に押さえます。

2023.02.24

なぜApexから直接アップロードするのか

Salesforceのレコードに添付したファイルを、外部の保管先や連携先に渡したい場面があります。ダウンロードしてS3のコンソールから手作業でアップロードすれば済みますが、件数が増えると続きません。Apexからコールアウトを1回送るだけで、レコードの保存やボタンのクリックに合わせて自動で転送できます。手作業を無くせるのが、Apexで直接送る一番の理由です。

用意するもの

用意するもの内容
S3バケットファイルの送り先。本記事では example-bucket という名前で作成します
Salesforce側の認証経路S3への発信を許可し、署名を作る仕組み。リモートサイト設定と指定ログイン情報の2通りがあります
取り込むファイルSalesforce側で ContentVersion として保存されているファイル

S3バケットを作る

  1. Amazon S3のコンソールを開く

    「Create bucket」を押します。

  2. バケット名を入力する

    ここでは example-bucket とします。リージョンも合わせて選びます。

  3. 既定値のまま作成する

    「Create bucket」を押して作成します。パブリックアクセスは既定でブロックしたままにします。

  4. 作成されたことを確認する

    バケット一覧に example-bucket が表示されていれば完了です。

Amazon S3のバケット一覧画面。右上のCreate bucketボタンが赤枠で囲まれている
S3のコンソールでバケット一覧を開いたところです。右上の赤枠、Create bucketから作成を始めます。
Create bucketの入力画面。Bucket name欄が赤枠で、リージョンは東京が選ばれている
バケット名とリージョンを決める画面です。赤枠のBucket name欄と、その下のAWS Regionをご覧ください。
Create bucket画面の最下部。Cancelと赤枠のCreate bucketボタンが並んでいる
設定を既定のままにして、画面の一番下にある赤枠のCreate bucketを押すと作成されます。
バケット一覧に追加された1行。バケット名が赤枠で、リージョンは東京と表示されている
作成したバケットが一覧に増えていれば完了です。リージョンと公開設定もこの行で確認できます。

Salesforce側の認証経路を選ぶ

Apexからの発信は、まず送信先のドメインを許可する必要があります。古くからある方法はリモートサイト設定で、名前と送信先URLを登録するだけです。

すべてのリモートサイトの一覧画面。新規リモートサイトのボタンが赤枠で囲まれている
設定のリモートサイトの設定を開いたところです。赤枠の新規リモートサイトから登録を始めます。
リモートサイトの編集画面。リモートサイト名とURLの入力欄が赤枠で囲まれている
赤枠の2つの欄に、分かりやすい名前とS3のエンドポイントURLを入れて保存します。
登録後のリモートサイトの詳細画面。名前とS3のURLが赤枠で示されている
保存後の詳細画面です。赤枠のURLが、Apexのコールアウト先として許可されたアドレスになります。
現在の公式ドキュメントが第一に勧めているのは、リモートサイト設定ではなく指定ログイン情報です。指定ログイン情報で送信先を定義すると、その送信先へのリモートサイト設定自体を省略できます。さらに、S3のようなAWSのサービス向けには「AWS Signature Version 4」を使う外部ログイン情報が用意されていて、アクセスキーとシークレットを外部ログイン情報側に保存し、Apexのコードには一切書かずに済みます。

指定ログイン情報を使う場合の構成は、次の2つに分かれます。

部品持つ情報
外部ログイン情報認証方式(AWS Signature Version 4)、アクセスキー、シークレット、AWSのリージョンとサービス名
指定ログイン情報コールアウト先のURL。外部ログイン情報と紐づけて使います

外部ログイン情報にAWSの認証情報を入れてしまえば、Apex側の setEndpoint は callout:指定ログイン情報名/パス の形で書くだけになり、署名の計算はSalesforceの側で行われます。アクセスキーとシークレットをApexのコードに文字列として書く必要が無くなるのが、最大の利点です。

自分で署名する場合の仕組み

外部ログイン情報にまだ移行していない組織や、単純なリモートサイト設定のまま動かしたい場合は、Apex側でAWSの署名を組み立てる必要があります。仕組みを知っておくと、指定ログイン情報に切り替えたときの動きも理解しやすくなります。

⚠️ ここで紹介する署名の作り方は「AWS Signature Version 2」という古い方式です。AWSは2020年6月24日以降に作成したバケットでは、この方式でのリクエストを受け付けません。東京リージョンを含む一部の古いリージョンでは、それより前に作られたバケットに限り引き続き使えますが、AWSは現行の「Signature Version 4」への移行を案内しています。新しくバケットを作って試す場合、この方式では通りません。

署名の元になる文字列は、HTTPメソッドと対象のパス、日付などを改行でつないだものです。これを Crypto.generateMac でHMAC-SHA1署名し、Base64エンコードして Authorization ヘッダーに載せます。

String stringToSign = method + '\n\n' + contentType + '\n' + dateHeader + '\n' + '/' + bucketName + '/' + objectKey;
Blob mac = Crypto.generateMac('HMACSHA1', Blob.valueOf(stringToSign), Blob.valueOf(secretKey));
String signature = EncodingUtil.base64Encode(mac);
String authHeader = 'AWS' + ' ' + accessKey + ':' + signature;

署名文字列を作りHMAC-SHA1で署名する部分だけを抜き出しています。単体では動きません

ContentVersionからファイルを取り出す

以前の書き方では Attachment からファイルを読んでいましたが、Attachment は新規のファイル添付には使いません。取引先に添付されたファイルは ContentDocumentLink で紐づいた ContentVersion から取得します。

SELECT Id, Title, FileExtension, VersionData
FROM ContentVersion
WHERE ContentDocumentId IN (
  SELECT ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = :accountId
)
ORDER BY CreatedDate DESC
LIMIT 1

取引先(取引先Id)に紐づく最新のファイル本体を1件取得します

VersionData には、ファイルの中身がBlobとして入っています。このBlobを、そのままアップロードのリクエストボディに使います。

取引先のメモと添付ファイル欄。test.pngという添付ファイルが1件、赤枠で示されている
アップロードの対象になる添付ファイルです。この行のファイルがS3へ送られます。

Apexからアップロードする

PUTメソッドでファイルのパスへ直接送ります。ボディにはBase64にせず、元のバイナリのBlobをそのまま渡します。

public with sharing class S3FileUploader {
  public static Integer uploadToS3(ContentVersion cv, String accessKey, String secretKey) {
    String bucketName = 'example-bucket';
    String host = bucketName + '.s3.ap-northeast-1.amazonaws.com';
    String objectKey = EncodingUtil.urlEncode(cv.Title + '.' + cv.FileExtension, 'UTF-8');
    String contentType = 'application/octet-stream';
    String dateHeader = Datetime.now().formatGMT('EEE, dd MMM yyyy HH:mm:ss z');

    String stringToSign = 'PUT\n\n' + contentType + '\n' + dateHeader + '\n' + '/' + bucketName + '/' + objectKey;
    Blob mac = Crypto.generateMac('HMACSHA1', Blob.valueOf(stringToSign), Blob.valueOf(secretKey));
    String authHeader = 'AWS' + ' ' + accessKey + ':' + EncodingUtil.base64Encode(mac);

    HttpRequest req = new HttpRequest();
    req.setMethod('PUT');
    req.setEndpoint('https://' + host + '/' + objectKey);
    req.setHeader('Host', host);
    req.setHeader('Date', dateHeader);
    req.setHeader('Content-type', contentType);
    req.setHeader('Authorization', authHeader);
    req.setBodyAsBlob(EncodingUtil.base64Decode(EncodingUtil.base64Encode(cv.VersionData)));

    HttpResponse res = new Http().send(req);
    return res.getStatusCode();
  }
}

ContentVersionの中身をS3へPUTでアップロードします。読み取り専用のSOQLと外部への書き込みだけで、組織のデータは変更しません

Content-Length はヘッダーへ手で書きません。Base64エンコード後の文字列長を Content-Length に入れると、実際に送るバイナリの長さと食い違います。送信バイト数はSalesforce側が自動で計算するので、手計算は不要です。

取引先のカスタムボタンの詳細画面。表示ラベルやVisualforceページの設定が赤枠で囲まれている
Visualforceページを開くカスタムボタンの設定です。コンテンツソースとVisualforceページの指定をご覧ください。
取引先の詳細画面。ボタンが並ぶ列の中のファイルアップロードが赤枠で囲まれている
ページレイアウトに配置したあとの取引先画面です。赤枠のファイルアップロードを押すと転送が走ります。
閉じるとだけ書かれた小さなボタンが赤枠で囲まれている
Visualforceページに置いた閉じるボタンです。押すと取引先の詳細画面へ戻ります。
S3バケットの中のオブジェクト一覧。test.pngが1件、赤枠で示されている
S3側の一覧です。赤枠の行にファイル名とサイズ、更新日時が出ていれば転送できています。

大きいファイルを扱うときの制約

setBodyAsBlob で渡せるリクエストボディの大きさは、同期のApexで6MB、非同期(@future・バッチ)で12MBまでです。この上限はApexのヒープサイズ上限と同じ数字で、ContentVersion から読み込んだファイルをApexのメモリ上で扱っている間はヒープを消費します。動画のような大きいファイルは、同期の処理では送り切れないことがあります。バッチApexやキューアブルに乗せて非同期で送るか、送るファイルの大きさそのものを見直します。

ここで間違えやすい

間違い何が起きるか
アクセスキーとシークレットをApexの文字列に直書きするコードを読める人に漏れます。外部ログイン情報に移します
新しく作ったバケットにSignature Version 2で署名するAWSが2020年6月24日以降に作られたバケットでは拒否します
Content-Length をBase64後の文字列長で計算する実際に送るバイナリの長さと食い違い、リクエストが失敗します
Attachment のままファイルを読み込む新規のファイル添付には使われないオブジェクトです。ContentVersion を使います
コールアウト1回あたりのタイムアウトを既定の10秒のまま大きいファイルに使う最大120,000ミリ秒まで延ばせますが、延ばさないとタイムアウトで失敗します

確認した環境

  • 2026年9月/Salesforce Summer '26(APIバージョン67.0)時点の公式ドキュメントで、コールアウトの制限と指定ログイン情報の仕様を確認しています
  • AWS Signature Version 2の廃止時期は、AWSの公式ブログで確認しています

まとめ

  • Apexのコールアウトで、レコードに添付したファイルをS3へ自動で送れます
  • 認証情報は外部ログイン情報(指定ログイン情報)に置き、Apexのコードには書きません
  • 自前で署名する方式(Signature Version 2)は、2020年6月24日以降に作ったバケットでは使えません
  • ファイルはAttachmentではなくContentVersionから取得します
  • 送れるボディの大きさは同期6MB・非同期12MBまでです

Salesforceの導入・運用についてご相談ください

導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。