DynamoDB API:400 Bad Request,没有响应数据

时间:2018-01-14 17:18:48

标签: javascript amazon-web-services amazon-dynamodb

上周我在Javascript(不是SDK)上实现了Signature Version 4,因为我不喜欢外部依赖项。在多次调试期间,我遇到了我的签名不正确的错误。现在我看到相同的400 Bad Request但没有响应数据 - 所以我不知道如何处理它。

我几次检查了我的代码和文档。

下面有关签名的代码(也许对某人也很有用,作为实现的例子)。 storageCache['Credentials']是从AWSCognitoIdentityService.GetCredentialsForIdentity返回的数据。

async function _updatePlayedStatus(id, val) {

    const dateObj = new Date();

    const body = JSON.stringify({
        'TableName': 'history',
        'Key': {'email': nodes.email.innerText, 'utc': id},
        'UpdateExpression': 'SET isNew = :isNew, updated = :updated',
        'ExpressionAttributeValues': {
            ':isNew': val.toString(),
            ':updated': dateObj.getTime() / 1000 | 0}
    });

    const headers = {
        'Authorization': await _buildAuthorizationHeaderForDynamoDB(
            storageCache['Credentials']['SessionToken'],
            storageCache['Credentials']['SecretKey'],
            storageCache['Credentials']['AccessKeyId'],
            body,
            dateObj),
        'content-type': 'application/x-amz-json-1.0',
        'x-amz-date': _getDatetimeIn8601Format(dateObj),
        'x-amz-target': 'DynamoDB_20120810.UpdateItem',
        'x-amz-security-token': storageCache['Credentials']['SessionToken']
        /* Without date will be 400 Bad Request.
         * I tried shorter Date instead of x-amz-date but 400 Bad Request
         * com.amazon.coral.service#IncompleteSignatureException

         * 'Authorization header requires existence of either a 'X-Amz-Date' or a 'Date' header.'
         * When 'Date' - I see it in console.debug() but not in request headers.
         */
    };
    console.debug('headers:', headers);
    fetch('https://dynamodb.us-east-1.amazonaws.com', {
        'method': 'POST',
        'headers': headers,
        'body': body
    });
}


/**
 * https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
 */
async function _buildAuthorizationHeaderForDynamoDB(SessionToken, SecretKey, AccessKeyId, body, dateObj) {
    console.debug('_buildAuthorizationHeaderForDynamoDB args:', arguments);

    /** Task 1: Create a Canonical Request for Signature Version 4
     *  https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html */
    const canonicalRequest = `POST
/

content-type:application/x-amz-json-1.0
host:dynamodb.us-east-1.amazonaws.com
x-amz-date:${_getDatetimeIn8601Format(dateObj)}
x-amz-security-token:${SessionToken}
x-amz-target:DynamoDB_20120810.UpdateItem
content-type;host;x-amz-date;x-amz-security-token;x-amz-target
${await _sha256(body)}`;
    const dateStr = dateObj.toISOString().slice(0, 10).replace(/-/g, '');
    // YYYYMMDD, for example 20150830

    /** Task 2: Create a String to Sign for Signature Version 4
     *  https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html */
    const stringToSign = `AWS4-HMAC-SHA256
${_getDatetimeIn8601Format(dateObj)}
${dateStr}/us-east-1/dynamodb/aws4_request
${await _sha256(canonicalRequest)}`;

    /** Task 3: Calculate the Signature for AWS Signature Version 4
    *   https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
    *   https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/sign */

    const credential = `${AccessKeyId}/${dateStr}/us-east-1/dynamodb/aws4_request`;

    const kDate = await crypto.subtle.sign(
        'HMAC',
        await _importKey(gTextEncoder.encode(`AWS4${SecretKey}`)),
        gTextEncoder.encode(dateStr)
    );

    const kRegion = await crypto.subtle.sign(
        'HMAC',
        await _importKey(kDate),
        gTextEncoder.encode('us-east-1')
    );

    const kService = await crypto.subtle.sign(
        'HMAC',
        await _importKey(kRegion),
        gTextEncoder.encode('dynamodb')
    );

    const kSigning = await crypto.subtle.sign(
        'HMAC',
        await _importKey(kService),
        gTextEncoder.encode('aws4_request')
    );

    const signatureArrayBuffer = await crypto.subtle.sign(
        'HMAC',
        await _importKey(kSigning),
        gTextEncoder.encode(stringToSign)
    );

    const signatureHex = _binaryToHex(Array.from(new Uint8Array(signatureArrayBuffer)));
    // Array.from() because ArrayBuffer does not have the map() method.

    return `AWS4-HMAC-SHA256 Credential=${credential}, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=${signatureHex}`;
}


/**
 * https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
 */
async function _importKey(keyDataArrayBuffer) {
    return crypto.subtle.importKey(
        'raw',
        keyDataArrayBuffer,
        {'name': 'HMAC', 'hash': {'name': 'SHA-256'}},
        false,
        ['sign']);
}


/**
 * Function from
 * https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
 */
async function _sha256(message) {

    // encode as UTF-8
    const msgBuffer = gTextEncoder.encode(message);

    // hash the message
    const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);

    // convert ArrayBuffer to Array
    const hashArray = Array.from(new Uint8Array(hashBuffer));

    return _binaryToHex(hashArray);
}


function _binaryToHex(array) {
    return array.map(b => ('00' + b.toString(16)).slice(-2)).join('');
}


/** Date format is specified with ISO8601 basic format
 * YYYYMMDD'T'HHMMSS'Z'
 * for example
 * 20180112T101849
 */
function _getDatetimeIn8601Format(dateObj) {
    return dateObj.toISOString().replace(/[:-]/g, '').slice(0, 15) + 'Z';
}

1 个答案:

答案 0 :(得分:0)

Content-Length的回复非零而Response标签显示为This request has no response data available时,看起来就是Google Chrome的缺陷。

console.log(response)向我展示了相同的The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.。也许有人会注意到我的代码存在一些问题..

更新:啊哈,我的问题出现在task one - 规范标题后必须是两个\n。在文档中,这仅在示例中显示