如何在没有AWS JavaScript SDK的情况下将文件上传到S3?

问题描述 投票:0回答:3

适用于

JavaScript
的 AWS 开发工具包(即使仅包含
S3
组件)对于我的 Web 应用程序中的一些零星文件上传来说是一个巨大的块。考虑到我可以使用存储桶名称
accessKeyId
secretAccessKey
,是否有更精简的方法可以直接从客户端浏览器将文件上传到 S3 存储桶?

javascript amazon-s3 xmlhttprequest
3个回答
6
投票

要将文件从浏览器上传到 S3,您可以使用预签名的 PUT。这样您就不会向浏览器泄露 aws 秘密。您可以使用 minio-js 库生成预签名的 PUT url。

在服务器端,您可以像这样生成预签名的 PUT url:

var Minio = require('minio')

// find out your s3 end point here:
// http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region

var s3Client = new Minio({
    url: 'https://<your-s3-endpoint>',
    accessKey: 'YOUR-ACCESSKEYID',
    secretKey: 'YOUR-SECRETACCESSKEY'
})
var presignedUrl = s3Client.presignedPutObject('bucket', 'object', 24*60*60)
// presignedUrl expires in 1 day

您可以将此预签名 URL 传递给浏览器,该浏览器只需对 amazon s3 执行简单的 HTTP PUT。 PUT 请求将会成功,因为签名将成为 presignedUrl 的一部分。

您也可以使用预签名 POST 上传。预签名 POST 可以对上传提供更多控制 - 就像您可以限制上传对象的大小、内容类型等。


3
投票

S3 支持使用表单帖子上传方式从浏览器上传,无需在浏览器处添加特殊代码。 它涉及特定的表单设计和签名的策略文档,允许用户仅上传与您施加的限制相匹配的文件,并且不会暴露您的密钥。 上传后,它还可以选择将浏览器重定向回您的网站。

http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html


0
投票

Awtsmoos Power S3 管理器
目前专门用于与 Cloudfare R2 配合使用,但如果更改主机详细信息,则可以与任何 S3 配合使用

出口发送
记录账户详细信息

账户ID
访问令牌Id
秘密访问密钥
存储桶 -- 你的存储桶名称
钥匙
内容(目前是文本,但很快也会是 blob)

我能够从 SDK 中提取必要的功能并将其放入新的模块中。目前仅支持上传,就像所问的问题一样。理论上获取和删除也是可以实现的。唯一的依赖项是 CryptoJS,它会自动加载。

async function script(url) {
    f = await (await fetch(url)).text();
    try {
        eval(f);
    } catch (e) { console.log(e) }
}


var request = null;
var signatureVersion = "v4"

var algorithm = "AWS4-HMAC-SHA256"
        
var serviceName = "s3"

function weirdTime (t) { 
    return iso8601(t).replace(/[:\-]|\.\d{3}/g, "")
}

function sendIt({
    accessKeyId, secretAccessKey,
    bucket,
    accountId,
    key,
    content
}) {
    return new Promise(async ret => {
        if(!window.CryptoJS) {
            await script("https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js");

        }
        
        request = {
            region:"enam",
            pathname: () => 
                `/${bucket}/${key}`,
            method: "PUT",
            headers: {
                "x-amz-content-sha256": "UNSIGNED-PAYLOAD",
            
            //  "host": "a93a26b5b92d877ed1261c3a03782c27.r2.cloudflarestorage.com",
                "content-type": "application/octet-stream",
                "content-md5": generateMD5(content),
            //  "content-length": content.length,
                "x-amz-user-agent": "aws-sdk-js/2.1481.0 callback"
            },
            body: content
            
        }
        var ob = {
            request
        };
        
        addAuthorization({
            accessKeyId,
            secretAccessKey
        }, new Date(), ob);
        //console.log(ob);
        
        var x = new XMLHttpRequest()
        x.open("PUT", `https://${accountId}.r2.cloudflarestorage.com/${bucket}/${key}`, true)
        for(var h in ob.request.headers) {
            try {
                x.setRequestHeader(h,ob.request.headers[h])
            } catch(e){console.log(e)}
        }
        x.onreadystatechange = () => {

            if(x.status == 200) {
                ret(ob)
            }
        }
        x.send(ob.request.body);    
    })
}
function stringToSign(e, H) {

    var t = [];
    t.push("AWS4-HMAC-SHA256"),
        t.push(e),
        t.push(credentialString(e));
    var con = canonicalString(H)
    t.push(hexEncodedHash(con));
    
    return t.join("\n")
}

function addAuthorization(e, t, par) {
    var r = iso8601(t).replace(/[:\-]|\.\d{3}/g, "");
    par.request.headers["X-Amz-Date"]=r;
    
    par.request.headers.Authorization = authorization(e, r, par.request)
}

