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

React Native上传AWS S3图片后URL访问被拒及签名URL报错问题

React Native 上传图片到AWS S3后访问异常问题

我用React Native将图片上传至AWS S3,上传流程显示成功,但把生成的URL粘贴到浏览器时出现AccessDenied(访问被拒)错误。尝试过多种URL格式,还使用s3.getSignedUrl方法,结果又碰到NoSuchKey(找不到密钥)、SignatureDoesNotMatch(签名不匹配)的问题。


上传图片到S3的核心函数

const uploadImageToS3 = async () => {
  try {
    const response = await fetch(photo);
    const blob = await response.blob();

    // 直接上传图片到S3,设置public-read ACL
    const result = await Storage.put(uuid.v4(), blob, {
      contentType: 'image/jpeg',
      level: 'public',
      bucket: BUCKET_NAME,
    });

    console.log('图片上传成功', result.key);

    const s3Link = `https://${BUCKET_NAME}.s3.ap-southeast-1.amazonaws.com/${result.key}`;
    console.log('拼接的URL: ', s3Link);
  } catch (error) {
    console.log('上传文件出错: ', error);
  }
};

尝试用s3.getSignedUrl的写法

const pathToImageFile = async () => {
  try {
    const response = await fetch(photo);
    const blob = await response.blob();
    const result = await Storage.put(uuid.v4(), blob, {
      contentType: 'image/jpeg',
      level: 'public',
      bucket: BUCKET_NAME,
    });
    console.log('图片上传成功', result.key);

    const params = {
      Bucket: BUCKET_NAME,
      Key: `${result.key}.jpeg`,
      Expires: 60 * 60 * 24 * 7,
    };

    const url = await s3.getSignedUrl('getObject', params);
    console.log('签名URL: ', url);

    const s3Link = `https://${BUCKET_NAME}.s3.ap-southeast-1.amazonaws.com/${result.key}`;
    console.log('拼接的URL: ', s3Link);
  } catch (error) {
    console.log('上传文件出错: ', error);
  }
};

S3客户端初始化代码

const credentials = new AWS.Credentials({
  accessKeyId: ACCESS_KEY_ID,
  secretAccessKey: SECRET_ACCESS_KEY,
  Bucket: BUCKET_NAME,
});

const s3 = new S3({
  signatureVersion: 'v4',
  region: 'ap-southeast-1',
  credentials,
});

完整业务代码

import {StyleSheet, Text, View, TouchableOpacity, Image} from 'react-native';
import React, {useEffect, useState, useRef} from 'react';
import {Camera} from 'react-native-vision-camera';
import uuid from 'react-native-uuid';
import {Storage} from 'aws-amplify';
import {BUCKET_NAME, MY_BUCKET_URL} from '../utils/contanst';
import {S3} from 'aws-sdk';
import {ACCESS_KEY_ID, SECRET_ACCESS_KEY} from '../PrivateKey';

