Введение
Страница загрузки в браузере может не работать, даже если предварительно подписанный URL успешно используется в curl. Браузер также проверяет, разрешает ли сервис хранения запросы от источника страницы. Вы настроите эту независимую границу CORS для предоставленной страницы синтетической загрузки, сохранив приватность бакета.
Сначала завершите лабораторную работу «Авторизация временного доступа к файлам». Эта новая виртуальная машина предоставляет Node.js 22.22.0, Wrangler 4.131.1 и AWS SDK 3.888.0 с зафиксированными версиями, а также готовую оболочку несвязанной страницы загрузки. Вы создадите новый приватный бакет, временные учетные данные для объекта и одноразовую страницу Worker. R2 уже должен быть активирован. Ознакомьтесь с разделами R2 pricing и CORS behavior. Покупать домен не нужно. Используйте только предоставленный синтетический текст; удалите все ресурсы и отзовите токены лабораторной работы.
Подключение бакета приложения
На этом шаге вы авторизуете виртуальную машину и создадите отдельный приватный бакет для приложения. Авторизация устройства подтверждает вашу учебную учетную запись. Для управления бакетом R2 используется отдельный API-токен, ограниченный этой учетной записью.
Запустите Bash, чтобы использовать приведенный ниже синтаксис команд, затем перейдите в подготовленный проект и проверьте инструменты. Не закрывайте этот терминал: переменные с именами ресурсов должны оставаться доступными:
bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version
Авторизуйте отображаемый код устройства в своем браузере. Перед подтверждением проверьте учебную учетную запись, а также запрошенные разрешения на чтение учетной записи и пользователя:
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write
npx wrangler whoami --json
Убедитесь, что указано loggedIn: true. Прочитайте имя учетной записи, даже если в списке есть только одна учетная запись. Замените YOUR_ACCOUNT_ID ниже фактическим 32-символьным идентификатором этой учетной записи. Команда openssl rand -hex 6 создает двенадцать случайных шестнадцатеричных символов, поэтому имя ресурса этой лабораторной работы не совпадет с именем из предыдущего запуска. Здесь-документ записывает стандартный файл конфигурации; оболочка подставляет в него значения ваших переменных.
ACCOUNT_ID=YOUR_ACCOUNT_ID
RUN_ID=$(openssl rand -hex 6)
NAME="labex-c05-r05-$RUN_ID"
BUCKET="$NAME-docs"
cat > wrangler.jsonc <<JSON
{"name":"$NAME","account_id":"$ACCOUNT_ID","main":"src/index.js","workers_dev":true,"compatibility_date":"2026-07-30","r2_buckets":[{"binding":"DOCUMENTS","bucket_name":"$BUCKET"}]}
JSON
Для управления бакетом откройте в профиле Cloudflare страницу API Tokens и создайте пользовательский токен с именем этой лабораторной работы. Назначьте разрешение Account → Workers R2 Storage → Edit и ограничьте Account Resources учебной учетной записью, идентификатор которой вы сохранили. Установите короткий срок действия. Не добавляйте другие учетные записи или несвязанные разрешения. Это разрешение на уровне учетной записи позволяет создавать и удалять бакеты; токен только для объектов, который вы создадите на следующем шаге, этого делать не может.
Один раз скопируйте токен в скрытое приглашение виртуальной машины. umask 077 ограничивает доступ к файлу вашей учетной записью, а read -s скрывает ввод. Файл использует стандартную переменную токена Wrangler и исключен из Git.
umask 077
read -r -s -p 'R2 management API token: ' R2_MANAGEMENT_TOKEN; printf '\n'
printf 'CLOUDFLARE_API_TOKEN=%s\n' "$R2_MANAGEMENT_TOKEN" > .env.management
unset R2_MANAGEMENT_TOKEN
Используйте --env-file=.env.management только для команд управления R2. Обычная команда whoami по-прежнему проверяет авторизацию устройства виртуальной машины.
Размещайте --env-file в конце каждой команды Wrangler, чтобы имя команды не попадало в список аргументов с файлами. Если после создания каждого бакета Wrangler предложит добавить привязку в конфигурацию, введите n и нажмите Enter. Нужная привязка уже указана в конфигурации.
npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management
Выведите список бакетов и найдите точное сгенерированное имя. Остальные бакеты принадлежат другим работам — не изменяйте их.
npx wrangler r2 bucket list --env-file=.env.management
В Dashboard откройте Storage & databases → R2 → Overview, выберите этот бакет и проверьте пустой список объектов. В его настройках оставьте отключенными общедоступный URL для разработки и пользовательские домены. Имя бакета в Dashboard подтверждает его идентичность; последующие проверки загрузки подтвердят сохраненные байты.
Разрешение на скрипты Worker нужно для развертывания. Разрешение KV нужно Wrangler для служебного учета удаления; в этой лабораторной работе пространство KV не создается. Токен управления R2 остается отдельными учетными данными, ограниченными конкретной учетной записью.
Подготовка страницы загрузки и доступа к подписыванию
На этом шаге вы развернете предоставленную страницу и передадите терминалу учетные данные, ограниченные этим бакетом. Страница содержит только фиксированную синтетическую нагрузку и пустое поле URL; долговременный секрет для подписывания ей не передается.
S3-совместимый API предоставляет стандартным SDK доступ к R2. Для него используется отдельная пара ключей доступа, а не токен устройства Wrangler. В разделе R2 Overview откройте Account Details → API Tokens → Manage, затем создайте User API token с именем, содержащим сгенерированное имя ресурса этой лабораторной работы. Выберите Object Read & Write, ограничьте токен только этим новым бакетом и укажите короткий срок действия, если форма предлагает такую возможность. Не выбирайте все бакеты или доступ Admin. Не закрывайте страницу с этим токеном, пока не сохраните секрет, который можно просмотреть только один раз.
Используйте следующие приглашения Bash в виртуальной машине. read -s скрывает ввод, а umask 077 делает файл с учетными данными доступным только вашей учетной записи. Эти имена соответствуют стандартным переменным окружения AWS SDK. Вставьте Access Key ID и Secret Access Key в соответствующие приглашения, затем нажмите Enter. Не вставляйте значение общего API-токена.
umask 077
read -r -s -p 'Access Key ID: ' AWS_ACCESS_KEY_ID; printf '\n'
read -r -s -p 'Secret Access Key: ' AWS_SECRET_ACCESS_KEY; printf '\n'
printf 'AWS_ACCESS_KEY_ID=%s\nAWS_SECRET_ACCESS_KEY=%s\n' "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" > .env.s3
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY
Создайте повторно используемый клиент стандартного SDK. SDK требует строку региона; для R2 используется значение auto. Чтение существующей конфигурации гарантирует, что операции CLI и SDK направлены на одну и ту же учетную запись и бакет.
cat > storage.mjs <<'JS'
import { S3Client } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("wrangler.jsonc", "utf8"));
export const Bucket = config.r2_buckets[0].bucket_name;
export const s3 = new S3Client({
region: "auto",
requestChecksumCalculation: "WHEN_REQUIRED",
endpoint: `https://${config.account_id}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
JS
requestChecksumCalculation: "WHEN_REQUIRED" предотвращает подписание контрольной суммы пустого тела до того, как браузер передаст содержимое. URL по-прежнему подписывает объект и операцию; приведенный ниже вызов явно включает Content-Type в подпись. Итоговая проверка сравнивает фактически сохраненные байты.
Опубликуйте предоставленную страницу:
npx wrangler deploy
Скопируйте HTTPS-URL развертывания в переменную ORIGIN, не добавляя путь или завершающую косую черту. Источник состоит из схемы, имени хоста и порта; https://example.com отличается от http://example.com. Сохраните значение для последующей независимой проверки:
ORIGIN=https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev
printf "%s\n" "$ORIGIN" > origin.txt
Откройте этот URL в браузере. Убедитесь, что отображается страница Synthetic export upload с пустым полем временного URL. Пока не выполняйте загрузку: бакет еще не разрешает запросы от этого источника браузера.
Разрешение одного источника браузера
На этом шаге вы настроите Cross-Origin Resource Sharing (CORS). Перед отправкой междоменного PUT-запроса браузер спрашивает бакет, разрешены ли источник страницы, метод и заголовки. Этот запрос OPTIONS называется предварительным запросом (preflight). CORS управляет доступом браузера, но не заменяет авторизацию подписанной операции хранения.
Создайте политику с фактическим источником страницы. Терминал подставит $ORIGIN в JSON. Wrangler использует формат Cloudflare с именами rules и allowed, а не формат AWS CORSRules:
cat > cors.json <<JSON
{"rules":[{"allowed":{"origins":["$ORIGIN"],"methods":["PUT"],"headers":["content-type"]},"exposeHeaders":["ETag"],"maxAgeSeconds":60}]}
JSON
Предоставленной странице нужны только PUT и Content-Type. Параметр exposeHeaders разрешает JavaScript читать возвращаемый ETag, а небольшое значение maxAgeSeconds сокращает время кэширования предварительных запросов во время тестирования.
npx wrangler r2 bucket cors set "$BUCKET" --file cors.json --env-file=.env.management
npx wrangler r2 bucket cors list "$BUCKET" --env-file=.env.management
Подтвердите перезапись политики только для этого недавно созданного бакета. В итоговом списке должен отображаться ваш точный источник. В настройках бакета в Dashboard просмотрите политику CORS в режиме только для чтения и оставьте общий доступ отключенным.

Пример: политика разрешает этот источник, PUT и content-type. Ваше сгенерированное имя хоста будет другим. Вывод CLI также проверяет доступность ETag и время кэширования.
Загрузка через браузер
На этом шаге вы объедините разрешение на подписанный PUT-запрос с политикой источника браузера. Средство подписывания, с которым вы уже работали, создает URL ровно для uploads/browser.txt; страница отправляет тот же Content-Type, который использовался при подписывании.
cat > sign-upload.mjs <<'JS'
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { writeFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const url = await getSignedUrl(s3, new PutObjectCommand({
Bucket, Key: "uploads/browser.txt", ContentType: "text/plain"
}), { expiresIn: 300, signableHeaders: new Set(["content-type"]) });
writeFileSync("put-url.txt", url, { mode: 0o600 });
console.log("Prepared a five-minute PUT URL");
JS
node --env-file=.env.s3 sign-upload.mjs
Выведите эту временную ссылку только на время, необходимое для копирования в предоставленную страницу. Не передавайте ее другим, не включайте в снимки экрана и не вставляйте вместо нее секрет S3:
cat put-url.txt
Вставьте ссылку в поле Temporary PUT URL, затем в течение пяти минут выберите Upload synthetic text. Убедитесь, что получен успешный HTTP-результат 2xx и отображается ETag. После загрузки очистите поле URL. Просроченный URL может выглядеть как обычная ошибка CORS браузера, поскольку в ответах с ошибками могут отсутствовать заголовки CORS. При необходимости создайте новую ссылку.
Загрузите реальный объект через Wrangler и сравните фиксированную нагрузку:
npx wrangler r2 object get "$BUCKET/uploads/browser.txt" --remote --file browser-download.txt --env-file=.env.management
printf "Synthetic browser export.\n" > expected-browser.txt
cmp expected-browser.txt browser-download.txt
Проверка платформы независимо читает байты и тип содержимого. Взаимодействие с браузером по-прежнему необходимо как доказательство выполнения этой лабораторной работы; одного curl недостаточно, поскольку он не применяет правила CORS браузера.

Настоящий браузер вернул HTTP 200 и доступный для чтения ETag; поле временного URL очищено. Страница показывает буквальное \n, но загружает настоящий перевод строки. Независимое скачивание подтверждает точное содержимое размером 26 байт.
Разделение политики источника и разрешения подписи
На этом шаге вы проверите разрешенный и запрещенный предварительные запросы, не создавая новый объект. Эти запросы curl спрашивают, что разрешено делать браузеру; сам curl не применяет полученный ответ.
ACCOUNT_ID=$(node -p "JSON.parse(require('fs').readFileSync('wrangler.jsonc')).account_id")
OBJECT_URL="https://$ACCOUNT_ID.r2.cloudflarestorage.com/$BUCKET/uploads/browser.txt"
curl -i -X OPTIONS -H "Origin: $ORIGIN" -H "Access-Control-Request-Method: PUT" -H "Access-Control-Request-Headers: content-type" "$OBJECT_URL"
Убедитесь, что Access-Control-Allow-Origin совпадает с точным источником страницы. Теперь отправьте запрос от несвязанного источника:
curl -i -X OPTIONS -H "Origin: https://outside.example" -H "Access-Control-Request-Method: PUT" -H "Access-Control-Request-Headers: content-type" "$OBJECT_URL"
Ответ не должен разрешать этот источник и не должен содержать *. Точный статус ответа при отказе может отличаться; для запрета доступа важно отсутствие разрешения. Наконец, попробуйте выполнить чтение без подписи:
curl -sS -o unsigned.xml -w "%{http_code}\n" "$OBJECT_URL"
Ожидайте 400 с XML Code InvalidArgument и Message Authorization, как наблюдалось для полностью неподписанного запроса к этой конечной точке R2 S3. Проверьте оба поля; произвольная ошибка или сбой сети не доказывает отклонение подписи.
python3 - <<'PYXML'
from xml.etree import ElementTree
root = ElementTree.parse("unsigned.xml").getroot()
print("Code:", root.findtext("Code"))
print("Message:", root.findtext("Message"))
PYXML
Включение CORS не сделало бакет общедоступным и не отменило проверки подписи. Клиент, не являющийся браузером, но имеющий действительную подпись, по-прежнему может выполнять разрешенную операцию независимо от заголовка Origin; строки источников не являются учетными данными для идентификации.
Удаление ресурсов и учетных данных загрузки
На этом шаге вы удалите Worker интерфейса, конкретный объект и бакет. Перед отзывом учетных данных убедитесь, что ресурсы отсутствуют.
npx wrangler delete
npx wrangler r2 object delete "$BUCKET/uploads/browser.txt" --remote --env-file=.env.management
npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management
npx wrangler r2 bucket list --env-file=.env.management
Убедитесь, что остались только сгенерированные имена этой лабораторной работы. Обновите списки Worker и бакетов в Dashboard. Запустите проверку очистки платформы, пока авторизация управления еще активна. Конфигурация CORS бакета удаляется вместе с бакетом.
Отзыв учетных данных лабораторной работы и выход
На этом шаге вы закроете доступ, оставшийся после выполнения работы. На странице токенов API R2 отзовите только токен объектов, названный в соответствии с этой лабораторной работой. На странице API Tokens своего профиля отзовите отдельный токен управления R2, созданный для этой работы. Удаление бакета не отзывает токен, а выход из Wrangler не отзывает учетные данные S3.
После отзыва удалите локальный файл с учетными данными и выйдите из Wrangler на этой виртуальной машине:
rm .env.s3 .env.management put-url.txt
npx wrangler logout
Проверьте структурированные данные об идентификации. Ненулевой код завершения при выходе из системы ожидаем:
npx wrangler whoami --json || true
Убедитесь, что указано loggedIn: false, и не выходите из обычного Dashboard. Платформа проверяет удаление локальных учетных данных и выход из Wrangler. Отзыв обоих токенов выполняется вручную в Dashboard на контрольных этапах этой лабораторной работы; по удалению файлов он не определяется.
Резюме
Настройте CORS для точного источника, загрузите синтетические байты по предварительно подписанному URL, различите разрешение браузера и авторизацию, а затем удалите ресурсы.



