AWS S3 V4签名实现(nodejs)

虽然亚马逊提供了sdk,使用SDK会自动帮你计算签名。如果你能使用SDK 请一定使用SDK。
但是目标平台为嵌入式平台(非Linux)只支持C接口,很多库移植成本过高,为了使用S3服务,我们应该首选restfulAPI 这种方案。这就涉及到了自己实现AWS V4签名的问题。

前排资料:
我们可以阅读AWS SDK(nodejs) 源码签名实现进行参考验证:
aws-nodejs-sample\node_modules\aws-sdk\lib\signers 及aws-nodejs-sample\node_modules\aws-sdk\libutil.js
AWSV4签名文档:
https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/sig-v4-authenticating-requests.html

1 AWS v4签名

与Amazon S3的每次交互都是经过身份验证。本节介绍使用AWS Signature Version 4算法的请求认证。S3目前只支持V4验证。

1.1认证方法:

HTTP授权标头 - 使用HTTP Authorization标头是验证Amazon S3请求的最常用方法。所有Amazon S3 REST操作(使用POST请求的基于浏览器的上传除外)都需要此标头。本文使用的是这种方式。且为单个区块传输方式。

1.2 签名环境

在单个区块中传输有效负载(AWS签名版本4)
当在单个块中传输有效载荷时,可以选择将有效载荷散列包含在签名计算中,称为带符号有效载荷(如果不包含它,有效载荷将被视为无符号)。即是否计算待上传文件hash值。
我们选择的是第二种:无符号有效负载选项 - UNSIGNED-PAYLOAD在构建规范请求时包含文字字符串,并x-amz-content-sha256在向S3发送请求时将相同的值设置为 标题值。不计算负载hash值。即x-amz-content-sha256 设置为UNSIGNED-PAYLOAD。

签名环境总结:
1 用于s3
2 单个区块上传文件
3 不计算有效负载hash

1.3签名理论

要计算签名,您首先需要一个字符串来签名。然后HMAC-SHA256使用签名密钥计算要签名的字符串的散列。下图说明了该过程,包括您为签名创建的字符串的各个组件
当Amazon S3收到验证请求时,它会计算签名,然后将其与您在请求中提供的签名进行比较。因此,您必须使用Amazon S3使用的相同方法来计算签名。以协议形式签署请求的过程称为规范化。
即你的操作必须完全符合标准,因为AWS 也会按照标准计算一次,相同就成功。
签名流程图:
这里写图片描述
所需要的基础方法:
这里写图片描述

2 实际操作

我们可以从图中看出Authorization的得到有3部分组成,且是顺序获取 前一个是后一个的前置条件。
1 Canonical Request
2 StringToSign
3 Signature
下面将分别介绍
本文验证用了些全局变量:

var accessKeyId = ' ';
var secretAccessKey= ' ';
var region='us-east-1';
var serviceName='s3';
var algorithm = 'AWS4-HMAC-SHA256';
var v4Identifier = 'aws4_request';

2.1 Canonitcal Requet

Canonitcal Requet 有下面六个部分组成

<HTTPMethod>\n
<CanonicalURI>\n
<CanonicalQueryString>\n
<CanonicalHeaders>\n
<SignedHeaders>\n
<HashedPayload>

2.1.1HTTPMethod

HTTPMethod 是HTTP方法之一,例如GET,PUT,HEAD和DELETE。

2.1.2 CanonicalURI

CanonicalURI是URI的绝对路径组件的URI编码版本 - 任何以跟在域名后面的“/”开头,直到字符串末尾或问号字符。
http://s3.amazonaws.com/examplebucket/myphoto.jpg ,CanonicalURI即为/examplebucket/myphoto.jpg 。

2.1.3 CanonicalQueryString

CanonicalQueryString指定URI编码的查询字符串参数。您可以单独对URI进行编码名称和值。即url的query参数。比较复杂,详情可参见文档。
因为我们是S3操作不带query参数,按文档说明:
如果URI不包含’?’,则请求中没有查询字符串,并且将规范查询字符串设置为空字符串(“”)。您仍然需要包含“\ n”。

2.1.4 CanonicalHeaders

CanonicalHeaders是请求标题及其值的列表。单独的标题名称和值对由换行符(“\ n”)分隔。标题名称必须小写。您必须按字母顺序排列标题名称以构建字符串

Lowercase(<HeaderName1>)+":"+Trim(<value>)+"\n"
Lowercase(<HeaderName2>)+":"+Trim(<value>)+"\n"
...
Lowercase(<HeaderNameN>)+":"+Trim(<value>)+"\n"