function authorization(e, t, R) {
    
    var r = []
        , a = credentialString(t);
    return r.push(algorithm + " Credential=" + e.accessKeyId + "/" + a),
        r.push("SignedHeaders=" + signedHeaders(R.headers)),
        r.push("Signature=" + signature(e, t, R)),
        r.join(", ")
}
function signature(e, t, R) {
    var r = getSigningKey(e, t.substr(0, 8), request.region, serviceName, !0);

    var otherThing = stringToSign(t, Object.assign({},R.headers));

    return generateHMAC(r, otherThing , "hex")
}


function uriEscapePath(e) {
    var t = [];
    return s.arrayEach(e.split("/"), function(e) {
        t.push(s.uriEscape(e))
    }),
        t.join("/")
}

        /**

PUT
/awtsmoos-audio/ok/wow8738.txt

content-md5:3F2SHjus2f4LjMPjFLZSzA==
host:.r2.cloudflarestorage.com
x-amz-content-sha256:UNSIGNED-PAYLOAD
x-amz-date:20241119T063943Z
x-amz-user-agent:aws-sdk-js/2.1481.0 callback

content-md5;host;x-amz-content-sha256;x-amz-date;x-amz-user-agent
UNSIGNED-PAYLOAD
        **/
function canonicalString(headers) {
    var e = []
        , t = request.pathname();
    
    return "s3" !== serviceName && "s3v4" !== this.signatureVersion && (t = uriEscapePath(t)),
        e.push(request.method),
        e.push(t),
        e.push(""),
        e.push(canonicalHeaders(headers || request.headers) + "\n"),
        e.push(signedHeaders(headers || request.headers)),
        e.push("UNSIGNED-PAYLOAD"),
        e.join("\n")
}

function iso8601(e) {
    return void 0 === e && (e = s.date.getDate()),
        e.toISOString().replace(/\.\d{3}Z$/, "Z")
}

function getSkewCorrectedDate() {
    return new Date(Date.now())
}

var unsignableHeaders = ["authorization", "content-type", "content-length", "user-agent", "presigned-expires", "expect", "x-amzn-trace-id"]

    
function isSignableHeader(e) {
    return 0 === e.toLowerCase().indexOf("x-amz-") || this.unsignableHeaders.indexOf(e) < 0
}

function each(e, t) {
    for (var r in e)
        if (Object.prototype.hasOwnProperty.call(e, r)) {
            var a = t.call(this, r, e[r]);
            if (a === s.abort)
                break
        }
}

function arrayEach(e, t) {
    for (var r in e)
        if (Object.prototype.hasOwnProperty.call(e, r)) {
            var a = t.call(this, e[r], parseInt(r, 10));
            if (a === s.abort)
                break
        }
}

function signedHeaders(headers) {
    var e = [];
    var keys = Object.keys(headers||request.headers)
    for(var key of keys) {
        key = key.toLowerCase()
        if(isSignableHeader(key)) {
            e.push(key)
        }
    }
    /*each.call(this, this.request.headers, function(t) {
        t = t.toLowerCase(),
            isSignableHeader(t) && e.push(t)
    });*/

    return e.sort().join(";")
}

                         /*


(5) (5) 

['content-md5:6ucujzbo0vbhm0k+xcljlq==', 'host:.r2.cloudflarestorage.com', 'x-amz-content-sha256:unsigned-payload', 'x-amz-date:20241119t062926z', 'x-amz-user-agent:aws-sdk-js/2.1481.0 callback']

['content-md5:3f1M2c/rNoABD8C9PZtn+g==', 'host:.r2.cloudflarestorage.com', 'x-amz-content-sha256:UNSIGNED-PAYLOAD', 'x-amz-date:20241119T063040Z', 'x-amz-user-agent:aws-sdk-js/2.1481.0 callback']
         */
function canonicalHeaders(headers) {
    var ar = [];
    var source = Object.assign({}, headers)
    var ky = Object.keys(source)
    for(var k of ky) {
        var old = source[k];
        delete source[k]
        source[k.toLowerCase()] = old;
    }
    var keys = signedHeaders(headers || request.headers).split(";")
    for(var k of keys) {
        ar.push(
            (""+k.toLowerCase()+":"+source[k])
        )
    }

    return ar.join("\n");
    var e = [];
    each.call(this, this.request.headers, function(t, r) {
        e.push([t, r])
    }),
        e.sort(function(e, t) {
            return e[0].toLowerCase() < t[0].toLowerCase() ? -1 : 1
        });
    var t = [];
    return arrayEach.call(this, e, function(e) {
        var r = e[0].toLowerCase();
        if (isSignableHeader(r)) {
            var i = e[1];
            if (void 0 === i || null === i || "function" != typeof i.toString)
                throw new Error("Header " + r + " contains invalid value")
            t.push(r + ":" + canonicalHeaderValues(i.toString()))
        }
    }),
        t.join("\n")
}
function canonicalHeaderValues(e) {
    return e.replace(/\s+/g, " ").replace(/^\s+|\s+$/g, "")
}

