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-readACL,若桶的桶策略或访问控制列表(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
相关产品推荐
相关产品推荐

