【问题标题】:Google Cloud Storage Signed URLs -- How to specify a maximum file size?谷歌云存储签名网址——如何指定最大文件大小?
【发布时间】:2018-01-30 10:37:22
【问题描述】:

目标

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

问题

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

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

建议的解决方案

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

解决方案有问题

有没有办法强制上传文件的最大大小? 显然,我们希望避免用户尝试上传 20GB 的文件,而我们预计

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

似乎有一种方法可以使用 Policy Documents (Stack Overflow answer) 来做到这一点,但这个问题现在已经超过 2 年了。

【问题讨论】:

    标签: http upload google-cloud-platform google-cloud-storage pre-signed-url


    【解决方案1】:

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

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

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

    【讨论】:

    • 同一个链接是这样写的:“注意:除非您需要使用 HTML 表单(通常通过网络浏览器)上传对象,否则我们强烈建议使用 PUT 对象而不是 POST。我>”。但是,没有为 PUT 对象定义 content-length-range。这是包含但未记录的功能吗?
    • 不,不幸的是,它只存在于 POST 中。
    • 所以 Google 建议使用 PUT 将对象上传到 Cloud Storage,但不允许您指定最大文件大小???似乎 PUT 完全没用......
    • 使用PUT方法,你可以尝试使用x-goog-content-length-range header。更多信息请访问cloud.google.com/storage/docs/xml-api/…
    • 我试过这个,但是 x-goog-content-length-range 不起作用,它在哪里? $url = $object-&gt;signedUrl(new \DateTime('120 min'),['method' =&gt; 'PUT','contentType' =&gt; "$type",'version' =&gt; 'v4', 'x-goog-content-length-range' =&gt; '0,1' ]);
    【解决方案2】:

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

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

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

    X-Upload-Content-Length 将不起作用,您仍然可以上传更大的文件

    【讨论】:

    • 我测试了x-goog-content-length-range 这个,它的作用是截断上传的文件并且不会因为错误的大小而取消。
    • 我试过这个,但是 x-goog-content-length-range 不起作用,它在哪里? $url = $object-&gt;signedUrl(new \DateTime('120 min'),['method' =&gt; 'PUT','contentType' =&gt; "$type",'version' =&gt; 'v4', 'x-goog-content-length-range' =&gt; '0,1' ]);
    【解决方案3】:

    签署 content-length 应该可以解决问题。

    即使 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
      }
    }
    

    【讨论】:

    • 这对我来说效果很好。它也适用于 PUT 请求
    【解决方案4】:

    您可以使用X-Upload-Content-Length 代替Content-LengthSee blog post here.

    在服务器端(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
      }
    ]
    

    【讨论】:

    • 这也对我有用。此链接的更多详细信息:blog.koliseo.com/…
    • 这允许上传超出所需限制,您只需在签名时撒谎 X-Upload-Content-Length 并在实际上传时设置相同的值。
    • @RaduDiță 签名是在服务器端完成的,而不是在客户端。这个想法是在签名之前验证内容长度,如果它无效则抛出异常。
    • @NachoColoma 即使在服务器端进行检查,上传时也可以躺在客户端。 GCS 不检查提供的 X-Upload-Content-Length 是否为实际内容长度。例如,您可以获得 1MB 的有效签名并上传 2MB 的文档。我能够让 GCS 实际阻止上传的唯一方法是使用 Content-Length。
    【解决方案5】:

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

    在前端,我们必须在headerX-Upload-Content-Length中设置相同数量的Size

    
    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);
      });
    }
    
    

    别忘了在GS CORS中配置responseHeader

    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"]}]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-25
      • 2018-02-24
      • 2021-03-07
      • 2018-10-10
      • 2017-12-21
      • 2018-05-31
      • 2015-07-17
      • 2018-11-25
      相关资源
      最近更新 更多