当前位置: 首页 > 知识库问答 >
问题:

AWS S3预签名请求缓存

公西岳
2023-03-14

我想将用户配置文件图片存储在S3桶中,但保留这些图片的私密性。为了做到这一点,我正在创建一个预先签名的网址,每当图像是必需的。然而,这每次都会创建一个独特的网址,这意味着浏览器永远不会缓存图像,最终我会在GET请求中支付更多。

下面是生成url的代码示例,我使用的是Laravel:

$s3 = \Storage::disk('s3');
$client = $s3->getDriver()->getAdapter()->getClient();
$expiry = new \DateTime('2017-07-25');

$command = $client->getCommand('GetObject', [
    'Bucket' => \Config::get('filesystems.disks.s3.bucket'),
    'Key'    => $key
]);

$request = $client->createPresignedRequest($command, $expiry);

return (string) $request->getUri();

我认为,通过指定日期时间而不是时间单位,它将创建相同的url,但实际上会将剩余秒数添加到url,下面是一个示例:

xxxx.s3.eu-west-2.amazonaws.com/profile pics/92323.png?X-Amz-Content-Sha256=无符号有效载荷

是否可以生成可重复的预签名请求url,以便用户浏览器可以缓存图像?

共有3个答案

贲凌
2023-03-14

类似于@Aragorn的概念,但这是更完整的代码。这又是Java。另外,由于我的应用程序是多区域的,所以我必须输入区域属性。

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.signer.Signer;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;

import javax.annotation.PostConstruct;
import javax.validation.constraints.NotNull;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

@Component
@Slf4j
public class S3Operations {

    @Autowired
    private Signer awsSigner;

    private final Map<Region, S3Presigner> presignerMap = new ConcurrentHashMap<>();

    private S3Presigner buildPresignerForRegion(
      AwsCredentialsProvider credentialsProvider,
      Region region) {

        return S3Presigner.builder()
            .credentialsProvider(credentialsProvider)
            .region(region)
            .build();

    }


    /**
     * Convert an S3 URI to a normal HTTPS URI that expires.
     *
     * @param s3Uri S3 URI (e.g. s3://bucketname/ArchieTest/フェニックス.jpg)
     * @return https URI
     */
    @SneakyThrows
    public URI getExpiringUri(final URI s3Uri) {

        final GetObjectRequest getObjectRequest =
            GetObjectRequest.builder()
                .bucket(s3Uri.getHost())
                .key(s3Uri.getPath().substring(1))
                .overrideConfiguration(builder -> builder.signer(awsSigner))
                .build();

        final Region bucketRegion = bucketRegionMap.computeIfAbsent(s3Uri.getHost(),
            bucketName -> {
                final GetBucketLocationRequest getBucketLocationRequest = GetBucketLocationRequest.builder()
                    .bucket(bucketName)
                    .build();

                return Region.of(s3Client.getBucketLocation(getBucketLocationRequest).locationConstraint().toString());
            });

        final GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder()
            .signatureDuration(Duration.ofSeconds(0)) // required, but ignored
            .getObjectRequest(getObjectRequest)
            .build();

        return presignerMap.computeIfAbsent(bucketRegion, this::buildPresignerForRegion).presignGetObject(getObjectPresignRequest).url().toURI();

    }

对于上面注入的CustomAwsSigner。关键的区别在于我抛出了一个不受支持的操作异常。

import org.jetbrains.annotations.TestOnly;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.auth.signer.AwsS3V4Signer;
import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute;
import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.signer.Presigner;
import software.amazon.awssdk.core.signer.Signer;
import software.amazon.awssdk.http.SdkHttpFullRequest;

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;

/**
 * This is a custom signer where the expiration is preset to a 5 minute block within an hour.
 * This must only be used for presigning.
 */
@Component
public class CustomAwsSigner implements Signer, Presigner {
    private final AwsS3V4Signer theSigner = AwsS3V4Signer.create();

    /**
     * This is the clip time for the expiration.  This should be divisible into 60.
     */
    @Value("${aws.s3.clipTimeInMinutes:5}")
    private long clipTimeInMinutes;

    @Value("${aws.s3.expirationInSeconds:3600}")
    private long expirationInSeconds;

    /**
     * Computes the base time as the processing time to the floor of nearest clip block.
     *
     * @param processingDateTime processing date time
     * @return base time
     */
    @TestOnly
    public ZonedDateTime computeBaseTime(final ZonedDateTime processingDateTime) {

        return processingDateTime
            .truncatedTo(ChronoUnit.MINUTES)
            .with(temporal -> temporal.with(ChronoField.MINUTE_OF_HOUR, temporal.get(ChronoField.MINUTE_OF_HOUR) / clipTimeInMinutes * clipTimeInMinutes));

    }

    @Override
    public SdkHttpFullRequest presign(final SdkHttpFullRequest request, final ExecutionAttributes executionAttributes) {

        final Instant baselineInstant = computeBaseTime(ZonedDateTime.now()).toInstant();

        final Aws4PresignerParams signingParams = Aws4PresignerParams.builder()
            .awsCredentials(executionAttributes.getAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS))
            .signingName(executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME))
            .signingRegion(executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION))
            .signingClockOverride(Clock.fixed(baselineInstant, ZoneId.of("UTC")))
            .expirationTime(baselineInstant.plus(expirationInSeconds, ChronoUnit.SECONDS))
            .build();
        return theSigner.presign(request, signingParams);

    }

    @Override
    public SdkHttpFullRequest sign(final SdkHttpFullRequest request, final ExecutionAttributes executionAttributes) {

        throw new UnsupportedOperationException("this class is only used for presigning");

    }
}
丌官博文
2023-03-14