const VisionCamera = () => {
  const cameraPermission = Camera.requestCameraPermission();
  useEffect(() => {
    cameraPermission.then(res => {
      console.log(res);
    });
  }, []);

  const credentials = new AWS.Credentials({
    accessKeyId: ACCESS_KEY_ID,
    secretAccessKey: SECRET_ACCESS_KEY,
    Bucket: BUCKET_NAME,
  });

  const s3 = new S3({
    signatureVersion: 'v4',
    region: 'ap-southeast-1',
    credentials,
  });

  const devices = Camera.getAvailableCameraDevices();
  const device = devices.find(d => d.position === 'back');

  const camera = useRef(null);
  const [isTakingPhoto, setIsTakingPhoto] = useState(false);
  const [photo, setPhoto] = useState(null);

  const takingPhoto = async () => {
    if (camera.current == null) return;
    const photo = await camera.current.takePhoto();
    console.log('照片路径', `file://${photo.path}`);
    setPhoto(`file://${photo.path}`);
    setIsTakingPhoto(false);
  };

  const pathToImageFile = async () => {
    try {
      const response = await fetch(photo);
      const blob = await response.blob();
      const result = await Storage.put(uuid.v4(), blob, {
        contentType: 'image/jpeg',
        level: 'public',
        bucket: BUCKET_NAME,
      });
      console.log('图片上传成功', result.key);

      const params = {
        Bucket: BUCKET_NAME,
        Key: `${result.key}.jpeg`,
        Expires: 60 * 60 * 24 * 7,
      };

      const url = await s3.getSignedUrl('getObject', params);
      console.log('签名URL: ', url);

      const s3Link = `https://${BUCKET_NAME}.s3.ap-southeast-1.amazonaws.com/${result.key}`;
      console.log('拼接的URL: ', s3Link);
    } catch (error) {
      console.log('上传文件出错: ', error);
    }
  };

  if (device == null) return <NoCameraDeviceError />;
  return (
    <>
      {isTakingPhoto ? (
        <>
          <Camera
            style={StyleSheet.absoluteFill}
            device={device}
            isActive={true}
            photo={true}
            ref={camera}
          />
          <TouchableOpacity
            style={styles.btnTakingPhoto}
            onPress={() => takingPhoto()}>
            <Text>拍照</Text>
          </TouchableOpacity>
        </>
      ) : (
        <>
          <View style={{flex: 1}}>
            <TouchableOpacity onPress={() => setIsTakingPhoto(true)}>
              <Text>准备拍照</Text>
            </TouchableOpacity>
            <Image source={{uri: photo}} style={styles.image} />
            <TouchableOpacity onPress={() => pathToImageFile()}>
              <Text>上传图片</Text>
            </TouchableOpacity>
          </View>
        </>
      )}
    </>
  );
};

export default VisionCamera;

const styles = StyleSheet.create({
  btnTakingPhoto: {
    position: 'absolute',
    bottom: 0,
    width: '100%',
    height: 50,
    backgroundColor: 'red',
    justifyContent: 'center',
    alignItems: 'center',
  },
  image: {
    width: 200,
    height: 200,
    alignSelf: 'center',
    resizeMode: 'contain',
  },
});

AWS IAM 策略

  • 策略列表
  • 策略JSON内容

问题排查与修复方案

1. AccessDenied 问题

原因及修复:

  • 桶权限未开放:即使上传时设置了public-read ACL,若桶的桶策略或访问控制列表(ACL)未开放公共读取权限,依然会被拒绝。
    • 检查桶策略,添加以下语句:
      {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::你的桶名/*"
          }
        ]
      }
      
    • 检查桶ACL,确保"Everyone"用户组拥有"读取对象"权限。
  • ACL未生效:显式在上传参数中指定ACL,确保权限设置生效:
    const result = await Storage.put(uuid.v4(), blob, {
      contentType: 'image/jpeg',
      level: 'public',
      bucket: BUCKET_NAME,
      ACL: 'public-read' // 显式添加
    });
    

2. NoSuchKey 问题

原因及修复:

使用s3.getSignedUrl时,给Key添加了多余的.jpeg后缀,而实际上传的对象Key是result.key(无后缀),导致找不到对象。去掉后缀即可:

const params = {
  Bucket: BUCKET_NAME,
  Key: result.key, // 移除多余的.jpeg后缀
  Expires: 60 * 60 * 24 * 7,
};

3. SignatureDoesNotMatch 问题

原因及修复:

  • 凭证配置错误:AWS.Credentials构造函数不需要Bucket参数,移除后重新初始化:
    const credentials = new AWS.Credentials({
      accessKeyId: ACCESS_KEY_ID,
      secretAccessKey: SECRET_ACCESS_KEY,
      // 移除Bucket参数
    });
    
  • URL拼接错误:避免手动拼接URL,改用Amplify的Storage.get方法直接获取正确的公共URL:
    const publicUrl = await Storage.get(result.key, {
      level: 'public',
      bucket: BUCKET_NAME
    });
    console.log('公共访问URL:', publicUrl);
    
  • 区域/签名版本不匹配:确认S3客户端的region和signatureVersion与桶所在区域完全一致。

4. 额外检查点

  • 确认IAM用户拥有s3:PutObject和s3:GetObject权限;
  • 检查桶是否开启了"阻止所有公共访问",若需要公共访问需关闭该设置。

内容的提问来源于stack exchange,提问作者MayChaosGuideYourWay

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 19:30:58