跳到主要内容

AWS Signature 认证介绍

AWS Signature 是Amazon Web Services (AWS) 使用的一种认证机制,允许客户端向AWS服务发出安全的API请求。它主要用于验证请求的身份和确保请求在传输过程中未被篡改。AWS目前主要使用Signature Version 4 (SigV4)算法,这是一种基于HMAC-SHA256的签名过程。

AWS Signature 的工作原理

AWS Signature V4的签名过程相对复杂,涉及多个步骤:

  1. 创建规范请求:将HTTP请求的方法、URI、查询字符串、标准化的请求头和签名头列表组合成一个规范格式。
  2. 创建待签字符串:将算法、请求日期、凭证范围和规范请求的哈希值组合成待签字符串。
  3. 计算签名:使用AWS密钥通过多步HMAC-SHA256派生得到签名密钥,然后对待签字符串进行签名。
  4. 添加签名到请求:将签名添加到Authorization请求头中。

AWS Signature V4 的主要组成部分

  1. 访问密钥ID (Access Key ID):用于标识请求者的公共部分。
  2. 秘密访问密钥 (Secret Access Key):用于签名计算的私有部分,必须保密。
  3. 区域 (Region):AWS服务所在的地理区域。
  4. 服务名称 (Service Name):目标AWS服务的名称(如s3、ec2等)。
  5. X-Amz-Date 头:请求的时间戳,格式为ISO8601(如 20231231T235959Z)。
  6. Authorization 头:包含签名算法、凭证、签名头列表和计算得出的签名。

如何使用 AWS Signature 认证

实施AWS Signature V4认证

以下是使用AWS Signature V4进行认证的详细步骤:

  1. 准备凭证和请求信息

    首先,准备AWS访问密钥、秘密密钥、区域和服务名称。

  2. 创建规范请求

    规范请求由以下部分组成:

    • HTTP方法(GET、POST等)
    • 规范化的URI路径
    • 规范化的查询字符串
    • 规范化的头信息
    • 签名头列表
    • 请求体的SHA256哈希值

    例如:

    GET
    /path/to/resource
    param1=value1&param2=value2
    host:example.amazonaws.com
    x-amz-date:20231231T235959Z

    host;x-amz-date
    e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
  3. 创建待签字符串

    待签字符串由以下部分组成:

    • 签名算法(通常为AWS4-HMAC-SHA256)
    • 请求时间戳
    • 凭证范围(日期/区域/服务/aws4_request)
    • 规范请求的哈希值

    例如:

    AWS4-HMAC-SHA256
    20231231T235959Z
    20231231/us-east-1/s3/aws4_request
    hash_of_canonical_request
  4. 计算签名

    首先,通过多步派生创建签名密钥:

    kDate = HMAC-SHA256("AWS4" + secret_access_key, date)
    kRegion = HMAC-SHA256(kDate, region)
    kService = HMAC-SHA256(kRegion, service)
    kSigning = HMAC-SHA256(kService, "aws4_request")

    然后,使用派生的密钥对待签字符串进行签名:

    signature = HMAC-SHA256(kSigning, string_to_sign)
  5. 添加Authorization头

    最后,构建Authorization头并添加到请求中:

    Authorization: AWS4-HMAC-SHA256 Credential=access_key_id/credential_scope, SignedHeaders=signed_headers, Signature=signature

使用JavaScript实现AWS Signature V4认证

const crypto = require('crypto');
const axios = require('axios');

// AWS凭证和请求信息
const accessKeyId = 'YOUR_ACCESS_KEY_ID';
const secretAccessKey = 'YOUR_SECRET_ACCESS_KEY';
const region = 'us-east-1';
const service = 's3';
const host = 'example.s3.amazonaws.com';
const method = 'GET';
const path = '/my-bucket/my-object';
const queryParams = {};

