咨询:TypeScript NextJS中AWS S3Client v3预签名URL请求构造方法
在TypeScript Next.js项目中用AWS SDK v3生成S3预签名URL(浏览器无密钥下载)
首先明确核心原则:预签名URL必须在后端(Next.js API路由)生成,不能直接在前端处理——如果把AWS密钥暴露在前端代码中,会导致严重的安全风险。前端只需调用后端API获取预签名URL,即可直接下载文件,无需接触密钥。
1. 安装依赖
需要两个AWS SDK v3包:S3客户端和预签名工具
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner # 或使用yarn yarn add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
2. 配置AWS凭证
在项目根目录的.env.local文件中添加环境变量(绝对不要硬编码密钥):
AWS_ACCESS_KEY_ID=你的AWS访问密钥ID AWS_SECRET_ACCESS_KEY=你的AWS秘密访问密钥 AWS_REGION=你的S3存储桶所在区域(例如us-east-1)
3. 创建Next.js API路由生成预签名URL
根据你使用的路由方案选择对应代码:
Pages Router 版本(pages/api/generate-presigned-url.ts)
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import type { NextApiRequest, NextApiResponse } from "next"; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { if (req.method !== "GET") { return res.status(405).json({ message: "仅支持GET请求" }); } const { bucket, key } = req.query; if (!bucket || !key) { return res.status(400).json({ message: "缺少bucket或key参数" }); } // 初始化S3客户端 const s3Client = new S3Client({ region: process.env.AWS_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, }, }); // 构造下载请求(你需要的request对象就是这个Command实例) const request = new GetObjectCommand({ Bucket: bucket as string, Key: key as string, // 可选:设置响应头强制浏览器下载而非预览 ResponseContentDisposition: `attachment; filename="${decodeURIComponent(key as string)}"`, }); // 生成预签名URL,有效期设为1小时(3600秒),可自定义 const presignedUrl = await getSignedUrl(s3Client, request, { expiresIn: 3600 }); res.status(200).json({ presignedUrl }); }
App Router 版本(app/api/generate-presigned-url/route.ts)
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { NextResponse } from "next/server"; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const bucket = searchParams.get("bucket"); const key = searchParams.get("key"); if (!bucket || !key) { return NextResponse.json({ message: "缺少bucket或key参数" }, { status: 400 }); } const s3Client = new S3Client({ region: process.env.AWS_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, }, }); const requestCommand = new GetObjectCommand({ Bucket: bucket, Key: key, ResponseContentDisposition: `attachment; filename="${decodeURIComponent(key)}"`, }); const presignedUrl = await getSignedUrl(s3Client, requestCommand, { expiresIn: 3600 }); return NextResponse.json({ presignedUrl }); }
4. 前端组件调用API并触发下载
"use client"; import { useState } from "react"; export default function DownloadButton() { const [isLoading, setIsLoading] = useState(false); const handleDownload = async () => { setIsLoading(true); try { // 替换为你的存储桶名和文件路径 const bucket = "your-bucket-name"; const fileKey = "documents/report.pdf"; // 调用后端API获取预签名URL const response = await fetch(`/api/generate-presigned-url?bucket=${bucket}&key=${encodeURIComponent(fileKey)}`); const data = await response.json(); // 创建临时a标签触发下载 const aTag = document.createElement("a"); aTag.href = data.presignedUrl; aTag.download = fileKey.split("/").pop() || "file"; document.body.appendChild(aTag); aTag.click(); document.body.removeChild(aTag); } catch (error) { console.error("下载失败:", error); alert("下载失败,请稍后重试"); } finally { setIsLoading(false); } }; return ( <button onClick={handleDownload} disabled={isLoading}> {isLoading ? "生成链接中..." : "下载文件"} </button> ); }
关键细节说明
- request对象的本质:你提到的
request就是AWS SDK v3中对应S3操作的Command实例——下载用GetObjectCommand,上传用PutObjectCommand,构造时传入的参数就是S3 API的请求参数(如Bucket、Key、响应头等)。 - 安全注意:永远不要在前端代码中写入AWS密钥,必须通过Next.js后端处理凭证,前端仅获取预签名URL。
- 有效期自定义:
getSignedUrl的expiresIn参数可以设置URL的有效时长(单位秒),最大不超过7天(604800秒),具体受限于IAM用户的配置。
内容的提问来源于stack exchange,提问作者Karatekid430
相关产品推荐
相关产品推荐

