javascript 通过 AWS 开发工具包创建签名的 S3 和 Cloudfront URL

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21521302/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 21:05:25  来源:igfitidea点击:

Creating signed S3 and Cloudfront URLs via the AWS SDK

javascriptnode.jsamazon-web-servicesamazon-s3pre-signed-url

提问by Jason Sims

Has anyone successfully used the AWS SDK to generate signed URLs to objects in an S3 bucket which also work over CloudFront? I'm using the JavaScript AWS SDKand it's really simple to generate signed URLs via the S3 links. I just created a private bucket and use the following code to generate the URL:

有没有人成功使用 AWS 开发工具包为 S3 存储桶中的对象生成签名 URL,该对象也可以在 CloudFront 上运行?我正在使用JavaScript AWS SDK,通过 S3 链接生成签名 URL 非常简单。我刚刚创建了一个私有存储桶并使用以下代码生成 URL:

var AWS = require('aws-sdk')
  , s3 = new AWS.S3()
  , params = {Bucket: 'my-bucket', Key: 'path/to/key', Expiration: 20}

s3.getSignedUrl('getObject', params, function (err, url) {
  console.log('Signed URL: ' + url)
})

This works great but I also want to expose a CloudFront URL to my users so they can get the increased download speeds of using the CDN. I setup a CloudFront distribution which modified the bucket policy to allow access. However, after doing this any file could be accessed via the CloudFront URL and Amazon appeared to ignore the signature in my link. After reading some more on this I've seen that people generate a .pem file to get signed URLs working with CloudFront but why is this not necessary for S3? It seems like the getSignedUrl method simply does the signing with the AWS Secret Key and AWS Access Key. Has anyone gotten a setup like this working before?

这很好用,但我也想向我的用户公开一个 CloudFront URL,以便他们可以通过使用 CDN 获得更高的下载速度。我设置了一个 CloudFront 分配,它修改了存储桶策略以允许访问。但是,执行此操作后,可以通过 CloudFront URL 访问任何文件,而亚马逊似乎忽略了我链接中的签名。在阅读了更多关于此的内容后,我看到人们生成了一个 .pem 文件来获取与 CloudFront 一起使用的签名 URL,但为什么这对于 S3 来说不是必需的?似乎 getSignedUrl 方法只是使用 AWS Secret Key 和 AWS Access Key 进行签名。有没有人以前做过这样的设置?

Update:After further research it appears that CloudFront handles URL signatures completely different from S3 [link]. However, I'm still unclear as to how to create a signed CloudFront URL using Javascript.

更新:经过进一步研究,似乎 CloudFront 处理与 S3 [link]完全不同的 URL 签名。但是,我仍然不清楚如何使用 Javascript 创建签名的 CloudFront URL。

回答by Jason Sims

Update:I moved the signing functionality from the example code below into the aws-cloudfront-signpackage on NPM. That way you can just require this package and call getSignedUrl().

更新:我将签名功能从下面的示例代码移到NPM 上的aws-cloudfront-sign包中。这样你就可以只需要这个包并调用getSignedUrl().



After some further investigation I found a solution which is sort of a combo between this answerand a method I found in the Boto library. It is true that S3 URL signatures are handled differently than CloudFront URL signatures. If you just need to sign an S3 link then the example code in my initial question will work just fine for you. However, it gets a little more complicated if you want to generate signed URLs which utilize your CloudFront distribution. This is because CloudFront URL signatures are not currently supported in the AWS SDK so you have to create the signature on your own. In case you also need to do this, here are basic steps. I'll assume you already have an S3 bucket setup:

经过一些进一步的调查,我找到了一个解决方案,它是这个答案和我在Boto 库中找到的方法之间的组合。S3 URL 签名的处理方式确实与 CloudFront URL 签名不同。如果您只需要签署一个 S3 链接,那么我最初问题中的示例代码对您来说就足够了。但是,如果您想生成使用 CloudFront 分配的签名 URL,则情况会变得稍微复杂一些。这是因为 AWS 开发工具包当前不支持 CloudFront URL 签名,因此您必须自己创建签名。如果您还需要这样做,这里是基本步骤。我假设您已经有一个 S3 存储桶设置:

Configure CloudFront

配置 CloudFront

  1. Create a CloudFront distribution
  2. Configure your origin with the following settings
    • Origin Domain Name: {your-s3-bucket}
    • Restrict Bucket Access: Yes
    • Grant Read Permissions on Bucket: Yes, Update Bucket Policy
  3. Create CloudFront Key Pair. Should be able to do this here.
  1. 创建 CloudFront 分配
  2. 使用以下设置配置您的来源
    • 源域名:{your-s3-bucket}
    • 限制存储桶访问:是
    • 授予对存储桶的读取权限:是的,更新存储桶策略
  3. 创建 CloudFront 密钥对。如果能够做到这一点在这里

Create Signed CloudFront URL

创建签名的 CloudFront URL

To great a signed CloudFront URL you just need to sign your policy using RSA-SHA1 and include it as a query param. You can find more on custom policies herebut I've included a basic one in the sample code below that should get you up and running. The sample code is for Node.js but the process could be applied to any language.

