Google Cloud Storage Подписанные URL - Как указать максимальный размер файла?

Цель

Мы хотим, чтобы пользователи могли загружать изображения в Google Cloud Storage.

проблема

Мы могли бы достичь этого косвенно с нашим сервером в качестве посредника - сначала пользователь загружает данные на наш сервер, затем наш привилегированный сервер может загружать данные в облачное хранилище.

Однако мы считаем, что это неоправданно медленно, и вместо этого хотели бы, чтобы пользователь загружал данные непосредственно в облачное хранилище.

Предложенное решение

Для прямой загрузки мы генерируем подписанный URL на нашем сервере. Подписанный URL-адрес указывает срок действия и может использоваться только с глаголом HTTP PUT. Пользователь может запросить подписанный URL, а затем - только в течение ограниченного времени - загрузить изображение по пути, указанному в подписанном URL.

Проблема с решением

Есть ли способ обеспечить максимальный размер загружаемого файла? Очевидно, что мы бы хотели, чтобы пользователи не пытались загружать файлы размером 20 ГБ, когда мы ожидаем, что файлы размером менее 1 МБ.

Кажется, что это очевидная уязвимость, но я не знаю, как ее устранить, все еще используя Signed URL.

Кажется, есть способ сделать это, используя документы политики ( ответ на переполнение стека), но этому вопросу уже более 2 лет.

5 ответов

Всем, кто смотрит на тент сегодня, следует помнить, что x-goog-content-length-range:0,25000 - допустимый способ ограничить размер загрузки от 0 до 25000 байтов для PUT

X-Upload-Content-Length

не будет работать, и вы все равно сможете загружать файлы большего размера

Подписание длины содержимого должно помочь.

Google Cloud не разрешает загрузку файлов большего размера, даже если длина содержимого установлена ​​на меньшее значение.

Вот как должны выглядеть параметры подписанного URL:

      const writeOptions: GetSignedUrlConfig = {
  version: 'v4',
  action: 'write',
  expires: Date.now() + 900000, // 15 minutes
  extensionHeaders: {
    "content-length": length // desired length in bytes
  }
}

Политические документы по-прежнему правильный ответ. Они описаны здесь: https://cloud.google.com/storage/docs/xml-api/post-object

Важная часть политического документа, который вам понадобится:

["content-length-range", <min_range>, <max_range>].

Мой рабочий код в NodeJS соответствовал https://blog.koliseo.com/limit-the-size-of-uploaded-files-with-signed-urls-on-google-cloud-storage/. Вы должны использовать версию v4

       public async getPreSignedUrlForUpload(
    fileName: string,
    contentType: string,
    size: number,
    bucketName: string = this.configService.get('DEFAULT_BUCKET_NAME'),
  ): Promise<string> {
    const bucket = this.storage.bucket(bucketName);
    const file = bucket.file(fileName);

    const response = await file.getSignedUrl({
      action: 'write',
      contentType,
      extensionHeaders: {
        'X-Upload-Content-Length': size,
      },
      expires: Date.now() + 60 * 1000, // 1 minute
      version: 'v4',
    });

    const signedUrl = this.maskSignedUrl(response[0], bucketName);
    return signedUrl;
  }

Во Frontend мы должны установить такое же количество Size в заголовке X-Upload-Content-Length

      
export async function uploadFileToGCP(
  signedUrl: string,
  file: any
): Promise<any> {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.withCredentials = process.env.NODE_ENV === 'production';

    xhr.addEventListener('readystatechange', function () {
      if (this.readyState === 4) {
        resolve(this.responseText);
      }
    });

    xhr.open('PUT', signedUrl, true);
    xhr.setRequestHeader('Content-Type', file.type);
    xhr.setRequestHeader('X-Upload-Content-Length', file.size);

    xhr.send(file);
  });
}

А также не забудьте настроить responseHeader в GS CORS

      gsutil cors get gs://asia-item-images
[{"maxAgeSeconds": 3600, "method": ["GET", "OPTIONS", "PUT"], "origin": ["*"], "responseHeader": ["Content-Type", "Access-Control-Allow-Origin", "X-Upload-Content-Length", "X-Goog-Resumable"]}]

Вы можете использовать X-Upload-Content-Length вместо Content-Length. См. Сообщение в блоге здесь.

На стороне сервера (Java):

Map<String, String> extensionHeaders = new HashMap<>();
extensionHeaders.put("X-Upload-Content-Length", "" + contentLength);
extensionHeaders.put("Content-Type", "application/octet-stream");

var url =
  storage.signUrl(
    blobInfo,
    15,
    TimeUnit.MINUTES,
    Storage.SignUrlOption.httpMethod(HttpMethod.PUT),
    Storage.SignUrlOption.withExtHeaders(extensionHeaders),
    Storage.SignUrlOption.withV4Signature()
  );

На стороне клиента (машинописный текст):

const response = await fetch(url, {
  method: 'PUT',
  headers: {
    'X-Upload-Content-Length': `${file.size}`,
    'Content-Type': 'application/octet-stream',
  },
  body: file,
});

Вам нужно будет настроить политику cors для своего ведра:

[
  {
    "origin": ["https://your-website.com"],
    "responseHeader": [
      "Content-Type",
      "Access-Control-Allow-Origin",
      "X-Upload-Content-Length",
      "x-goog-resumable"
    ],
    "method": ["PUT", "OPTIONS"],
    "maxAgeSeconds": 3600
  }
]
Другие вопросы по тегам