如何使用 AWS S3 预签名 URL 上传和下载文件
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
本月我们新增了一位客户,需要跟踪分销商和医生的订单及销售情况。客户要求对S3存储桶进行私有化管理,仅允许使用预签名URL访问文件。因此,在本篇博客中,我将向您展示如何使用预签名URL在AWS S3存储桶中上传和下载文件,同时确保存储桶的私密性。
目录
先决条件
- JavaScript 基础知识
- AWS S3 存储桶的基本知识
- 具备HTTP请求的基础知识
- 具备 Node.js 和 Express.js 的基础知识
让我们把任务分解成更小的步骤。
步骤 1:设置后端
mkdir backend
cd backend
npm init -y
npm install express aws-sdk
touch index.js
Windows 用户可以使用此方法type nul > index.js创建新文件。
// index.js
const express = require('express')
const app = express()
const AWS = require('aws-sdk')
app.listen(3000, () => {
console.log('Server is running on port 3000')
})
步骤 2:开发一个函数来生成 AWS S3 预签名 URL
// index.js
const express = require('express')
const app = express()
const AWS = require('aws-sdk')
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID, // Your AWS Access Key ID
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, // Your AWS Secret Access Key
region: process.env.AWS_REGION // Your AWS region
signatureVersion: 'v4', // This is the default value
})
const awsS3GeneratePresignedUrl = async (
path,
operation = 'putObject', // Default value is putObject, for get use getObject
expires = 60
): Promise<string> => {
const params = {
Bucket: bucketName, // Bucket name
Key: path, // File name you want to save as in S3
Expires: expires, // 60 seconds is the default value, change if you want
}
const uploadURL = await s3.getSignedUrlPromise(operation, params)
return uploadURL
}
app.listen(3000, () => {
console.log('Server is running on port 3000')
})
步骤 3:配置 AWS S3 存储桶
如果我们尝试将 API 连接到我们的函数,将会收到一个 CORS(跨域资源共享)错误。要解决此问题,我们需要配置 S3 存储桶以允许 API 访问。我们希望 API 能够发起 PUT 和 GET 请求。为此,我们需要按如下方式向 S3 存储桶添加 CORS 配置:
- 打开 Amazon S3 控制台,网址为https://console.aws.amazon.com/s3/
- 找到要配置的存储桶,然后点击它。
- 点击标签
Permissions页 - 向下滚动至该
Cross-origin resource sharing (CORS)部分 - 点击
Edit并添加以下政策
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"PUT",
"GET",
"HEAD"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
- 点击
Save changes - 另外,请确保已启用“阻止公共访问(存储桶设置)”以保持存储桶的私密性(可选)。
将 `<API URL>` 替换AllowedOrigins为您的 API URL 以提高安全性。或者,您也可以使用通配符*允许来自任何来源的访问。
步骤 4:将函数连接到 API 端点
我们将有两个端点,一个用于生成用于上传文件的预签名 URL,另一个用于生成用于下载文件的预签名 URL。
// index.js
const express = require('express')
const app = express()
const AWS = require('aws-sdk')
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID, // Your AWS Access Key ID
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, // Your AWS Secret Access Key
region: process.env.AWS_REGION // Your AWS region
signatureVersion: 'v4', // This is the default value
})
const awsS3GeneratePresignedUrl = async (
path,
operation = 'putObject', // Default value is putObject, for get use getObject
expires = 60
): Promise<string> => {
const params = {
Bucket: bucketName, // Bucket name
Key: path, // File name you want to save as in S3
Expires: expires, // 60 seconds is the default value, change if you want
}
const uploadURL = await s3.getSignedUrlPromise(operation, params)
return uploadURL
}
app.get('/generate-presigned-url', async (req, res) => {
const { path } = req.query
const uploadURL = await awsS3GeneratePresignedUrl(path, 'putObject', 60)
res.send({ path, uploadURL })
})
app.get('/download-presigned-url', async (req, res) => {
const { path } = req.query
const downloadURL = await awsS3GeneratePresignedUrl(path, 'getObject', 60)
res.send({ downloadURL })
})
app.listen(3000, () => {
console.log('Server is running on port 3000')
})
步骤五:设置前端
前端我使用的是 React.js。你也可以选择任何你喜欢的前端框架。安装后axios即可发送 HTTP 请求。
npx create-react-app frontend
cd frontend
npm install axios
步骤 6:将前端连接到 API
// App.js
import React, { useState } from 'react'
import axios from 'axios'
export default function App() {
const [uploadURL, setUploadURL] = useState('')
const [downloadURL, setDownloadURL] = useState('')
const generatePresignedURL = async (path, type) => {
const response = await axios.get(
`http://localhost:3000/generate-presigned-url?path=${path}`,
)
if (type === 'upload') {
setUploadURL(response.data.uploadURL)
} else {
setDownloadURL(response.data.downloadURL)
}
}
return (
<div>
<input type="file" onChange={(e) => uploadFile(e.target.files[0])} />
<button onClick={() => generatePresignedURL('file1.txt', 'upload')}>
Generate Upload URL
</button>
<button onClick={() => generatePresignedURL('file1.txt', 'download')}>
Generate Download URL
</button>
{downloadURL && (
<a href={downloadURL} download>
Download file
</a>
)}
</div>
)
}
用例
- 从前端上传文件到 S3 存储桶,而无需暴露您的 AWS 凭证。
- 无需暴露您的 AWS 凭证,即可将文件从 S3 存储桶下载到您的前端。
- 无需创建 API 来处理文件上传,即可直接从前端将文件上传到 S3 存储桶。
奖励代码片段
// Code to get uploadUrl and put the file to the S3 bucket using fetch API.
const putFileToS3Api = async ({ uploadURL, file }) => {
try {
if (!file) throw new Error('No file provided')
const res = await fetch(signedUrl, {
method: 'PUT',
headers: {
'Content-Type': file.type ?? 'multipart/form-data',
},
body: file,
})
return res
} catch (error) {
console.error(error)
}
}
const getUploadUrlApi = async ({ filename }) => {
// Update the URL to your Graphql Endpoint.
const res = await axios.get('http://localhost:3000/generate-presigned-url', {
path: filename,
})
return res
try {
} catch (error) {
console.error(error)
}
}
export const uploadFileToS3Api = async ({ file }) => {
try {
if (!file) throw new Error('No file provided')
const generateUploadRes = await getUploadUrlApi({ filename: file.name })
if (!generateUploadRes.data.uploadURL)
throw new Error('Error generating pre signed URL')
const uploadRes = await putFileToS3Api({
uploadURL: generateUploadRes.data.uploadURL,
file,
})
if (!uploadRes.ok) throw new Error('Error uploading file to S3')
return {
message: 'File uploaded successfully',
uploadURL: generateUploadRes.data.uploadURL,
path: generateUploadRes.data.path,
}
} catch (error) {
console.error(error)
}
}
调用此uploadFileToS3Api函数,一次性上传要上传到 S3 存储桶的文件。此外,您还可以使用此函数await Promise.all一次上传多个文件。
const uploadFiles = async (files) => {
const uploadPromises = files.map((file) => uploadFileToS3Api({ file }))
const uploadResults = await Promise.all(uploadPromises)
console.log(uploadResults)
}
附加资源
有关 AWS S3 预签名 URL 的更多信息,请点击此处。
结论
好了,你已经成功学会了如何在保证 S3 存储桶私密性的同时,生成用于从 S3 存储桶上传和下载文件的预签名 URL。
希望这篇博客对你有所帮助。如有任何疑问,欢迎在下方评论区留言或在 Twitter 上联系我@thesohailjafri