Amazon CloudFront
开发人员指南 (API 版本 2016-09-29)
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 AWS 服务入门

使用 Perl 创建 URL 签名

本部分包括一个用于 Linux/Mac 平台的 Perl 脚本,您可以使用它为私有内容创建签名。要创建签名,请使用指定 CloudFront URL、签署人私有密钥的路径、密钥 ID 以及 URL 的到期日期的命令行参数来运行该脚本。工具还可对签名 URL 进行解码。

注意

创建 URL 签名只是使用签名 URL 提供私有内容过程的一部分。有关端到端流程的更多信息,请参阅使用签名 URL

使用 Perl 脚本创建签名 URL 的示例

下面的示例显示如何使用本主题中提供的 Perl 脚本创建 RTMP 的分配签名。要开始,请将该脚本另存为一个名为 cfsign.pl 的文件。然后,使用以下命令行参数运行该脚本:

$ cfsign.pl --action encode --stream example/video.mp4 --private-key /path/to/my-private-key.pem --key-pair-id PK12345EXAMPLE --expires 1265838202

该脚本从命令行参数产生策略声明。其产生的签名是 SHA1 哈希的策略声明。

以下是 Base64 编码的示例流名称:

mp4:example/video.mp4%3FPolicy%3DewogICJTdGF0ZW1lbnQiOlt7CiAgICAgICJSZXNvdXJjZSI 6ImRyciIsCiAgICAgICJDb25kaXRpb24iOnsKICAgICAgICAiSXBBZGRyZXNzIjp7IkFXUzpTb3VyY2V JcCI6IjAuMC4wLjAvMCJ9LAogICAgICAgICJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MjE 0NTkxNjgwMH0KICAgICAgfQogICAgEXAMPLE_%26Signature%3DewtHqEXK~68tsZt-eOFnZKGwTf2a JlbKhXkK5SSiVqcG9pieCRV3xTEPtc29OzeXlsDvRycOM2WK0cXzcyYZhpl9tv2796ihHiCTAwIHQ8yP 17Af4nWtOLIZHoH6wkR3tU1cQHs8R1d-g-SlZGjNBXr~J2MbaJzm8i6EXAMPLE_%26Key-Pair-Id%3 DPK12345EXAMPLE

此签名验证请求的身份,以便流式传输私有内容 example/video.mp4

如果您使用 Adobe Flash Player 并从使用 JavaScript 的网页传递流名称,则必须对签名进行 Base64 编码,并将 URL 请求参数中的无效字符 (+, =, /) 分别替换为有效字符 (-、 _ 和 ~)。

如果不从网页传入流名称,则不必对签名进行 Base64 编码。例如,如果您编写自己的播放器并从 Adobe Flash .swf 文件内获取流名称,将无需对签名进行 Base64 编码。

以下示例使用 jwplayer 和 CloudFront。

