10

目标

我们希望用户能够将图像上传到 Google Cloud Storage。

问题

我们可以通过我们的服务器作为中间人间接实现这一点——首先,用户上传到我们的服务器,然后我们的特权服务器可以上传到云存储。

但是,我们认为这不必要地慢,而是希望用户直接上传到云存储。

建议的解决方案

为了实现直接上传,我们在服务器上生成一个签名 URL。签名 URL 指定过期时间,并且只能与 HTTP PUT 动词一起使用。用户可以请求签名 URL,然后 - 仅在有限的时间内 - 将图像上传到签名 URL 指定的路径。

解决方案的问题

有没有办法强制执行最大文件上传大小?显然,我们希望避免用户在我们期望 <1MB 文件时尝试上传 20GB 文件。

看起来这是一个明显的漏洞,但我不知道如何在仍然使用 SignedURLs 的同时解决它。

似乎有一种方法可以使用策略文档(堆栈溢出答案)来做到这一点,但这个问题现在已经超过 2 年了。

4

5 回答 5

5

政策文件仍然是正确答案。它们记录在这里:https ://cloud.google.com/storage/docs/xml-api/post-object#policydocument

您需要的政策文件的重要部分是:

["content-length-range", <min_range>, <max_range>].
于 2017-08-23T00:44:54.197 回答
4

对于今天查看答案的所有人,请注意该链接

x-goog-content-length-range:0,25000

是将上传大小限制在 0 到 25000 字节 Cloud Storage 之间的方法。

X-Upload-Content-Length将无法工作,您仍然可以上传更大的文件

于 2020-11-23T21:34:01.977 回答
1

签署内容长度应该可以解决问题。

即使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
  }
}
于 2021-03-28T12:01:24.550 回答
0

我在 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;
  }

在前端,我们必须在 header 中设置相同数量的 SizeX-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);
  });
}

并且不要忘记responseHeaderGS 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"]}]
于 2021-02-10T04:30:48.477 回答
0

您可以使用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
  }
]
于 2020-10-12T07:55:13.797 回答