是否有人成功使用AWS开发工具包为S3存储桶中的对象生成签名URL,这些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 Secret Key和AWS Access Key进行签名。有没有人得到这样的设置呢?
更新 经过进一步研究后,CloudFront似乎处理的URL签名与S3 [link]完全不同。但是,我仍然不清楚如何使用Javascript创建签名的CloudFront URL。
答案 0 :(得分:23)
更新:我将签名功能从下面的示例代码移到了NPM上的aws-cloudfront-sign包中。这样您就可以只需要此包并致电getSignedUrl()
。
经过一些进一步的调查后,我找到了一个解决方案,它是this answer与我在Boto library中找到的方法之间的一种组合。确实,S3 URL签名的处理方式与CloudFront URL签名不同。如果您只需要签署一个S3链接,那么我的初始问题中的示例代码将适用于您。但是,如果要生成使用CloudFront分配的签名URL,则会更复杂一些。这是因为AWS SDK当前不支持CloudFront URL签名,因此您必须自己创建签名。如果您还需要这样做,这里是基本步骤。我假设您已经安装了S3存储桶:
要想签名的CloudFront网址,您只需使用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
答案 1 :(得分: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
}
答案 2 :(得分:0)
AWS包含一些内置的类和结构,以帮助为CloudFront创建签名的URL和Cookies。我将它们与Jason Sims的出色答案结合使用,以使其工作模式略有不同(这与他创建的NPM软件包非常相似)。
即,AWS.CloudFront.Signer类型描述抽象了创建签名URL和Cookies的过程。
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);
});
});
}