如何在React/Next.js受保护路由访问AWS非public-read权限文件
实现方案拆解
一、后端上传PDF至AWS S3(私有权限)
首先确保S3桶默认权限为私有(无需额外设置public-read),后端通过IAM授权的账号完成上传,代码示例以Node.js + AWS SDK v3为例:
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; import { Upload } from "@aws-sdk/lib-storage"; // 初始化S3客户端 const s3Client = new S3Client({ region: "你的AWS区域", credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, }, }); // 上传PDF文件到S3 export async function uploadPdfToS3(fileBuffer, fileName) { try { const uploadParams = { Bucket: "你的桶名称", Key: `pdfs/${Date.now()}-${fileName}`, // 自定义存储路径,避免重名 Body: fileBuffer, ContentType: "application/pdf", ACL: "private", // 明确设置私有,默认值也是private }; // 大文件用分段上传,小文件可直接用PutObjectCommand const upload = new Upload({ client: s3Client, params: uploadParams, }); await upload.done(); return { success: true, fileKey: uploadParams.Key }; } catch (error) { console.error("S3上传失败:", error); throw new Error("文件上传失败"); } }
权限配置说明:给后端使用的IAM账号分配s3:PutObject权限,S3桶策略不要添加任何公开访问规则,确保只有授权账号能操作文件。
二、后端生成预签名URL(供前端临时访问)
私有文件无法直接访问,后端需为授权用户生成带过期时间的预签名URL,示例代码:
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { GetObjectCommand } from "@aws-sdk/client-s3"; // 生成PDF的预签名访问链接 export async function getPdfPresignedUrl(fileKey) { try { const getObjectParams = { Bucket: "你的桶名称", Key: fileKey, }; const command = new GetObjectCommand(getObjectParams); // 设置URL过期时间,建议15-30分钟(单位:秒) const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 900 }); return signedUrl; } catch (error) { console.error("生成预签名URL失败:", error); throw new Error("获取文件链接失败"); } }
随后编写后端接口(如GET /api/pdf/:fileKey),接口需先校验用户身份(比如JWT验证),通过后返回预签名URL。
三、React/Next.js受保护路由配置
确保只有登录用户能访问PDF查看页面,以Next.js App Router为例,用Middleware做身份校验:
// middleware.js import { NextResponse } from "next/server"; import { verifyAuthToken } from "./lib/auth"; export async function middleware(request) { const token = request.cookies.get("auth-token")?.value; if (!token) { return NextResponse.redirect(new URL("/login", request.url)); } try { await verifyAuthToken(token); return NextResponse.next(); } catch (error) { return NextResponse.redirect(new URL("/login", request.url)); } } // 指定受保护路由的匹配规则 export const config = { matcher: "/protected/pdf/:path*", };
四、用react-pdf组件渲染PDF
先安装依赖:
npm install @react-pdf/renderer
在受保护页面中,先调用后端接口获取预签名URL,再渲染PDF:
"use client"; import { useState, useEffect } from "react"; import { Document, Page, pdfjs } from "@react-pdf/renderer"; // 配置pdf.js的worker路径,避免加载失败 pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`; export default function PdfViewer({ params }) { const [pdfUrl, setPdfUrl] = useState(""); const [numPages, setNumPages] = useState(null); const [currentPage, setCurrentPage] = useState(1); useEffect(() => { const fetchPdfUrl = async () => { try { const res = await fetch(`/api/pdf/${params.fileKey}`); if (!res.ok) throw new Error("获取PDF链接失败"); const data = await res.json(); setPdfUrl(data.signedUrl); } catch (error) { console.error(error); } }; fetchPdfUrl(); }, [params.fileKey]); const handleDocumentLoad = ({ numPages }) => { setNumPages(numPages); setCurrentPage(1); }; return ( <div className="p-6 max-w-4xl mx-auto"> {pdfUrl ? ( <> <Document file={pdfUrl} onLoadSuccess={handleDocumentLoad}> <Page pageNumber={currentPage} /> </Document> <div className="mt-4 flex items-center gap-3"> <button onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))} disabled={currentPage === 1} className="px-3 py-1 bg-gray-200 rounded" > 上一页 </button> <span>第 {currentPage} 页 / 共 {numPages} 页</span> <button onClick={() => setCurrentPage(prev => Math.min(prev + 1, numPages))} disabled={currentPage === numPages} className="px-3 py-1 bg-gray-200 rounded" > 下一页 </button> </div> </> ) : ( <div className="text-center py-10">加载中...</div> )} </div> ); }
关键注意事项
- 预签名URL的过期时间需平衡安全性和用户体验,建议15-30分钟。
- 后端接口必须严格校验用户身份,防止未授权用户获取文件链接。
- S3桶权限需最小化,仅允许后端IAM账号执行上传和生成预签名的操作。
内容的提问来源于stack exchange,提问作者Ebrahim Khalil Amid
相关产品推荐
相关产品推荐

