S3、签名 URL 和缓存

20

我正在我的Web应用程序(nodejs)上使用knox nodejs库生成已签名的URL。

然而,问题出现了,对于每个请求,我都需要重新生成当前用户的唯一GET签名URL,使浏览器的缓存控制失效。

我在网上搜索了很久,但没有成功,因为浏览器似乎将完整的URL作为缓存键值,所以我真的很好奇如何在给定情况下(nodejs,knox库)解决问题,并在仍然能够为每个请求生成签名URL的同时使用缓存控制,因为我需要验证用户的访问权限。

虽然我不相信没有解决方案。


一个建议:将签名部分从URL中删除,并添加自定义HTTP头进行身份验证。 - Nitzan Shaked
我很好奇使用Knox库是否可能实现这一点。 - anderswelt
你有同样的困惑吗?你找到了解决方法吗? - Sello Mkantjwa
5个回答

4

我正在使用Java AmazonS3客户端,但处理过程应相同。

有一种策略可以用来处理这种情况。

您可以使用固定的日期时间作为到期日期。 我将此日期设置为明天中午12点

现在每次生成URL时,它将在当天内始终保持相同,直到00:00。 这样浏览器缓存就可以在一定程度上使用。


1
不错的方法。完整解释请参见:https://www.bennadel.com/blog/3686-calculating-a-consistent-cache-friendly-expiration-date-for-signed-urls-in-lucee-5-3-2-77.htm - Adarsh Madrecha
我使用这个函数s3.getSignedUrl("getObject", {Bucket: "mybucket", Key:'myImage", Expires:18000} ),它指定了URL在18000秒或5小时后过期。如何设置固定的日期时间而不是一个时间量作为过期时间? - KJ Ang
1
@AdarshMadrecha的答案就是正确方法。我测试了他的答案,它可以正常工作。请阅读他提供的链接:https://advancedweb.hu/cacheable-s3-signed-urls/。该链接详细解释了这种方法的原理。 - KJ Ang

3

扩展@semir-deljić的回答。

每次调用getSignedUrl函数,它都会生成新的URL。这将导致即使存在Cache Control头也无法缓存图片。

因此,我们使用timekeeper库来冻结时间。现在当函数被调用时,它认为时间没有流逝,并返回相同的URL。

const moment = require('moment');
const tk = require("timekeeper");

function url4download(awsPath, awsKey) {

  function getFrozenDate() {
    return moment().startOf('week').toDate();
  }

  // Paramters for getSignedUrl function
  const params = {
    // Ref: https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html
    // Ref: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
    Bucket: awsBucket,
    Key: `${awsPath}/${awsKey}`,
    // 604800 == 7 days
    ResponseCacheControl: `public, max-age=604800, immutable`,
    Expires: 604800, // 7 days is max
  };

  const url = tk.withFreeze(getFrozenDate(), () => {
    return S3.getSignedUrl('getObject', params);
  });
  return url;
}

注意: 使用moment().toDate()作为时间记录器需要本机日期对象。

即使问题是关于使用knox库,我的答案也使用了AWS官方库。

// This is how the AWS & S3 is initiliased.
const AWS = require('aws-sdk');

const S3 = new AWS.S3({
  accessKeyId: awsAccessId,
  secretAccessKey: awsSecretKey,
  region: 'ap-south-1',
  apiVersion: '2006-03-01',
  signatureVersion: 'v4',
});

激励: https://advancedweb.hu/cacheable-s3-signed-urls/
这篇文章涉及IT技术,介绍了如何创建可缓存的S3签名URL来提高AWS S3的性能。

1
这就是答案。它的一个关键概念是:欺骗AWS S3的getSignedUrl函数,使其认为它在过去的一个“预设”时间内。例如,每天10分钟间隔的预设时间是1pm、1:10pm、1:20pm等。实际例子:当用户在1:02pm请求图像时,服务器设置为最近的预设时间(使用timekeeper),即1pm,并运行s3.getSignedUrl();这就好像服务器在1pm运行了s3.getSignedUrl();1.04pm的请求将在1pm运行s3.getSignedUrl(),以此类推。因此,从1pm到1:10pm生成的所有URL都是相同的!我测试过了,它可行! - KJ Ang
1
我必须说,“冻结时间”这个词组是具有误导性的。在这里,timekeeper的作用更像是一个可以前往指定时间的时间旅行者。代码tk.withFreeze(time, callback)将Date对象设置为指定时间,然后运行callback。因此,timekeeper的有用之处在于,你可以在任何时候,向后或向前前往指定的时间点并运行你的callback - KJ Ang

2

1
这对我来说行不通,因为用户可能会经常更改他们的IP地址 :( - anderswelt
@anderswelt,你找到解决方案了吗?我在安卓上遇到了同样的问题。 - Zicsus

1
在计算签名URL时,您可以将“signingDate”设置为过去的一个固定时间,例如昨天早上,然后从那个时间开始计算到期时间。不要忘记使用UTC并考虑时区。
import { S3Client, GetObjectCommand, GetObjectCommandInput } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

let signingDate = new Date();
signingDate.setUTCHours(0, 0, 0, 0);
signingDate.setUTCDate(signingDate.getUTCDate() - 1);

let params: GetObjectCommandInput = {
    Bucket: BUCKET_NAME,
    Key: filename
};
const command = new GetObjectCommand(params);
const url = await getSignedUrl(s3Client,
    command, {
        expiresIn: 3 * 3600 * 24, // 1 day until today + 1 expiration + 1 days for timezones
        signableHeaders: new Set < string > (),
        signingDate: signingDate
    });

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接