(React-Django) Получение 403 Forbidden при загрузке файла в облачное хранилище google с использованием подписанного url
Я создал подписанный url для загрузки файлов (mp3 видео файл до 1GB) со стороны клиента непосредственно на облачное хранилище. Но когда я пытаюсь загрузить файл, я получаю следующую ошибку:
<Code> SignatureDoesNotMatch </Code> <Message> Access denied. </Message> <Details> The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method. </Details>
Вот как был сгенерирован URL:
https://storage.googleapis.com/bucket-name/filename?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=something.iam.gserviceaccount.com%2xyz%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20240207T120631Z&X-Goog-Expires=900&X-Goog-SignedHeaders=content-type%3Bhost&X-Goog-Signature=21...
Передняя часть выполнена на react, и я сначала делаю вызов для получения signedurl, а затем загружаю файл. Код React :
const responseForSignedUrl = await axios.get(
`${baseUrl}/api/posts/getuploadurl/`
);
if (responseForSignedUrl.status !== 200) {
throw new Error("Failed to obtain signed URL.");
}
const signedUrl = responseForSignedUrl.data.url;
// Upload video file to Cloud Storage using the signed URL
const videoFormData = new FormData();
videoFormData.append("file", video_file);
const uploadResponse = await axios.put(signedUrl, videoFormData, {
headers: {
"Content-Type": "video/mp4",
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
setProgress(percentCompleted);
},
});
if (!uploadResponse.ok) {
throw new Error("Failed to upload video file.");
}
Код бэкенда для генерации подписанных url:
def generate_upload_signed_url_v4(request):
"""Generates a v4 signed URL for uploading a blob using HTTP PUT.
Note that this method requires a service account key file. You can not use
this if you are using Application Default Credentials from Google Compute
Engine or from the Google Cloud SDK.
"""
bucket_name = 'production-bucket-name'
blob_name = 'test1'
# storage_client = storage.Client()
current_directory = os.path.dirname(os.path.abspath(__file__))
# Navigate to the parent directory (folder A)
parent_directory = os.path.dirname(current_directory)
# Access file B within folder A
file_b_path = os.path.join(parent_directory, "signurl.json")
storage_client = storage.Client.from_service_account_json(file_b_path)
bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(blob_name)
print(storage_client)
url = blob.generate_signed_url(
version="v4",
# This URL is valid for 15 minutes
expiration=datetime.timedelta(minutes=15),
# Allow PUT requests using this URL.
method="PUT",
content_type="video/mp4",
)
print("Generated PUT signed URL:")
print(url)
print("You can use this URL with any user agent, for example:")
print(
"curl -X PUT -H 'Content-Type: application/octet-stream' "
"--upload-file my-file '{}'".format(url)
)
return JsonResponse({'url': url})
- Попробовал создать новую учетную запись службы с действительными правами и затем получить ключ, не получилось. Так что уверен, что что-то не так с кодом.
- Пробовал менять типы содержимого на application/octet-stream и multipart/form-data. Я что-то упустил в заголовках запроса?
Я нашел ошибку. Использование FormData() для формирования запроса всегда отправляет запрос с типом содержимого "multipart/form-data". Поэтому даже если мы создадим подписанный url с любым другим типом содержимого, он не будет работать. Я не знаю, почему он также потерпел неудачу, когда я использовал форму-данные при подписании url. В любом случае, нам нужно просто отправить файл, только видео файл, во время отправки запроса, а не форму.