0

我正在尝试使用从 AssumeRole 返回的凭据从 Apex 访问 s3。但是,我收到以下错误:

<Message>The AWS Access Key Id you provided does not exist in our records.</Message>
<AWSAccessKeyId>ASIA********</AWSAccessKeyId>

我能够使用从 AssumeRole 返回的凭据从 CLI 成功调用此 s3 存储桶上的 GetObject,因此我可以合理地确定我的存储桶权限已设置好。我在 Apex 中有以下代码:

        Http http = new http();
        Profile p = [SELECT Id FROM Profile WHERE Profile.Name = 'S3 Test User' LIMIT 1];
        S3_Settings__c s3 = S3_Settings__c.getInstance(p.Id);
        String exp = String.valueOf(Cache.Session.get('expiration'));
        String sessionToken = String.valueOf(Cache.Session.get('token'));

        if(exp == null || exp == '' || (DateTime) JSON.deserialize('"' + exp + '"', DateTime.class) < System.now()) {
            requestSessionToken();
        }

        sessionToken = String.valueOf(Cache.Session.get('token'));
        DateTime expires = (DateTime) JSON.deserialize('"' + String.valueOf(Cache.Session.get('expiration')) + '"', DateTime.class);
        String accessKeyId =  String.valueOf(Cache.Session.get('accessKeyId'));
        String accessSecret = String.valueOf(Cache.Session.get('secret'));

        String bucketname = s3.Recording_Bucket__c;
        String host = 's3.amazonaws.com';  
        String formattedDateString = Datetime.now().formatGMT('EEE, dd MMM yyyy HH:mm:ss z');
        String method = 'GET';
        String filePath = 'https://' + bucketname + '.' + host + '/' + filename; 
        HttpRequest req = new HttpRequest();
        req.setMethod(method);
        req.setEndpoint(filePath);
        req.setHeader('Host', bucketname + '.' + host);
        req.setHeader('Connection', 'keep-alive');
        String stringToSign = 'GET\n\n' + 'x-amz-security-token=' + sessionToken + '&expiration=' + expires + '\n' + formattedDateString + '\n/' + '/' + bucketname + '/' + filename;
        System.debug('SIGN ' + stringToSign);
        String encodedStringToSign = EncodingUtil.urlEncode(stringToSign, 'UTF-8');
        Blob mac = Crypto.generateMac('HMACSHA1', blob.valueof(stringToSign),blob.valueof(accessSecret));
        String signedKey  = EncodingUtil.base64Encode(mac);

        String authHeader = 'AWS' + ' ' + accessKeyId + ':' + signedKey;
        req.setHeader('Date', formattedDateString);
        //req.setHeader('x-amz-security-token', sessionToken); //AWS returns 'invalid signature' if this is set

        req.setHeader('Authorization',authHeader);

        HttpResponse resp = http.send(req);

似乎 AWS 正在读取 AccessKeyId/Secret,而不是会话令牌。我也尝试将 x-amz-security-token 设置为标头,但这会引发 403 错误——签名不匹配。我是否在我的标头或签名中遗漏了可以使该请求成功返回的内容?

4

1 回答 1

0

原来我将 x-amz-security-token 标头放置在错误的位置。它需要在格式化日期之后立即出现在规范的 AMZ 标题部分中,并用逗号分隔名称和值:

String stringToSign = 'GET\n\n\n' + formattedDateString + '\n' + 'x-amz-security-token:' + sessionToken + '\n' + '/' + bucketname + '/' + filename;

此外,需要取消注释以下行:

req.setHeader('x-amz-security-token', sessionToken);

最后一点,请确保标头和规范化的 AMZ 标头都没有大写。

于 2019-11-16T00:31:42.660 回答