function credentialString(e) {
    return createScope(e.substr(0, 8), request.region, serviceName)
}

function hexEncodedHash(e) {
    return sha256(e, "hex")
}


function createScope(e, t, r) {
    return [e.substr(0, 8), t, r, "aws4_request"].join("/")
}

function preparePostPolicy (e, t) {
    return atob(JSON.stringify({
        expiration: iso8601(e),
        conditions: t
    }))
}

function copy(e) {
    if (null === e || void 0 === e)
        return e;
    var t = {};
    for (var r in e)
        t[r] = e[r];
    return t
}
var awtsCache = {};
var awtsCacheAr = [];
function getSigningKey(e, t, r, o, n) {

    var u = generateHMAC(e.secretAccessKey, e.accessKeyId)
        , p = [u, t, r, o].join("_");
    if ((n = !1 !== n) && p in awtsCache)
        return awtsCache[p];
    var m = generateHMAC("AWS4" + e.secretAccessKey, t, "buffer");

    var c = generateHMAC(m, r, "buffer")
    var l = generateHMAC(c, o, "buffer")
    var d = generateHMAC(l, "aws4_request", "buffer");

    
    return n && (awtsCache[p] = d,
                 awtsCacheAr.push(p),
                 awtsCacheAr.length > 50 && delete awtsCache[awtsCacheAr.shift()]),
        d
}

function sha256(message, outputFormat = "hex") {
    // Ensure the input message is a string or WordArray
    const msg = typeof message === "string" ? CryptoJS.enc.Utf8.parse(message) : message;

    // Compute the SHA-256 hash
    const hash = CryptoJS.SHA256(msg);

    // Return the hash in the requested format
    if (outputFormat === "hex") {
        return CryptoJS.enc.Hex.stringify(hash);
    } else if (outputFormat === "base64") {
        return CryptoJS.enc.Base64.stringify(hash);
    } else {
        throw new Error("Invalid output format specified. Use 'hex' or 'base64'.");
    }
}

function generateMD5(input, type = "base64") {
    // Convert input to WordArray
    const data = 
        typeof input === "string" ? CryptoJS.enc.Utf8.parse(input) :
        input instanceof Uint8Array ? CryptoJS.lib.WordArray.create(input) :
        input;

    // Generate MD5 hash
    const md5Hash = CryptoJS.MD5(data);

    // Return in the desired format
    if (type === "base64") {
        return CryptoJS.enc.Base64.stringify(md5Hash);
    } else if (type === "hex") {
        return CryptoJS.enc.Hex.stringify(md5Hash);
    } else if (type === "buffer") {
        // Convert WordArray to Uint8Array
        const wordArray = md5Hash;
        const buffer = new ArrayBuffer(wordArray.sigBytes);
        const view = new DataView(buffer);

        for (let i = 0; i < wordArray.sigBytes; i++) {
            view.setUint8(i, (wordArray.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
        }

        return new Uint8Array(buffer);
    } else {
        throw new Error("Invalid type specified. Use 'base64', 'hex', or 'buffer'.");
    }
}

function generateHMAC(secretKey, message, type = "base64") {
    // Ensure inputs are in the correct format
    const key = 
        typeof secretKey === "string" ? CryptoJS.enc.Utf8.parse(secretKey) :
        secretKey instanceof Uint8Array ? CryptoJS.lib.WordArray.create(secretKey) :
        secretKey;

    const msg = 
        typeof message === "string" ? CryptoJS.enc.Utf8.parse(message) :
        message instanceof Uint8Array ? CryptoJS.lib.WordArray.create(message) :
        message;

    // Create HMAC using SHA-256
    const hmac = CryptoJS.HmacSHA256(msg, key);

    // Convert the HMAC to the desired format
    if (type === "base64") {
        return CryptoJS.enc.Base64.stringify(hmac);
    } else if (type === "buffer") {
        // Convert WordArray to Uint8Array
        const wordArray = hmac;
        const buffer = new ArrayBuffer(wordArray.sigBytes);
        const view = new DataView(buffer);

        for (let i = 0; i < wordArray.sigBytes; i++) {
            view.setUint8(i, (wordArray.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
        }

        return new Uint8Array(buffer);
    }  else if (type === "hex") {
        return CryptoJS.enc.Hex.stringify(hmac);
    } else {
        throw new Error("Invalid type specified. Use 'base64' or 'buffer' or hex.");
    }
}

© www.soinside.com 2019 - 2024. All rights reserved.