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

Posted

技术标签:

【中文标题】通过 AWS 开发工具包创建签名的 S3 和 Cloudfront URL【英文标题】:Creating signed S3 and Cloudfront URLs via the AWS SDK 【发布时间】:2014-02-26 13:57:28 【问题描述】:

是否有人成功地使用 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)
)

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

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

【问题讨论】:

我不明白你的实现。你能详细解释一下吗?我需要为我的 android 应用程序做同样的事情。所以我的想法是:我的客户端将向我的服务器发送请求——>我的服务器将请求 AWS S3 获取签名 URL——>我的服务器将把这个 URL 发送回客户端完成上传部分。这是不是很好的方法。我的服务器是用 node.js 编写的。你能帮我做这件事吗? ? 如果它是用节点编写的,你应该可以使用我写的aws-cloudfront-sign 实用程序。那里的 README.md 应该解释你需要做什么。 【参考方案1】:

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


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

配置 CloudFront

    创建 CloudFront 分配 使用以下设置配置您的来源 源域名:your-s3-bucket 限制存储桶访问:是 授予对存储桶的读取权限:是,更新存储桶政策 创建 CloudFront 密钥对。应该可以做到这一点here。

创建签名的 CloudFront URL

要获得出色的签名 CloudFront URL,您只需使用 RSA-SHA1 对策略进行签名并将其作为查询参数包含在内。您可以在here 找到更多关于自定义策略的信息,但我在下面的示例代码中包含了一个基本的策略,它应该可以帮助您启动并运行。示例代码适用于 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

【讨论】:

嗨,我需要为浏览器创建签名的 url。我没有使用 node.js。我在浏览器中使用 sdk for javascript。你知道我必须为此包含什么库吗? var crypto= require('crypto') 在这什么是“要求”? Crypto 是一个 nodejs 内置的签名模块,require 语句是它的导入方式。如果您在纯 Javascript 中执行此操作,则可以考虑使用 crypto-js 之类的东西。虽然,我认为最好在服务器端进行签名以保护您的密钥对的隐私。 如果有人想要一个 bash shell 脚本,我写了一个在 bash 中签名 S3 url:github.com/gdbtek/aws-tools 这可以使用自定义 S3 POST 策略作为 Policy 查询参数吗?需要通过 POST 请求上传到需要预签名 URL 的 CloudFront 分配。【参考方案2】:

为了让我的代码与 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 

【讨论】:

从 v2.1.0 的 aws-cloudfront-sign 开始解决此问题【参考方案3】:

AWS 包含一些内置类和结构,以帮助为 CloudFront 创建签名 URL 和 Cookie。我利用这些以及 Jason Sims 的出色回答,让它以稍微不同的模式工作(这似乎与他创建的 NPM 包非常相似)。

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

    ....

以及带有策略 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

示例实现:

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

【讨论】:

以上是关于通过 AWS 开发工具包创建签名的 S3 和 Cloudfront URL的主要内容,如果未能解决你的问题,请参考以下文章

通过使用 AWS-SDK PHP 生成的预签名帖子拒绝 AWS S3 上传访问

通过使用 AWS-SDK PHP 生成的预签名帖子拒绝 AWS S3 上传访问

AWS S3 存储桶预签名 URL

AWS S3 通过预签名 URL 上传返回 400 错误请求

AWS 静态网站 + 云端签名 cookie

Sketch JS,如何在音频标签上使用 AWS 预签名 URL?