React应用上传PDF至S3 Bucket时出现403权限禁止错误
React应用向S3 Bucket上传PDF时出现403 Forbidden错误
我在使用React应用向S3 Bucket上传PDF文件时触发403 Forbidden错误,错误信息不够清晰,相关代码及错误详情如下:
Flask生成预签名URL代码
from flask import Flask, jsonify, request import boto3 import datetime def create_file_upload_url_controller(): try: s3_client = boto3.client('s3') print('create_file_upload_url_controller()') user_id = str(request.user.id) file_name = f"{user_id}_{datetime.datetime.utcnow()}" bucket_name = "docwise-large-files-dev" print(f'file_name: {file_name}') # Generate pre-signed URL for upload presigned_url = s3_client.generate_presigned_url( 'put_object', Params={'Bucket': bucket_name, 'Key': file_name}, ExpiresIn=3600, # URL valid for 1 hour HttpMethod="PUT" ) print(f'presigned_url: {presigned_url}') return jsonify({'upload_url': presigned_url, 'file_name': file_name}) except Exception as e: print(f'e: {e}') return jsonify({'error': str(e)}), 500
React文件上传组件代码
// FileUpload.js import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { addFile } from '../../../../../../actions/filesActions'; import { useNavigate, useParams } from 'react-router-dom'; const FileUpload = ({ onFileChange, addFile, conversation_id,files }) => { const { chatId } = useParams(); useEffect( ()=>{ },[files]) const handleFileChange = (event) => { const selectedFiles = event.target.files; for (let i = 0; i < selectedFiles.length; i++) { const file = selectedFiles[i]; const fileDetails = { conversation_id: chatId, file: file, }; addFile(fileDetails); } }; return ( <div> <input type="file" onChange={handleFileChange} /> </div> ); }; const mapStateToProps = (state) => ({ files: state.files.files }); const mapActionsToProps = { addFile, }; export default connect(mapStateToProps, mapActionsToProps)(FileUpload);
上传S3的Redux Action代码
// filesActions.js export const ADD_PDF = 'ADD_PDF'; export const ADD_PDF_SUCCESS = 'ADD_PDF_SUCCESS'; export const ADD_PDF_FAIL = 'ADD_PDF_FAIL'; export const DELETE_PDF = 'DELETE_PDF'; export const UPDATE_PDF = 'UPDATE_PDF'; let baseUrl; if (process.env.MODE === 'local') { baseUrl = `/api/file/`; } else { baseUrl = `${process.env.REACT_APP_BACKEND_URL}/api/file/`; } export const addFile = (pdfDetails) => async (dispatch, getState) => { try { const { token } = getState().auth; const createFileUploadURLResponse = await fetch(`${baseUrl}create_file_upload_url`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: null, }); if (createFileUploadURLResponse.error != null) { console.log('error: ', createFileUploadURLResponse.error); return dispatch({ type: ADD_PDF_FAIL, payload: createFileUploadURLResponse.error, }); } const createFileUploadURLResponseData = await createFileUploadURLResponse.json(); console.log(`createFileUploadURLResponseData: ${createFileUploadURLResponseData.upload_url}`); console.log(`key : ${createFileUploadURLResponseData.file_name}`) console.log('pdfDetails.file: ',pdfDetails.file); console.log('before uploading file to s3') const uploadFileResponse = await fetch(`${createFileUploadURLResponseData.upload_url}`, { method: 'PUT', headers: { 'Content-Type': pdfDetails.file.type, // Change to file's actual MIME type 'Key': `${createFileUploadURLResponseData.file_name}`, }, body: pdfDetails.file, }); const uploadFileResponseData = await uploadFileResponse.json(); console.log(`uploadFileResponseData: ${JSON.stringify(uploadFileResponseData)}`); } catch (error) { console.log('error: ', error); return null; } };
遇到的错误
filesActions.js:52 PUT https://docwise-large-files-dev.s3.amazonaws.com/Juan_Casas_Resume.pdf?AWSAccessKeyId=AKIAYNCJFQSXTCQHFGR3&Signature=GTb89cRSPxDQhp0TmTfuggDKPfc%3D&Expires=1692137811 403 (Forbidden)
排查修复方案
- 移除上传请求中的
Key请求头
预签名URL已经包含了对象Key的签名信息,额外在请求头中添加Key字段会导致签名不匹配,直接删除该字段:
const uploadFileResponse = await fetch(`${createFileUploadURLResponseData.upload_url}`, { method: 'PUT', headers: { 'Content-Type': pdfDetails.file.type, // 仅保留文件MIME类型 // 移除此行:'Key': `${createFileUploadURLResponseData.file_name}`, }, body: pdfDetails.file, });
确保预签名Key与上传Key完全一致
从错误URL可见,实际上传的Key是Juan_Casas_Resume.pdf,但后端生成的Key是{user_id}_{datetime}格式,两者不匹配。需保证前端完全使用后端返回的file_name作为S3对象的Key,不能替换为原文件名,同时检查后端日志输出的file_name是否与上传URL中的Key一致。检查S3 Bucket的CORS配置
确保Bucket的CORS规则允许PUT请求及前端域名,示例配置:
<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>你的前端域名</AllowedOrigin> <AllowedMethod>PUT</AllowedMethod> <AllowedHeader>*</AllowedHeader> <ExposeHeader>ETag</ExposeHeader> </CORSRule> </CORSConfiguration>
- 验证IAM权限
生成预签名URL的IAM用户必须拥有s3:PutObject权限,示例策略:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::docwise-large-files-dev/*" } ] }
- 完善fetch响应错误处理
fetch仅在网络错误时进入catch块,HTTP状态码错误需手动判断,修改后端请求的错误处理逻辑:
if (!createFileUploadURLResponse.ok) { const errorData = await createFileUploadURLResponse.json(); console.log('error: ', errorData.error); return dispatch({ type: ADD_PDF_FAIL, payload: errorData.error, }); }
内容的提问来源于stack exchange,提问作者user17281101
相关产品推荐
相关产品推荐

