适用于
JavaScript
的 AWS 开发工具包(即使仅包含 S3
组件)对于我的 Web 应用程序中的一些零星文件上传来说是一个巨大的块。考虑到我可以使用存储桶名称 accessKeyId
和 secretAccessKey
,是否有更精简的方法可以直接从客户端浏览器将文件上传到 S3 存储桶?
要将文件从浏览器上传到 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 可以对上传提供更多控制 - 就像您可以限制上传对象的大小、内容类型等。
S3 支持使用表单帖子上传方式从浏览器上传,无需在浏览器处添加特殊代码。 它涉及特定的表单设计和签名的策略文档,允许用户仅上传与您施加的限制相匹配的文件,并且不会暴露您的密钥。 上传后,它还可以选择将浏览器重定向回您的网站。
http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
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.");
}
}