如何通过SST框架让未登录用户授权访问S3存储桶?
解决SST框架中React应用授权访问S3私有存储桶的问题
你遇到的问题本质是:未登录状态下React应用没有合法的AWS凭证访问私有S3桶,关闭mandatorySignIn后,Amplify不再强制获取凭证,但无凭证访问私有桶必然返回403。以下是两种基于SST框架的解决方案,无需将存储桶设为公开:
方案一:通过API + Lambda代理S3访问
这种方式让前端通过SST创建的API间接访问S3,权限由Lambda和API网关控制,用户无需直接和S3交互。
1. 在SST栈中配置资源
import { StackContext, Api, Bucket, Auth } from "@serverless-stack/resources"; export function MyStack({ stack }: StackContext) { // 创建私有S3桶 const pdfBucket = new Bucket(stack, "PDFBucket", { access: "private", }); // 配置用户认证(如果已有Auth组件可复用) const auth = new Auth(stack, "AppAuth", { authenticator: { handler: "src/auth.handler", }, cognito: { userPoolClient: { oAuth: { callbackUrls: [`${process.env.SITE_URL}/callback`], }, }, }, }); // 创建API网关 const pdfApi = new Api(stack, "PDFApi", { routes: { // 管理员上传PDF(仅认证且具备admin权限的用户可访问) "POST /upload": { handler: "src/uploadPdf.handler", authorizer: auth.cognitoAuthorizer({ scope: ["admin"], }), }, // 所有用户(含未登录)获取PDF(无需认证) "GET /pdf/{key}": { handler: "src/getPdf.handler", authorizer: "none", }, }, }); // 给Lambda授予S3桶的访问权限 pdfApi.attachPermissions([pdfBucket]); // 将API地址注入React应用环境变量 const site = new ReactStaticSite(stack, "ReactApp", { path: "./frontend", environment: { REACT_APP_API_URL: pdfApi.url, }, }); stack.addOutputs({ SiteUrl: site.url, ApiUrl: pdfApi.url, }); }
2. 实现Lambda处理函数
获取PDF的Lambda(src/getPdf.handler)
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; const s3Client = new S3Client({}); const bucketName = process.env.PDF_BUCKET_NAME; export async function handler(event) { const pdfKey = event.pathParameters.key; // 生成有效期5分钟的预签名URL const getCommand = new GetObjectCommand({ Bucket: bucketName, Key: pdfKey, }); const signedUrl = await getSignedUrl(s3Client, getCommand, { expiresIn: 300 }); return { statusCode: 302, headers: { Location: signedUrl, }, body: "", }; }
上传PDF的Lambda(src/uploadPdf.handler)
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; const s3Client = new S3Client({}); const bucketName = process.env.PDF_BUCKET_NAME; export async function handler(event) { const { key, fileContent } = JSON.parse(event.body); await s3Client.send(new PutObjectCommand({ Bucket: bucketName, Key: key, Body: Buffer.from(fileContent, "base64"), ContentType: "application/pdf", })); return { statusCode: 200, body: JSON.stringify({ message: "PDF上传成功" }), }; }
3. React应用中调用API
// 获取PDF并加载 async function loadPdf(pdfKey) { const response = await fetch(`${process.env.REACT_APP_API_URL}/pdf/${pdfKey}`); const signedUrl = response.headers.get("Location"); // 用signedUrl加载PDF(示例用react-pdf) return signedUrl; } // 管理员上传PDF async function uploadPdf(pdfFile, pdfKey) { const reader = new FileReader(); reader.readAsDataURL(pdfFile); reader.onload = async () => { const fileContent = reader.result.split(",")[1]; await fetch(`${process.env.REACT_APP_API_URL}/upload`, { method: "POST", headers: { "Authorization": `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}`, "Content-Type": "application/json", }, body: JSON.stringify({ key: pdfKey, fileContent }), }); }; }
方案二:利用Cognito身份池授予未登录用户临时权限
通过SST配置Cognito身份池,允许未认证用户获取临时AWS凭证,直接访问S3桶的指定资源。
1. 在SST栈中配置Auth和Bucket权限
import { StackContext, Bucket, Auth } from "@serverless-stack/resources"; export function MyStack({ stack }: StackContext) { const pdfBucket = new Bucket(stack, "PDFBucket", { access: "private", }); const auth = new Auth(stack, "AppAuth", { cognito: { // 允许未认证用户获取身份凭证 identityPool: { allowUnauthenticatedIdentities: true, }, userPoolClient: { oAuth: { callbackUrls: [`${process.env.SITE_URL}/callback`], }, }, }, }); // 给未认证用户授予S3读取权限 auth.attachPermissionsForUnauthUsers([ pdfBucket.permissionsFor("read"), ]); // 给认证用户(管理员)授予S3读写权限 auth.attachPermissionsForAuthUsers([ pdfBucket.permissionsFor("write"), ]); const site = new ReactStaticSite(stack, "ReactApp", { path: "./frontend", environment: { REACT_APP_REGION: stack.region, REACT_APP_USER_POOL_ID: auth.userPoolId, REACT_APP_USER_POOL_CLIENT_ID: auth.userPoolClientId, REACT_APP_IDENTITY_POOL_ID: auth.cognitoIdentityPoolId, }, }); stack.addOutputs({ SiteUrl: site.url, }); }
2. 配置React应用的Amplify
确保Amplify正确初始化,未登录时会自动获取未认证用户的临时凭证:
import { Amplify } from "aws-amplify"; Amplify.configure({ Auth: { region: process.env.REACT_APP_REGION, userPoolId: process.env.REACT_APP_USER_POOL_ID, userPoolWebClientId: process.env.REACT_APP_USER_POOL_CLIENT_ID, identityPoolId: process.env.REACT_APP_IDENTITY_POOL_ID, mandatorySignIn: false, // 保持关闭,允许未登录访问 }, Storage: { region: process.env.REACT_APP_REGION, bucket: process.env.REACT_APP_S3_BUCKET_NAME, identityPoolId: process.env.REACT_APP_IDENTITY_POOL_ID, }, });
3. 直接访问S3资源
import { Storage } from "aws-amplify"; // 未登录用户读取PDF async function loadPdf(pdfKey) { const url = await Storage.get(pdfKey); return url; } // 管理员上传PDF async function uploadPdf(pdfFile, pdfKey) { await Storage.put(pdfKey, pdfFile, { contentType: "application/pdf", }); }
方案对比
- API代理方式:适合需要复杂权限控制(如部分PDF仅对特定用户开放)、不想暴露S3桶信息的场景,安全性更高。
- 身份池方式:实现更简单,适合未登录用户可访问所有公开PDF、管理员负责上传的基础场景。
内容的提问来源于stack exchange,提问作者Chris Young
相关产品推荐
相关产品推荐