sample:

'content-md5:ZwmeZ+R9fiKB4KNgjnCUbw==\nhost:xxxxxx.s3.amazonaws.com\nx-amz-content-sha256:UNSIGNED-PAYLOAD\nx-amz-date:20180313T054459Z\n'

实现code:

function canonicalHeaderValues(values) {
    return values.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '');
}
function canonicalHeaders(request) {
    var headers = [];
    for(var i in request.header)
    {
        headers.push([i, request.header[i]]);
    }
    headers.sort(function (a, b) {
      return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1;
    });

    var parts = [];
    for(var i in headers)
    {
        var key = headers[i][0].toLowerCase();
        var value =  headers[i][1];
        parts.push(key + ':' + canonicalHeaderValues(value.toString()));
    }
    return parts.join('\n');
  }

2.1.6 HashedPayload

HashedPayload 是请求有效负载的SHA256哈希值的十六进制值。我们选择不计算有效负载hash值 直接给 UNSIGNED-PAYLOAD

2.1.7 代码实现

function canonicalString(request) {
    var parts = [];
    //pathname = uriEscapePath(request.pathname);

    parts.push(request.method);
    parts.push(request.pathname);
    parts.push("");
    parts.push(canonicalHeaders(request) + '\n');
    parts.push(signedHeaders(request));
    parts.push('UNSIGNED-PAYLOAD');
    console.info(parts);
    return parts.join('\n');
  }

Request 参数

var header ={
    'host' : 'xxxxxx.s3.amazonaws.com',
    'x-amz-content-sha256':'UNSIGNED-PAYLOAD',
    'x-amz-date':date,
    'content-md5':md5
}
var req ={
    header : header,
    pathname : '/xxxx/test02.jpg',
    method : 'PUT',
    bodyhash: 'UNSIGNED-PAYLOAD'
}

date参数:

var date = (new Date()).toISOString().replace(/\-|:|\.\d\d\d/g,""); (后面所有的date都是这个)
20180313T054459Z   (注意时区)
Md5参数:
const hash = crypto.createHash('md5');
hash.update(bitmap);
var md5 = hash.digest('base64');
console.info(md5);

