部署Node.js AWS S3应用:解决临时凭证过期致图片请求失败问题
问题描述
(说明:这不是EC2实例,仅作信息补充)
我开发了一个Node.js应用,通过app.use("/images", s3Proxy({...}))处理图片请求,图片会被上传至AWS S3存储桶并对外提供访问服务。我使用multer-s3将图片上传至该存储桶,这两个工具都需要使用凭证(access_key、secret_access_key和session_token)进行身份验证。
问题在于这些凭证是临时凭证,部署应用后凭证会过期,导致应用无法再处理图片请求,请问该如何解决这个问题?
const { v4: uuidv4 } = require("uuid"); require("dotenv").config(); const { S3Client } = require("@aws-sdk/client-s3"); const multerS3 = require("multer-s3"); const express = require("express"); const mongoose = require("mongoose"); const bodyParser = require("body-parser"); const multer = require("multer"); const cors = require("cors"); const { graphqlHTTP } = require("express-graphql"); const schema = require("./util/schema"); const resolver = require("./util/resolver"); const isAuth = require("./middleware/isAuth"); const s3Proxy = require("s3-proxy"); const app = express(); const s3 = new S3Client({ credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, sessionToken: process.env.AWS_SESSION_TOKEN, }, region: "eu-central-1", }); app.options("*", cors()); app.use(cors()); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); const upload = multer({ storage: multerS3({ s3: s3, bucket: "express-app-recipe", metadata: function (req, file, cb) { cb(null, { fieldName: file.fieldname }); }, key: function (req, file, cb) { cb(null, uuidv4()); }, }), }); app.use( "/images/", s3Proxy({ bucket: "express-app-recipe", accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, sessionToken: process.env.AWS_SESSION_TOKEN, }) ); app.use(isAuth); app.post("/post-image", upload.single("image"), (req, res, next) => { if (!req.isAuth) { const error = new Error("Not authenticated"); error.statusCode = 401; throw error; } if (!req.file) { const error = new Error("No file attached!"); error.statusCode = 400; throw error; } return res.status(201).send({ message: "File recieved", filePath: req.file.key, }); }); app.use( "/graphql", graphqlHTTP({ schema: schema, rootValue: resolver, graphiql: true, customFormatErrorFn(err) { if (!err.originalError) { return err; } const data = err.originalError.data; // naš seznam izjem const message = err.message || "An error occured"; const code = err.originalError.code; return { message: message, status: code, data: data }; }, }) ); app.use((error, req, res, next) => { console.log(error); const status = error.statusCode; const data = error.data; const message = error.message; res.status(status).json({ message: message, data: data }); }); mongoose .connect(process.env.DB_CONN) .then((result) => { app.listen(process.env.PORT); }) .catch((err) => console.log(err));
解决方案
1. 使用AWS SDK默认凭证链(推荐)
AWS SDK for JavaScript v3(你使用的@aws-sdk/client-s3属于该版本)支持默认凭证链,会自动按优先级查找凭证:
- 环境变量
- 本地AWS配置文件(
~/.aws/credentials) - 绑定到AWS服务的IAM角色(如ECS、EKS、Lambda等,SDK会自动获取并刷新临时凭证)
- 容器凭证(ECS任务角色)
修改代码,移除硬编码的凭证配置,让SDK自动处理:
// 初始化S3Client时移除credentials配置 const s3 = new S3Client({ region: "eu-central-1" }); // 修改s3-proxy配置,移除凭证参数 app.use( "/images/", s3Proxy({ bucket: "express-app-recipe", region: "eu-central-1" }) );
如果应用部署在AWS托管服务上,直接给服务分配具备S3读写权限的IAM角色,SDK会自动维护临时凭证的刷新,无需手动管理。
2. 实现凭证自动刷新逻辑(非AWS环境适用)
若必须使用临时凭证,可通过AWS STS服务定时获取并更新凭证:
- 使用
@aws-sdk/client-sts调用AssumeRole接口获取临时凭证 - 设置定时器,在凭证过期前(如提前5分钟)重新获取并更新S3Client、multer-s3和s3-proxy的配置
简化示例代码:
const { STSClient, AssumeRoleCommand } = require("@aws-sdk/client-sts"); let s3Client; let upload; let s3ProxyMiddleware; async function refreshCredentials() { const stsClient = new STSClient({ credentials: { accessKeyId: process.env.AWS_LONG_TERM_ACCESS_KEY, secretAccessKey: process.env.AWS_LONG_TERM_SECRET_KEY }, region: "eu-central-1" }); const command = new AssumeRoleCommand({ RoleArn: "arn:aws:iam::ACCOUNT_ID:role/YOUR_ROLE", RoleSessionName: "node-app-session" }); const response = await stsClient.send(command); const credentials = response.Credentials; // 更新S3Client s3Client = new S3Client({ credentials: { accessKeyId: credentials.AccessKeyId, secretAccessKey: credentials.SecretAccessKey, sessionToken: credentials.SessionToken }, region: "eu-central-1" }); // 更新multer-s3配置 upload = multer({ storage: multerS3({ s3: s3Client, bucket: "express-app-recipe", metadata: (req, file, cb) => cb(null, { fieldName: file.fieldname }), key: (req, file, cb) => cb(null, uuidv4()), }), }); // 更新s3-proxy中间件 s3ProxyMiddleware = s3Proxy({ bucket: "express-app-recipe", accessKeyId: credentials.AccessKeyId, secretAccessKey: credentials.SecretAccessKey, sessionToken: credentials.SessionToken, region: "eu-central-1" }); // 设置下一次刷新时间(过期前5分钟) const refreshTime = credentials.Expiration.getTime() - Date.now() - 5 * 60 * 1000; setTimeout(refreshCredentials, refreshTime); } // 初始化凭证并启动应用 refreshCredentials().then(() => { app.post("/post-image", upload.single("image"), (req, res, next) => { // 原接口逻辑 }); app.use("/images/", (req, res, next) => s3ProxyMiddleware(req, res, next)); mongoose.connect(process.env.DB_CONN) .then(() => app.listen(process.env.PORT)) .catch(err => console.log(err)); });
3. 使用S3预签名URL替代s3-proxy
无需通过应用代理图片请求,上传成功后生成预签名URL返回给前端,前端直接访问S3:
const { GetObjectCommand } = require("@aws-sdk/client-s3"); const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); app.post("/post-image", upload.single("image"), async (req, res, next) => { if (!req.isAuth) { const error = new Error("Not authenticated"); error.statusCode = 401; throw error; } if (!req.file) { const error = new Error("No file attached!"); error.statusCode = 400; throw error; } // 生成有效期1小时的预签名URL const command = new GetObjectCommand({ Bucket: "express-app-recipe", Key: req.file.key }); const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 }); return res.status(201).send({ message: "File received", filePath: signedUrl }); });
此方式减少应用负担,同时避免s3-proxy长期持有凭证的问题,上传部分仍可结合默认凭证链自动刷新。
内容的提问来源于stack exchange,提问作者Python_Slayer
相关产品推荐
相关产品推荐