<script type='text/javascript'> var so1 = new SWFObject ('http://d84l721fxaaqy9.cloudfront.net/player/player.swf', 'mpl', '640', '360', '9'); so1.addParam('allowfullscreen','true'); so1.addParam('allowscriptaccess','always'); so1.addParam('wmode','opaque'); so1.addVariable('streamer','rtmp://s33r3xe4ayhhis.cloudfront.net/cfx/st'); so1.addVariable("file","mp4:example/video.mp4%3FPolicy%3DewogICJTdGF0ZW1lbnQi Olt7CiAgICAgICJSZXNvdXJjZSI6ImRyciIsCiAgICAgICJDb25kaXRpb24iOnsKICAgICAgICA iSXBBZGRyZXNzIjp7IkFXUzpTb3VyY2VJcCI6IjAuMC4wLjAvMCJ9LAogICAgICAgICJEYXRlTG Vzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MjE0NTkxNjgwMH0KICAgICAgfQogICAgEXAMPLE_% 26Signature%3DewtHqEXK~68tsZt-eOFnZKGwTf2aJlbKhXkK5SSiVqcG9pieCRV3xTEPtc29O zeXlsDvRycOM2WK0cXzcyYZhpl9tv2796ihHiCTAwIHQ8yP17Af4nWtOLIZHoH6wkR3tU1cQHs8 R1d-g-SlZGjNBXr~J2MbaJzm8i6EXAMPLE_%26Key-Pair-Id%3DPK12345EXAMPLE so1.write('flv'); </script>

在检索流以从 Adobe Flash .swf 文件中播放时,不要对流名称进行 URL 编码。例如:

mp4:example/video.mp4?Policy=ewogICJTdGF0ZW1lbnQiOlt7CiAgICAgICJSZXNvdXJjZSI6ImR yciIsCiAgICAgICJDb25kaXRpb24iOnsKICAgICAgICAiSXBBZGRyZXNzIjp7IkFXUzpTb3VyY2VJcCI 6IjAuMC4wLjAvMCJ9LAogICAgICAgICJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MjE0NTk xNjgwMH0KICAgICAgfQogICAgEXAMPLE_&Signature=ewtHqEXK~68tsZt-eOFnZKGwTf2aJlbKhXkK 5SSiVqcG9pieCRV3xTEPtc29OzeXlsDvRycOM2WK0cXzcyYZhpl9tv2796ihHiCTAwIHQ8yP17Af4nWt OLIZHoH6wkR3tU1cQHs8R1d-g-SlZGjNBXr~J2MbaJzm8i6EXAMPLE_&Key-Pair-Id=PK12345 EXAMPLE

有关该工具的命令行开关和功能的更多信息,请参阅 Perl 源代码中的注释(在下一部分中包含)。

另请参阅

用于创建签名 URL 的 Perl 脚本的源

以下 Perl 源代码可用于为 CloudFront 创建签名 URL。代码中的注释包括有关该工具的命令行开关和功能的信息。

#!/usr/bin/perl -w # Copyright 2008 Amazon Technologies, Inc. Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. You may obtain a copy of the License at: # # http://aws.amazon.com/apache2.0 # # This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and limitations under the License. =head1 cfsign.pl cfsign.pl - A tool to generate and verify AWS CloudFront signed URLs =head1 SYNOPSIS This script uses an existing RSA key pair to sign and verify AWS CloudFront signed URLs View the script source for details as to which CPAN packages are required beforehand. For help, try: cfsign.pl --help URL signing examples: cfsign.pl --action encode --url http://images.my-website.com/gallery1.zip --policy sample_policy.json --private-key privkey.pem --key-pair-id mykey cfsign.pl --action encode --url http://images.my-website.com/gallery1.zip --expires 1257439868 --private-key privkey.pem --key-pair-id mykey Stream signing example: cfsign.pl --action encode --stream videos/myvideo.mp4 --expires 1257439868 --private-key privkey.pem --key-pair-id mykey URL decode example: cfsign.pl --action decode --url "http//mydist.cloudfront.net/?Signature=AGO-PgxkYo99MkJFHvjfGXjG1QDEXeaDb4Qtzmy85wqyJjK7eKojQWa4BCRcow__&Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cDovLypicmFkbS5qcGciLCJDb25kaXRpb24iOnsiSXBBZGRyZXNzIjp7IkFXUzpTb3VyY2VJcCI6IjEwLjUyLjE3LjkvMCJ9LCJEYXRlR3JlYXRlclRoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTI1MjUyMDgzMH19fV19Cg__&Key-Pair-Id=mykey" To generate an RSA key pair, you can use openssl and the following commands: # Generate a 1024bit key pair openssl genrsa -out private-key.pem 1024 openssl rsa -in private-key.pem -pubout -out public-key.pem =head1 OPTIONS =over 8 =item B<--help> Print a help message and exits. =item B<--action> [action] The action to execute. action can be one of: encode - Generate a signed URL (using a canned policy or a user policy) decode - Decode a signed URL =item B<--url> The URL to en/decode =item B<--stream> The stream to en/decode =item B<--private-key> The path to your private key. =item B<--key-pair-id> The AWS Portal assigned key pair identifier. =item B<--policy> The CloudFront policy document. =item B<--expires> The Unix epoch time when the URL is to expire. If both this option and the --policy option are specified, --policy will be used. Otherwise, this option alone will use a canned policy. =back =cut use strict; use warnings; # you might need to use CPAN to get these modules. # run perl -MCPAN -e "install <module>" to get them. # The openssl command line will also need to be in your $PATH. use File::Temp qw/tempfile/; use File::Slurp; use Getopt::Long; use IPC::Open2; use MIME::Base64 qw(encode_base64 decode_base64); use Pod::Usage; use URI; my $CANNED_POLICY = '{"Statement":[{"Resource":"<RESOURCE>","Condition":{"DateLessThan":{"AWS:EpochTime":<EXPIRES>}}}]}'; my $POLICY_PARAM = "Policy"; my $EXPIRES_PARAM = "Expires"; my $SIGNATURE_PARAM = "Signature"; my $KEY_PAIR_ID_PARAM = "Key-Pair-Id"; my $verbose = 0; my $policy_filename = ""; my $expires_epoch = 0; my $action = ""; my $help = 0; my $key_pair_id = ""; my $url = ""; my $stream = ""; my $private_key_filename = ""; my $result = GetOptions("action=s" => \$action, "policy=s" => \$policy_filename, "expires=i" => \$expires_epoch, "private-key=s" => \$private_key_filename, "key-pair-id=s" => \$key_pair_id, "verbose" => \$verbose, "help" => \$help, "url=s" => \$url, "stream=s" => \$stream, ); if ($help or !$result) { pod2usage(1); exit; } if ($url eq "" and $stream eq "") { print STDERR "Must include a stream or a URL to encode or decode with the --stream or --url option\n"; exit; } if ($url ne "" and $stream ne "") { print STDERR "Only one of --url and --stream may be specified\n"; exit; } if ($url ne "" and !is_url_valid($url)) { exit; } if ($stream ne "") { exit unless is_stream_valid($stream); # The signing mechanism is identical, so from here on just pretend we're # dealing with a URL $url = $stream; } if ($action eq "encode") { # The encode action will generate a private content URL given a base URL, # a policy file (or an expires timestamp) and a key pair id parameter my $private_key; my $public_key; my $public_key_file; my $policy; if ($policy_filename eq "") { if ($expires_epoch == 0) { print STDERR "Must include policy filename with --policy argument or an expires" . "time using --expires\n"; } $policy = $CANNED_POLICY; $policy =~ s/<EXPIRES>/$expires_epoch/g; $policy =~ s/<RESOURCE>/$url/g; } else { if (! -e $policy_filename) { print STDERR "Policy file $policy_filename does not exist\n"; exit; } $expires_epoch = 0; # ignore if set $policy = read_file($policy_filename); } if ($private_key_filename eq "") { print STDERR "You must specific the path to your private key file with --private-key\n"; exit; } if (! -e $private_key_filename) { print STDERR "Private key file $private_key_filename does not exist\n"; exit; } if ($key_pair_id eq "") { print STDERR "You must specify an AWS portal key pair id with --key-pair-id\n"; exit; } my $encoded_policy = url_safe_base64_encode($policy); my $signature = rsa_sha1_sign($policy, $private_key_filename); my $encoded_signature = url_safe_base64_encode($signature); my $generated_url = create_url($url, $encoded_policy, $encoded_signature, $key_pair_id, $expires_epoch); if ($stream ne "") { print "Encoded stream (for use within a swf):\n" . $generated_url . "\n"; print "Encoded and escaped stream (for use on a webpage):\n" . escape_url_for_webpage($generated_url) . "\n"; } else { print "Encoded URL:\n" . $generated_url . "\n"; } } elsif ($action eq "decode") { my $decoded = decode_url($url); if (!$decoded) { print STDERR "Improperly formed URL\n"; exit; } print_decoded_url($decoded); } else { # No action specified, print help. But only if this is run as a program (caller will be empty) pod2usage(1) unless caller(); } # Decode a private content URL into its component parts sub decode_url { my $url = shift; if ($url =~ /(.*)\?(.*)/) { my $base_url = $1; my $params = $2; my @unparsed_params = split(/&/, $params); my %params = (); foreach my $param (@unparsed_params) { my ($key, $val) = split(/=/, $param); $params{$key} = $val; } my $encoded_signature = ""; if (exists $params{$SIGNATURE_PARAM}) { $encoded_signature = $params{"Signature"}; } else { print STDERR "Missing Signature URL parameter\n"; return 0; } my $encoded_policy = ""; if (exists $params{$POLICY_PARAM}) { $encoded_policy = $params{$POLICY_PARAM}; } else { if (!exists $params{$EXPIRES_PARAM}) { print STDERR "Either the Policy or Expires URL parameter needs to be specified\n"; return 0; } my $expires = $params{$EXPIRES_PARAM}; my $policy = $CANNED_POLICY; $policy =~ s/<EXPIRES>/$expires/g; my $url_without_cf_params = $url; $url_without_cf_params =~ s/$SIGNATURE_PARAM=[^&]*&?//g; $url_without_cf_params =~ s/$POLICY_PARAM=[^&]*&?//g; $url_without_cf_params =~ s/$EXPIRES_PARAM=[^&]*&?//g; $url_without_cf_params =~ s/$KEY_PAIR_ID_PARAM=[^&]*&?//g; if ($url_without_cf_params =~ /(.*)\?$/) { $url_without_cf_params = $1; } $policy =~ s/<RESOURCE>/$url_without_cf_params/g; $encoded_policy = url_safe_base64_encode($policy); } my $key = ""; if (exists $params{$KEY_PAIR_ID_PARAM}) { $key = $params{$KEY_PAIR_ID_PARAM}; } else { print STDERR "Missing $KEY_PAIR_ID_PARAM parameter\n"; return 0; } my $policy = url_safe_base64_decode($encoded_policy); my %ret = (); $ret{"base_url"} = $base_url; $ret{"policy"} = $policy; $ret{"key"} = $key; return \%ret; } else { return 0; } } # Print a decoded URL out sub print_decoded_url { my $decoded = shift; print "Base URL: \n" . $decoded->{"base_url"} . "\n"; print "Policy: \n" . $decoded->{"policy"} . "\n"; print "Key: \n" . $decoded->{"key"} . "\n"; } # Encode a string with base 64 encoding and replace some invalid URL characters sub url_safe_base64_encode { my ($value) = @_; my $result = encode_base64($value); $result =~ tr|+=/|-_~|; return $result; } # Decode a string with base 64 encoding. URL-decode the string first # followed by reversing any special character ("+=/") translation. sub url_safe_base64_decode { my ($value) = @_; $value =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; $value =~ tr|-_~|+=/|; my $result = decode_base64($value); return $result; } # Create a private content URL sub create_url { my ($path, $policy, $signature, $key_pair_id, $expires) = @_; my $result; my $separator = $path =~ /\?/ ? '&' : '?'; if ($expires) { $result = "$path$separator$EXPIRES_PARAM=$expires&$SIGNATURE_PARAM=$signature&$KEY_PAIR_ID_PARAM=$key_pair_id"; } else { $result = "$path$separator$POLICY_PARAM=$policy&$SIGNATURE_PARAM=$signature&$KEY_PAIR_ID_PARAM=$key_pair_id"; } $result =~ s/\n//g; return $result; } # Sign a document with given private key file. # The first argument is the document to sign # The second argument is the name of the private key file sub rsa_sha1_sign { my ($to_sign, $pvkFile) = @_; print "openssl sha1 -sign $pvkFile $to_sign\n"; return write_to_program($pvkFile, $to_sign); } # Helper function to write data to a program sub write_to_program { my ($keyfile, $data) = @_; unlink "temp_policy.dat" if (-e "temp_policy.dat"); unlink "temp_sign.dat" if (-e "temp_sign.dat"); write_file("temp_policy.dat", $data); system("openssl dgst -sha1 -sign \"$keyfile\" -out temp_sign.dat temp_policy.dat"); my $output = read_file("temp_sign.dat"); return $output; } # Read a file into a string and return the string sub read_file { my ($file) = @_; open(INFILE, "<$file") or die("Failed to open $file: $!"); my $str = join('', <INFILE>); close INFILE; return $str; } sub is_url_valid { my ($url) = @_; # HTTP distributions start with http[s]:// and are the correct thing to sign if ($url =~ /^https?:\/\//) { return 1; } else { print STDERR "CloudFront requires absolute URLs for HTTP distributions\n"; return 0; } } sub is_stream_valid { my ($stream) = @_; if ($stream =~ /^rtmp:\/\// or $stream =~ /^\/?cfx\/st/) { print STDERR "Streaming distributions require that only the stream name is signed.\n"; print STDERR "The stream name is everything after, but not including, cfx/st/\n"; return 0; } else { return 1; } } # flash requires that the query parameters in the stream name are url # encoded when passed in through javascript, etc. This sub handles the minimal # required url encoding. sub escape_url_for_webpage { my ($url) = @_; $url =~ s/\?/%3F/g; $url =~ s/=/%3D/g; $url =~ s/&/%26/g; return $url; } 1;