打印结果( console.info(parts)

[ 'PUT',
  '/xxxx/test02.jpg',
  '',
  'content-md5:ZwmeZ+R9fiKB4KNgjnCUbw==\nhost:xxxxxx.s3.amazonaws.com\nx-amz-content-sha256:UNSIGNED-PAYLOAD\nx-amz-date:20180313T054459Z\n',
  'content-md5;host;x-amz-content-sha256;x-amz-date',

  'UNSIGNED-PAYLOAD' ]

2.2 StringToSign

本节概述创建要签名的字符串。对于一步一步的说明,请参阅任务2:创建一个字符串来登录在AWS一般参考。
要签名的字符串是以下字符串的串联:

"AWS4-HMAC-SHA256" + "\n" +
timeStampISO8601Format + "\n" +
<Scope> + "\n" +
Hex(SHA256Hash(<CanonicalRequest>))

2.2.1 AWS4-HMAC-SHA256

常量字符串AWS4-HMAC-SHA256指定您正在使用的哈希算法HMAC-SHA256

2.2.2 timeStampISO8601Format

这timeStamp是ISO 8601格式的当前UTC时间(例如, 20130524T000000Z)
前面我们已经获取到了

2.2.3 Scope

Scope将生成的签名绑定到特定日期,AWS区域和服务
sample:

20180313/us-east-1/s3/aws4_request

code:

  function createScope(date, region, serviceName) {
    return [
      date.substr(0, 8),
      region,
      serviceName,
      'aws4_request'
    ].join('/');
  }
  function credentialString(datetime) {
    return createScope(
      datetime.substr(0, 8),
      region,
      serviceName
    );
  }

2.2.4 Hex(SHA256Hash())

将2.1中得到的CanonicalRequest 进行sha256 在转成16进制
code:

sha256(string, 'hex'); //基础方法已经提供

2.2.5 代码实现

Code:

function stringToSign(datetime, request) {
    var parts = [];
    parts.push('AWS4-HMAC-SHA256');
    parts.push(datetime);
    parts.push(credentialString(datetime));
    console.info(canonicalString(request));
    parts.push(hexEncodedHash(canonicalString(request)));

    console.info(parts);
    return parts.join('\n');
  }

打印结果:

[ 'AWS4-HMAC-SHA256',
  '20180313T054459Z',
  '20180313/us-east-1/s3/aws4_request',
  '1ae0b089a24ef398ef1204efe29fee5b7c762e43f35165b0c4bc74xxxxxxxxx' ]

2.3 Signature

我们可以在图中看出获得Signature 需要signingKey和StringToSign。
HMAC-SHA256(SigningKey, StringToSign)

2.3.1 signingKey

在AWS签名版本4中,不是使用您的AWS访问密钥签署请求,而是首先创建一个签名密钥,该密钥的范围限定为特定的区域和服务。
签名密匙得到:

DateKey              = HMAC-SHA256("AWS4"+"<SecretAccessKey>", "<YYYYMMDD>")
DateRegionKey        = HMAC-SHA256(<DateKey>, "<aws-region>")
DateRegionServiceKey = HMAC-SHA256(<DateRegionKey>, "<aws-service>")
SigningKey           = HMAC-SHA256(<DateRegionServiceKey>, "aws4_request")

code:

function getSigningKey(credentials,date,region,service,shouldCache)
{
    var kDate =hmac(
      'AWS4' + secretAccessKey,
      date,
      'buffer'
    );
    var kRegion = hmac(kDate, region, 'buffer');
    var kService = hmac(kRegion, service, 'buffer');
    var signingKey = hmac(kService, v4Identifier, 'buffer');
    return signingKey;
}

2.3.2 Signature

HMAC-SHA256(SigningKey, StringToSign)

code:

function signature(datetime, request) {
    var signingKey = getSigningKey(
      0,
      datetime.substr(0, 8),
      region,
      serviceName
    );
    return hmac(signingKey, stringToSign(datetime, request), 'hex');
}

打印结果:

6553bf9cab10de458afb093543b8154903307ec96a7e2d84696c38941aaad1aa

2.4 获取Authorization

Authorization由下列值组成:

Authorization: AWS4-HMAC-SHA256 
Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request, 
SignedHeaders=host;range;x-amz-date,                                                                       ( Canonitcal Requet 已获取)
Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024 (已得到)

这里写图片描述

Code:

function authorization(request, datetime) {
    var parts = [];
    var credString = credentialString(datetime);
    parts.push('AWS4-HMAC-SHA256'+ ' Credential=' +accessKeyId + '/' + credString);
    parts.push('SignedHeaders=' + signedHeaders(request));
    parts.push('Signature=' + signature(datetime, request));
    return parts.join(', ');
  }

打印结果:

AWS4-HMAC-SHA256 Credential=XXXXXXX/20180313/us-east-1/s3/aws4_request, SignedHeaders=content-md5;host;x-amz-content-sha256;x-amz-date, Signature=6553bf9cab10de458afb093543b8154903307ec96a7e2d84696c3xxxxxxxxx

2.5发起s3put请求

var options = {url: 'http://xxxxxxx.s3.us-east-1.amazonaws.com/xxxxx/test02.jpg',
    headers: { 
        'content-md5':md5 ,
        host: 'xxxxxxx.s3.amazonaws.com', 
        'x-amz-content-sha256':'UNSIGNED-PAYLOAD',
        'x-amz-date':date,
        Authorization: result
    },
    body:bitmap
};

request.put(options, function(error, response, body ) {
    console.log( 'response: ' + response );
    console.log( 'body: ' + body );
});
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
在Spring Boot项目中集成AWS SDK,可以实现AWS S3的连接并实现文件的上传和下载功能。 首先,需要在pom.xml文件中添加AWS SDK的依赖项,并确保version保持一致。在dependencyManagement中添加以下代码: ``` <dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>2.0.0-preview-12</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> ``` 然后,在dependencies中添加以下代码: ``` <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> <version>2.0.0-preview-12</version> </dependency> ``` 接下来,在application.properties文件中进行配置,包括AWS的访问密钥、S3存储桶名称、存储桶所在地区以及上传文件的路径: ``` aws.access_key_id=your access key aws.secret_access_key=your secret access key aws.s3.bucket=your bucket name aws.s3.region=cn-northwest-1 aws.s3.filename=/666666/test123/photo.zip ``` 在文件上传和下载的方法中,需要注意传递给方法的key参数是指存储在AWS S3上的文件名。如果key参数是路径字符串格式,S3服务器会自动创建对应路径的文件夹。例如,如果在application.properties中设置了aws.s3.filename=/666666/test123/photo.zip,那么key参数为/666666/test123/photo.zip,S3存储桶下就会有一个名为/666666/test123/photo.zip的文件,即上传的文件。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Spring Boot项目集成AWS SDK连接到AWS S3,实现上传下载功能](https://blog.csdn.net/sinat_27428951/article/details/89675587)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值