也许您可以向应用程序添加一个经过身份验证的终结点,并在该终结点内检索图像,而不是使用预先签名的URL机制?在您的img标签等中使用此URL。这个endpoint可以缓存图像,并为浏览器提供适当的响应标头来缓存图像。

仉明知
2023-03-14

也许是迟来的回复,但为了将来阅读这篇文章的人的利益,我会添加我的方法。

要强制浏览器缓存启动,每次生成相同的精确url非常重要,直到您特别希望浏览器从服务器重新加载内容。不幸的是,sdk中提供的预签名器依赖于每次指向新url的当前时间戳。

这个例子是用Java编写的,但是可以很容易地扩展到其他语言

GetObjectRequestBuilder(用于创建预签名的url)允许覆盖配置。我们可以提供自定义签名者来修改其行为

AwsRequestOverrideConfiguration.builder()
    .signer(new CustomAwsS3V4Signer())
    .credentialsProvider(<You may need to provide a custom credential provider 
here>)))
.build())

GetObjectRequest getObjectRequest =
    GetObjectRequest.builder()
            .bucket(getUserBucket())
            .key(key)
            .responseCacheControl("max-age="+(TimeUnit.DAYS.toSeconds(7)+ defaultIfNull(version,0L)))
            .overrideConfiguration(overrideConfig)
            .build();

public class CustomAwsS3V4Signer implements Presigner, Signer
{
    private final AwsS3V4Signer awsSigner;

    public CustomAwsS3V4Signer()
    {
        awsSigner = AwsS3V4Signer.create();
    }

@Override
public SdkHttpFullRequest presign(SdkHttpFullRequest request, ExecutionAttributes executionAttributes)
{
    Instant baselineInstant = Instant.now().truncatedTo(ChronoUnit.DAYS);

    executionAttributes.putAttribute(AwsSignerExecutionAttribute.PRESIGNER_EXPIRATION,
            baselineInstant.plus(3, ChronoUnit.DAYS));

在这里,我们重写签名时钟来模拟一个固定的时间,最终导致在未来的某个日期之前在url中保持一致的过期和签名:

    Aws4PresignerParams.Builder builder = Aws4PresignerParams.builder()
            .signingClockOverride(Clock.fixed(baselineInstant, ZoneId.of("UTC")));

    Aws4PresignerParams signingParams =
            extractPresignerParams(builder, executionAttributes).build();

    return awsSigner.presign(request, signingParams);
    }
}

有关详情,请参阅:

https://murf.ai/resources/creating-cache-friendly-presigned-s3-urls-using-v4signer-q1bbqgk

 类似资料:
  • 我试图上传一个图像使用预先签名的网址 我得到了一个类似的url https://s3.eu-west-1.amazonaws.com/bucket/folder/access.JPG?AWSAccessKeyId=xxxx 我已经尝试上传文件与内容类型图像/jpg,多部分/表单数据。 尝试生成没有文件类型和上传的网址。 尝试了放后法 但似乎什么都不管用 错误总是: 我们计算的请求签名与您提供的签名

  • 请求缓存 支持请求缓存功能,支持对GET请求设置缓存访问,并设置有效期。 请求缓存仅对GET请求有效 有两种方式可以设置请求缓存: 路由设置 可以在路由规则里面调用cache方法设置当前路由规则的请求缓存,例如: // 定义GET请求路由规则 并设置3600秒的缓存 Route::get('new/:id','News/read')->cache(3600); 第二次访问相同的路由地址的时候,会自

  • 我的前端是Nativescript Vue应用程序。后端是Rails。我从rrails服务器获得一个预先签名的url,并使用该url在客户端发送put请求以进行图像上传。我在rails上生成了一个预先签名的url,如下所示: 长话短说,上面的代码生成了一个预签名的url,我使用它来使用NativeScript-background-超文本传输协议插件在客户端执行放置请求: var会话=bghttp

  • 实现原理 实现参考『Thikphp 3.2-静态缓存』, 静态缓存只能提供HTML的缓存且只能以文件形式进行缓存。而请求缓存,支持ajax/html的缓存,而且可以根据缓存配置自行切换文件/redis的缓存 缓存检测: 行为app_begin时,检测路由是否需要有缓存,有则返回缓存内容(\Common\Behavior\ReadRequestCacheBehavior) 缓存更新: 行为ajax_

  • 我试图上传图像/视频使用PHP/Javascript到AWS S3 这是我的PHP代码 这是我的Java脚本代码 所有这些功能工作伟大,我想即使在执行它给我文件上传成功消息too.but当我检查s3什么都没有。 即使在控制台中,它也会像下面一样生成预签名URL。但当我尝试单击它时,它会给我SignatureDesNotMatch错误。(请单击URL)。可能是什么错误。。? https://ibra