要获得签名的 CloudFront URL,您只需使用 RSA-SHA1 签署您的策略并将其作为查询参数包含在内。您可以在此处找到有关自定义策略的更多信息,但我在下面的示例代码中包含了一个基本的策略,可以帮助您启动和运行。示例代码适用于 Node.js,但该过程可以应用于任何语言。

var crypto = require('crypto')
  , fs = require('fs')
  , util = require('util')
  , moment = require('moment')
  , urlParse = require('url')
  , cloudfrontAccessKey = '<your-cloudfront-public-key>'
  , expiration = moment().add('seconds', 30)  // epoch-expiration-time

// Define your policy.
var policy = {
   'Statement': [{
      'Resource': 'http://<your-cloudfront-domain-name>/path/to/object',
      'Condition': {
         'DateLessThan': {'AWS:EpochTime': '<epoch-expiration-time>'},
      }
   }]
}

// Now that you have your policy defined you can sign it like this:
var sign = crypto.createSign('RSA-SHA1')
  , pem = fs.readFileSync('<path-to-cloudfront-private-key>') 
  , key = pem.toString('ascii')

sign.update(JSON.stringify(policy))
var signature = sign.sign(key, 'base64')

// Finally, you build the URL with all of the required query params:
var url = {
  host: '<your-cloudfront-domain-name>',
  protocol: 'http',
  pathname: '<path-to-s3-object>'
}    
var params = {
  'Key-Pair-Id=' + cloudfrontAccessKey,
  'Expires=' + expiration,
  'Signature=' + signature
}
var signedUrl = util.format('%s?%s', urlParse.format(url), params.join('&'))

return signedUrl

回答by Alexandre Bianchi

For my code to work with Jason Sims's code, I also had to convert policy to base64 and add it to the final signedUrl, like this:

为了让我的代码与 Jason Sims 的代码一起工作,我还必须将策略转换为 base64 并将其添加到最终的 signedUrl 中,如下所示:

sign.update(JSON.stringify(policy))
var signature = sign.sign(key, 'base64')

var policy_64 = new Buffer(JSON.stringify(policy)).toString('base64'); // ADDED

// Finally, you build the URL with all of the required query params:
var url = {
  host: '<your-cloudfront-domain-name>',
  protocol: 'http',
  pathname: '<path-to-s3-object>'
}    
var params = {
  'Key-Pair-Id=' + cloudfrontAccessKey,
  'Expires=' + expiration,
  'Signature=' + signature,
  'Policy=' + policy_64  // ADDED 
}

回答by msg45f

AWS includes some built in classes and structures to assist in the creation of signed URLs and Cookies for CloudFront. I utilized these alongside the excellent answer by Jason Sims to get it working in a slightly different pattern (which appears to be very similar to the NPM package he created).

AWS 包括一些内置类和结构,以帮助为 CloudFront 创建签名 URL 和 Cookie。我将这些与 Jason Sims 的优秀答案一起使用,使其以稍微不同的模式工作(这似乎与他创建的 NPM 包非常相似)。

Namely, the AWS.CloudFront.Signer type description which abstracts the process of creating signed URLs and Cookies.

即,AWS.CloudFront.Signer 类型描述抽象了创建签名 URL 和 Cookie 的过程。

export class Signer {
    /**
     * A signer object can be used to generate signed URLs and cookies for granting access to content on restricted CloudFront distributions.
     * 
     * @param {string} keyPairId - The ID of the CloudFront key pair being used.
     * @param {string} privateKey - A private key in RSA format.
     */
    constructor(keyPairId: string, privateKey: string);

    ....
}

And either an options with a policy JSON string or without a policy with a url and expiration time.

以及带有策略 JSON 字符串的选项或不带有带有 url 和过期时间的策略的选项。

export interface SignerOptionsWithPolicy {
    /**
     * A CloudFront JSON policy. Required unless you pass in a url and an expiry time. 
     */
    policy: string;
}
export interface SignerOptionsWithoutPolicy {
    /**
     * The URL to which the signature will grant access. Required unless you pass in a full policy.
     */
    url: string
    /**
     * A Unix UTC timestamp indicating when the signature should expire. Required unless you pass in a full policy.
     */
    expires: number
}

Sample implementation:

示例实现:

import aws, { CloudFront } from 'aws-sdk';

export async function getSignedUrl() {

    // https://abc.cloudfront.net/my-resource.jpg
    const url = <cloud front url/resource>;

    // Create signer object - requires a public key id and private key value
    const signer = new CloudFront.Signer(<public-key-id>, <private-key>);

    // Setup expiration time (one hour in the future, in this case)
    const expiration = new Date();
    expiration.setTime(expiration.getTime() + 1000 * 60 * 60);
    const expirationEpoch = expiration.valueOf();

    // Set options (Without policy in this example, but a JSON policy string can be substituted)
    const options = {
        url: url,
        expires: expirationEpoch
    };

    return new Promise((resolve, reject) => {
        // Call getSignedUrl passing in options, to be handled either by callback or synchronously without callback
        signer.getSignedUrl(options, (err, url) => {
            if (err) {
                console.error(err.stack);
                reject(err);
            }
            resolve(url);
        });
    });
}