You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 18:54:29