// 计算AWS签名V4
async function signRequest(method, host, path, queryParams, headers, body) {
// 获取当前时间
const now = new Date();
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
const dateStamp = amzDate.slice(0, 8);

// 默认请求头
headers = headers || {};
headers['host'] = host;
headers['x-amz-date'] = amzDate;

// 创建请求体的哈希值
const payloadHash = crypto
.createHash('sha256')
.update(body || '', 'utf8')
.digest('hex');

// 规范化查询字符串
const canonicalQueryString = Object.keys(queryParams)
.sort()
.map(key => {
return `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`;
})
.join('&');

// 规范化头信息
const canonicalHeaders = Object.keys(headers)
.sort()
.map(key => {
return `${key.toLowerCase()}:${headers[key].trim()}`;
})
.join('\n') + '\n';

// 签名头列表
const signedHeaders = Object.keys(headers)
.sort()
.map(key => key.toLowerCase())
.join(';');

// 创建规范请求
const canonicalRequest = [
method,
path,
canonicalQueryString,
canonicalHeaders,
signedHeaders,
payloadHash
].join('\n');

// 创建凭证范围
const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;

// 创建待签字符串
const stringToSign = [
'AWS4-HMAC-SHA256',
amzDate,
credentialScope,
crypto.createHash('sha256').update(canonicalRequest, 'utf8').digest('hex')
].join('\n');

// 计算签名密钥
const kDate = hmacSha256('AWS4' + secretAccessKey, dateStamp);
const kRegion = hmacSha256(kDate, region);
const kService = hmacSha256(kRegion, service);
const kSigning = hmacSha256(kService, 'aws4_request');

// 计算签名
const signature = hmacSha256(kSigning, stringToSign).toString('hex');

// 创建Authorization头
const authorizationHeader = [
`AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}`,
`SignedHeaders=${signedHeaders}`,
`Signature=${signature}`
].join(', ');

// 将认证头添加到请求头中
headers['Authorization'] = authorizationHeader;

return headers;
}

// HMAC-SHA256辅助函数
function hmacSha256(key, string) {
return crypto.createHmac('sha256', key).update(string, 'utf8').digest();
}

// 发送带AWS签名的请求
async function sendAwsRequest() {
try {
const url = `https://${host}${path}`;
const headers = await signRequest(method, host, path, queryParams, {}, '');

const response = await axios({
method,
url,
headers
});

console.log('响应状态:', response.status);
console.log('响应数据:', response.data);
return response.data;
} catch (error) {
console.error('请求失败:', error.message);
if (error.response) {
console.error('错误详情:', error.response.data);
}
throw error;
}
}

// 使用示例
sendAwsRequest()
.then(data => console.log('请求成功'))
.catch(error => console.error('请求失败'));

使用AWS SDK(更简单的方法)

大多数情况下,建议使用官方的AWS SDK,它会自动处理签名过程:

const AWS = require('aws-sdk');

// 配置AWS凭证
AWS.config.update({
accessKeyId: 'YOUR_ACCESS_KEY_ID',
secretAccessKey: 'YOUR_SECRET_ACCESS_KEY',
region: 'us-east-1'
});

// 创建S3客户端
const s3 = new AWS.S3();

// 获取对象示例
s3.getObject({
Bucket: 'my-bucket',
Key: 'my-object'
}, (err, data) => {
if (err) {
console.error('错误:', err);
} else {
console.log('对象内容:', data.Body.toString());
}
});

AWS Signature 的优势

  • 身份验证:确保只有拥有有效凭证的用户才能访问AWS资源。
  • 数据完整性:保证请求在传输过程中未被篡改。
  • 防止重放攻击:通过包含日期和时间戳,限制请求的有效期。
  • 无需会话状态:每个请求都单独签名,不需要维护服务器端会话状态。
  • 细粒度的访问控制:可以与AWS IAM(Identity and Access Management)集成,实现细粒度的权限控制。

安全建议

在使用AWS Signature时,应注意以下安全措施:

  • 使用HTTPS:所有AWS API请求应通过HTTPS发送,防止凭证被窃取。
  • 安全存储密钥:秘密访问密钥应安全存储,避免泄露。如果怀疑密钥已泄露,应立即轮换。
  • 使用临时凭证:尽可能使用AWS Security Token Service (STS)生成的临时凭证,而不是长期凭证。
  • 遵循最小权限原则:为每个账户或应用程序配置最小必要的权限。
  • 定期轮换密钥:定期更换访问密钥,减少密钥泄露的风险。
  • 使用AWS SDK:尽可能使用官方的AWS SDK,它实现了最佳实践并自动处理签名逻辑。

常见问题

  1. AWS Signature V4与V2有什么区别?

    Signature V4提供了更强的安全性,使用SHA-256而不是SHA-1,支持更多的区域,并且签名过程可以涵盖更多的请求组件。V2已被大多数服务弃用。

  2. 如何处理请求时间戳偏差问题?

    AWS要求客户端与服务器时间相差不超过15分钟。确保客户端系统时钟准确,可以使用NTP服务保持同步。

  3. 是否可以预签名URL以允许临时访问?

    是的,AWS支持生成预签名URL,允许未经身份验证的用户在有限时间内访问特定资源,这对于共享文件或临时授权访问非常有用。

  4. 如何在AWS Lambda函数中使用签名?

    在Lambda环境中,可以使用环境变量中自动提供的临时凭证,或者通过AWS SDK获取临时凭证,无需手动实现签名过程。