署名付き URL の説明
署名付き URL は、AWS S3(Simple Storage Service)のリソースに対して一時的なアクセス許可を付与するための URL です。通常、S3 バケット内のオブジェクト(ファイル)に対して生成されます。
一般的な使用例は、以下のような場合です:
- ユーザーがプライベートな S3 オブジェクトに直接アクセスできるようにする場合(ダウンロード、表示など)。
- S3 オブジェクトに対して一時的なアップロードポリシーを提供する場合。
署名付き URL は、AWS の認証情報(アクセスキー、シークレットアクセスキー)に基づいて生成されます。これにより、リクエストが認証され、許可された期間内に限定されます。また、HTTPS 経由でアクセスされるため、セキュアな通信が確保されます。
署名付き URL の生成には、以下の要素が含まれます:
- リクエストの HTTP メソッド(GET、PUT、DELETE など)
- リクエストのヘッダー(任意)
- リクエストのパラメーター(任意)
- リソース(S3 オブジェクト)のパス
- アクセスキー、シークレットアクセスキー
これらの要素を使用して、署名付き URL を生成するための署名アルゴリズムが適用されます。この署名アルゴリズムは、AWS アカウントの認証情報に基づいて計算され、リクエストに付加されます。
署名付き URL は、一時的なアクセス許可を持つため、有効期限が設定されます。有効期限が切れると、その URL は無効になります。
生成された署名付き URL を使用すると、ユーザーは有効期限内に S3 オブジェクトにアクセスできます。これにより、AWS アクセスキーとシークレットアクセスキーを直接公開せずに、セキュアなファイルの共有や制御が可能になります。
署名付き URL アップロード制限
AWS S3 における署名付き URL の最大アップロードサイズは、1 回の PUT リクエストでの制限に依存します。一般的に、署名付き URL を使用してアップロードするファイルのサイズには最大サイズが 5GB となっています。そのため、署名付き URL を使用してファイルをアップロードする場合も、1 つのファイルのサイズは 5GB 以下に制限されます。
大容量のファイルを S3 にアップロードする場合、複数のパートに分割してアップロードするマルチパートアップロードという方法を使用することが一般的です。マルチパートアップロードを使用すると、大きなファイルを分割し、並行してアップロードすることができます。この場合、各パートの最小サイズは 5MB、最大サイズは 5GB となります。
したがって、署名付き URL を使用して S3 にファイルをアップロードする場合、1 回の PUT リクエストでの最大サイズは 5GB です。大容量のファイルをアップロードする際は、マルチパートアップロードを検討することをおすすめします。
署名付き URL ダウンロード制限
AWS S3 の署名付き URL を使用してファイルをダウンロードする際の制限は、特定の制約はありません。ただし、以下の点に留意する必要があります。
-
有効期限: 署名付き URL には有効期限が設定されます。有効期限が切れた URL はアクセスできなくなります。有効期限は生成時に指定され、期限が切れると URL は無効になります。
-
アクセス許可: ダウンロード対象のオブジェクトに対して、アクセス許可が必要です。オブジェクトがプライベートである場合、ダウンロードするためには署名付き URL を持つユーザーに対して適切なアクセス許可が必要です。
-
ダウンロードサイズ: S3 のダウンロード制限は、署名付き URL に直接関連するものではありません。S3 のダウンロードには一般的に特定の制限はありませんが、ネットワークの帯域幅やダウンロード元・ダウンロード先の制約によって制限が発生する可能性があります。
署名付付き URL 発行方法
以下は、Apex で署名付き URL を生成するためのコード例です。
public class AwsS3Util {
private static final String AMZ_ALGORITHM = 'AWS4-HMAC-SHA256';
private static final String ACCESS_KEY = 'アクセスキー';
private static final String SECRET_KEY = 'シークレットキー';
private static final String BUCKET_NAME = 'バケット名';
private static final String REGION = '地域';
private static final String EXPIRES = '有効時間(ms)';
//請求メソッド
public enum RequestMethod {
GET, PUT
}
/**
* S3の署名付きURLを生成
* @param {RequestMethod} method アップロードの場合 : PUT, ダウンロードの場合 : GET
* @param {String} fileName ファイル名
* @return 署名付きURL
*/
public static String generateS3PreSignedURL(RequestMethod method, String fileName) {
String s3key = fileName.replaceAll('\\s+', '');
Datetime currentDateTime = Datetime.now();
String dateOnly = currentDateTime.formatGmt('yyyyMMdd');
String req = dateOnly + '/' + REGION + '/s3/aws4_request';
String xAmzCredentialStr = ACCESS_KEY + '/' + req;
String xAmzDate = currentDateTime.formatGmt('yyyyMMdd\'T\'HHmmss\'Z\'');
String xAmzSignedHeaders = 'host';
String host = BUCKET_NAME + '.s3.' + REGION + '.amazonaws.com';
String canonicalRequest =
method.name() +
'\n' +
'/' +
// UriEncode(s3key, false) +
EncodingUtil.urlEncode(s3key, 'UTF-8') +
'\n' +
UriEncode('X-Amz-Algorithm', true) +
'=' +
UriEncode(AMZ_ALGORITHM, true) +
'&' +
UriEncode('X-Amz-Credential', true) +
'=' +
UriEncode(xAmzCredentialStr, true) +
'&' +
UriEncode('X-Amz-Date', true) +
'=' +
UriEncode(xAmzDate, true) +
'&' +
UriEncode('X-Amz-Expires', true) +
'=' +
UriEncode(EXPIRES, true) +
'&' +
UriEncode('X-Amz-SignedHeaders', true) +
'=' +
UriEncode(xAmzSignedHeaders, true) +
'\n' +
'host:' +
host +
'\n\n' +
'host\n' +
'UNSIGNED-PAYLOAD';
String stringToSign =
AMZ_ALGORITHM +
'\n' +
xAmzDate +
'\n' +
req +
'\n' +
EncodingUtil.convertToHex(Crypto.generateDigest('SHA-256', Blob.valueOf(canonicalRequest)));
Blob dateKey = Crypto.generateMac(
'hmacSHA256',
Blob.valueOf(dateOnly),
Blob.valueOf('AWS4' + SECRET_KEY));
Blob dateRegionKey = Crypto.generateMac(
'hmacSHA256',
Blob.valueOf(REGION),
dateKey);
Blob dateRegionServiceKey = Crypto.generateMac(
'hmacSHA256',
Blob.valueOf('s3'),
dateRegionKey);
Blob signingKey = Crypto.generateMac(
'hmacSHA256',
Blob.valueOf('aws4_request'),
dateRegionServiceKey);
Blob signature = Crypto.generateMac(
'hmacSHA256',
Blob.valueOf(stringToSign),
signingKey);
String signatureStr = EncodingUtil.convertToHex(signature);
return 'https://' +
host +
'/' +
s3key +
'?X-Amz-Algorithm=' +
AMZ_ALGORITHM +
'&X-Amz-Credential=' +
EncodingUtil.urlEncode(xAmzCredentialStr, 'UTF-8') +
'&X-Amz-Date=' +
xAmzDate +
'&X-Amz-Expires=' +
String.valueOf(EXPIRES) +
'&X-Amz-Signature=' +
signatureStr +
'&X-Amz-SignedHeaders=host';
}
/**
* UriEncode変換
* @param {String} input
* @param {Boolean} encodeSlash
* @return 変換後のUriEncode
*/
private static String UriEncode(String input, Boolean encodeSlash) {
String result = '';
for (Integer i = 0; i < input.length(); i++) {
String ch = input.substring(i, i + 1);
if (
(ch >= 'A' &&
ch <= 'Z') ||
(ch >= 'a' &&
ch <= 'z') ||
(ch >= '0' &&
ch <= '9') ||
ch == '_' ||
ch == '-' ||
ch == '~' ||
ch == '.'
) {
result += ch;
} else if (ch == '/') {
result += encodeSlash ? '%2F' : ch;
} else {
String hexValue = EncodingUtil.convertToHex(Blob.valueOf(ch))
.toUpperCase();
if (hexValue.length() == 2) {
result += '%' + hexValue;
} else if (hexValue.length() == 4) {
result +=
'%' +
hexValue.substring(0, 2) +
'%' +
hexValue.substring(2);
}
}
}
return result;
}
}