
In diesem Leitfaden erfahren Sie, wie Sie vorab signierte URLs für Firebase Storage mit Astro auf Cloudflare Workers generieren. Sie werden den Prozess durchlaufen, ein neues Astro-Projekt einzurichten, serverseitiges Rendering mit dem Cloudflare-Adapter zu aktivieren, die Firebase Service Account JSON zu erhalten und dann Funktionen zu erstellen, um vorab signierte URLs für den Abruf und das Hochladen von Firebase Storage zu generieren.
Voraussetzungen
Um mitzumachen, benötigen Sie:
- Node.js 20 oder später
- Ein Firebase Konto
Inhaltsverzeichnis
- Generieren einer Firebase Service Account JSON
- Erstellen einer neuen Astro-Anwendung
- Integration des Cloudflare-Adapters in Ihr Astro-Projekt
- Generieren der vorab signierten URLs
- 1. Zugriff auf die Umgebungsvariablen
- 2. Generieren des Google OAuth 2.0 Tokens
- 3. Einrichten der CORS-Richtlinie für den Firebase Storage Bucket
- 4. Erstellen von vorab signierten URLs mit Cloud Storage APIs
- 5. Vorab signierte URL zum Abrufen eines Firebase-Objekts (retrieve)
- 6. Vorab signierte URL zum Hochladen eines Firebase-Objekts (upload)
- 7. Erstellen eines Server-Endpunkts (einer API-Route) in Astro
- Bereitstellung auf Cloudflare Workers
Generieren einer Firebase Service Account JSON
- Gehen Sie zur Firebase-Konsole und durchsuchen Sie Ihr Projekt (erstellen Sie eines, wenn Sie noch keines haben).
- Navigieren Sie in der Firebase-Konsole zu Projekteinstellungen, indem Sie auf das Zahnradsymbol klicken.
- Gehen Sie dann zur Registerkarte Dienstkonten.
- Klicken Sie schließlich auf Neuen privaten Schlüssel generieren und laden Sie die JSON-Datei herunter. Diese Datei enthält Ihre Firebase-Dienstkonto-Anmeldeinformationen.
Erstellen einer neuen Astro-Anwendung
Lassen Sie uns beginnen, indem wir ein neues Astro-Projekt erstellen. Öffnen Sie Ihr Terminal und führen Sie den folgenden Befehl aus:
npm create astro@latest my-app
npm create astro
ist die empfohlene Methode, um schnell ein Astro-Projekt zu erstellen.
Wenn Sie dazu aufgefordert werden, wählen Sie:
Use minimal (empty) template
, wenn Sie gefragt werden, wie Sie das neue Projekt starten möchten.Yes
, wenn Sie gefragt werden, ob Abhängigkeiten installiert werden sollen.Yes
, wenn Sie gefragt werden, ob ein Git-Repository initialisiert werden soll.
Sobald das erledigt ist, können Sie in das Projektverzeichnis wechseln und die App starten:
cd my-appnpm run dev
Die App sollte auf localhost:4321 laufen. Führen Sie als Nächstes den folgenden Befehl aus, um die erforderliche Bibliothek für den Aufbau der Anwendung zu installieren:
npm install jose
Die folgende Bibliothek wird installiert:
jose
: Eine Bibliothek für JavaScript Object Signing and Encryption (JOSE), die zur Handhabung von JWTs und anderen kryptografischen Operationen verwendet wird.
Integration des Cloudflare-Adapters in Ihr Astro-Projekt
Um vorab signierte URLs für jedes Objekt dynamisch zu generieren, aktivieren Sie serverseitiges Rendering in Ihrem Astro-Projekt über den Cloudflare-Adapter. Führen Sie den folgenden Befehl aus:
npx astro add cloudflare
Wenn Sie dazu aufgefordert werden, wählen Sie Folgendes:
Y
, wenn Sie gefragt werden, ob die Cloudflare-Abhängigkeiten installiert werden sollen.Y
, wenn Sie gefragt werden, ob Änderungen an der Astro-Konfigurationsdatei vorgenommen werden sollen.
Sie haben das serverseitige Rendering in Astro erfolgreich aktiviert.
Um sicherzustellen, dass die Ausgabe auf Cloudflare Workers bereitgestellt werden kann, erstellen Sie eine wrangler.toml
-Datei im Stammverzeichnis des Projekts mit dem folgenden Code:
name = "firebase-storage-astro-workers"main = "dist/_worker.js"compatibility_date = "2025-04-01"compatibility_flags = [ "nodejs_compat" ]
[assets]directory="dist"binding="ASSETS"
[vars]FIREBASE_CLIENT_ID=""FIREBASE_PROJECT_ID=""FIREBASE_PRIVATE_KEY=""FIREBASE_CLIENT_EMAIL=""FIREBASE_STORAGE_BUCKET=""FIREBASE_PRIVATE_KEY_ID=""FIREBASE_CLIENT_X509_CERT_URL=""
Stellen Sie danach sicher, dass Sie sowohl eine .env
-Datei als auch eine wrangler.toml
-Datei mit den definierten Variablen haben, damit sie während npm run dev
und bei der Bereitstellung auf Cloudflare Workers zugänglich sind.
Aktualisieren Sie außerdem die astro.config.mjs
-Datei mit dem folgenden Code, um diese Variablen im Code programmatisch zugänglich zu machen:
// ... Bestehende Importe ...import { defineConfig, envField } from 'astro/config'
export default defineConfig({ env: { schema: { FIREBASE_PROJECT_ID: envField.string({ context: 'server', access: 'secret', optional: false }), FIREBASE_CLIENT_X509_CERT_URL: envField.string({ context: 'server', access: 'secret', optional: false }), FIREBASE_PRIVATE_KEY_ID: envField.string({ context: 'server', access: 'secret', optional: false }), FIREBASE_PRIVATE_KEY: envField.string({ context: 'server', access: 'secret', optional: false }), FIREBASE_CLIENT_ID: envField.string({ context: 'server', access: 'secret', optional: false }), FIREBASE_CLIENT_EMAIL: envField.string({ context: 'server', access: 'secret', optional: false }), FIREBASE_STORAGE_BUCKET: envField.string({ context: 'server', access: 'secret', optional: false }), } } // adapter})
Generieren der vorab signierten URLs
1. Zugriff auf die Umgebungsvariablen
Der erste Schritt besteht darin, während der Laufzeit auf die erforderlichen Umgebungsvariablen zuzugreifen, um eine Fetch-Anfrage zu stellen, um CORS auf dem Bucket zu aktivieren und auf die vorab signierten URLs zugreifen zu können. Ab Astro 5.6 und darüber hinaus ist die Methode, wie Sie Laufzeit-Umgebungsvariablen in Ihrem Code zugreifen möchten, die Verwendung der getSecret
-Funktion aus astro:env/server
, um die Dinge anbieterunabhängig zu halten. Dies ist entscheidend, um sensible Informationen sicher zu speichern, ohne sie in Ihre Anwendung zu hardcodieren. Sie werden die folgenden Variablen abrufen:
- FIREBASE_CLIENT_ID
- FIREBASE_PROJECT_ID
- FIREBASE_PRIVATE_KEY
- FIREBASE_CLIENT_EMAIL
- FIREBASE_STORAGE_BUCKET
- FIREBASE_PRIVATE_KEY_ID
- FIREBASE_CLIENT_X509_CERT_URL
import { getSecret } from 'astro:env/server'
const project_id = getSecret('FIREBASE_PROJECT_ID')const private_key_id = getSecret('FIREBASE_PRIVATE_KEY_ID')const private_key = getSecret('FIREBASE_PRIVATE_KEY')const client_email = getSecret('FIREBASE_CLIENT_EMAIL')const client_id = getSecret('FIREBASE_CLIENT_ID')const storageBucket = getSecret('FIREBASE_STORAGE_BUCKET')const client_x509_cert_url = getSecret('FIREBASE_CLIENT_X509_CERT_URL')
export default function getFirebaseConfig() { return { type: 'service_account', client_id, project_id, private_key, client_email, storageBucket, private_key_id, client_x509_cert_url, universe_domain: 'googleapis.com', token_uri: 'https://oauth2.googleapis.com/token', auth_uri: 'https://accounts.google.com/o/oauth2/auth', auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs', }}
2. Generieren des Google OAuth 2.0 Tokens
import getFirebaseConfig from './config'import { importPKCS8, SignJWT } from 'jose'
const serviceAccount = getFirebaseConfig()
async function getAccessTokenWithJose() { const privateKeyPem = serviceAccount.private_key const clientEmail = serviceAccount.client_email if (!privateKeyPem) throw new Error(`FIREBASE_PRIVATE_KEY environment variable is not available`) const iat = Math.floor(Date.now() / 1000) const exp = iat + 3600 const jwt = await new SignJWT({ iss: clientEmail, scope: 'https://www.googleapis.com/auth/devstorage.full_control', aud: 'https://oauth2.googleapis.com/token', iat, exp, }) .setProtectedHeader({ alg: 'RS256' }) .sign(await importPKCS8(privateKeyPem, 'RS256')) const res = await fetch('https://oauth2.googleapis.com/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ assertion: jwt, grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', }), }) const data = await res.json() if (!res.ok) throw new Error(`Token error: ${data.error}`) return data.access_token}
Der obige Code definiert eine asynchrone Funktion getAccessTokenWithJose
, die ein JSON Web Token (JWT) mit der jose
-Bibliothek generiert. Es signiert das JWT mit einem privaten Schlüssel, der aus der Firebase-Konfiguration abgerufen wird, und verwendet es dann, um ein Zugriffstoken vom Google OAuth 2.0-Token-Endpunkt anzufordern. Dieses Zugriffstoken ist erforderlich, um Anfragen an Firebase Storage zu authentifizieren.
3. Einrichten der CORS-Richtlinie für den Firebase Storage Bucket
// ...Bestehende Importe...
// ...Bestehender Code...
async function setBucketCORS(bucketName) { const accessToken = await getAccessTokenWithJose() const corsConfig = { cors: [ { origin: [ 'http://localhost:3000', 'https://your-deployment.workers.dev', ], maxAgeSeconds: 3600, method: ['GET', 'PUT', 'POST'], responseHeader: ['Content-Type', 'Content-MD5', 'Content-Disposition'], }, ], } const res = await fetch(`https://storage.googleapis.com/storage/v1/b/${bucketName.replace('gs://', '')}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify(corsConfig), }) if (!res.ok) { const err = await res.text() console.warn(`Failed to update CORS: ${res.statusText}\n${err}`) }}
Der obige Code definiert eine asynchrone Funktion namens setBucketCORS
, um die Einstellungen für Cross-Origin Resource Sharing (CORS) für einen angegebenen Firebase Storage-Bucket zu konfigurieren. Zuerst wird ein Zugriffstoken mit der Funktion getAccessTokenWithJose
abgerufen. Dann wird ein CORS-Konfigurationsobjekt erstellt, das erlaubte Ursprünge, HTTP-Methoden und Antwortheader spezifiziert. Die Funktion sendet eine PATCH-Anfrage an die Google Cloud Storage API, um die CORS-Einstellungen des Buckets zu aktualisieren, wobei das Zugriffstoken zur Autorisierung verwendet wird.
4. Erstellen von vorab signierten URLs mit Cloud Storage APIs
// ...Bestehende Importe...import { createHash, createSign } from 'node:crypto'
async function generateSignedUrl(bucketName, objectName, { subresource = null, expiration = 604800, httpMethod = 'GET', queryParameters = {}, headers = {} } = {}) { if (expiration > 604800) throw new Error("Expiration can't be longer than 7 days (604800 seconds).") const privateKey = serviceAccount.private_key if (!privateKey) throw new Error(`FIREBASE_PRIVATE_KEY environment variable is not available`) const clientEmail = serviceAccount.client_email const now = new Date() const datestamp = now.toISOString().slice(0, 10).replace(/-/g, '') const timestamp = `${datestamp}T${now.toISOString().slice(11, 19).replace(/:/g, '')}Z` const credentialScope = `${datestamp}/auto/storage/goog4_request` const credential = `${clientEmail}/${credentialScope}` const host = `${bucketName.replace('gs://', '')}.storage.googleapis.com` headers['host'] = host // Kanonische Header const sortedHeaders = Object.keys(headers) .sort() .reduce((obj, key) => { obj[key.toLowerCase()] = headers[key].toLowerCase() return obj }, {}) const canonicalHeaders = Object.entries(sortedHeaders) .map(([k, v]) => `${k}:${v}\n`) .join('') const signedHeaders = Object.keys(sortedHeaders).join(';') // Kanonische Abfragezeichenfolge const fullQueryParams: Record<string, any> = { ...queryParameters, 'X-Goog-Date': timestamp, 'X-Goog-Credential': credential, 'X-Goog-SignedHeaders': signedHeaders, 'X-Goog-Algorithm': 'GOOG4-RSA-SHA256', 'X-Goog-Expires': expiration.toString(), } if (subresource) fullQueryParams[subresource] = '' const orderedQueryParams = Object.keys(fullQueryParams) .sort() .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(fullQueryParams[key])}`) .join('&') const canonicalUri = `/${encodeURIComponent(objectName).replace(/%2F/g, '/')}` const canonicalRequest = [httpMethod, canonicalUri, orderedQueryParams, canonicalHeaders, signedHeaders, 'UNSIGNED-PAYLOAD'].join('\n') const hash = createHash('sha256').update(canonicalRequest).digest('hex') const stringToSign = ['GOOG4-RSA-SHA256', timestamp, credentialScope, hash].join('\n') const signature = createSign('RSA-SHA256').update(stringToSign).sign(privateKey).toString('hex') return `https://${host}${canonicalUri}?${orderedQueryParams}&X-Goog-Signature=${signature}`}
// ...Bestehender Code...
Der obige Code definiert eine generateSignedUrl
-Funktion, die eine signierte URL zum Zugriff auf Objekte in Firebase Storage erstellt. Sie verwendet kryptografische Funktionen, um eine Signatur basierend auf den Anfragedetails zu generieren, einschließlich des Buckets und der Objektnamen, der HTTP-Methode und der Ablaufzeit. Die Funktion erstellt kanonische Header und Abfrageparameter und signiert dann die Anfrage mit dem privaten Schlüssel des Dienstkontos. Die resultierende signierte URL ermöglicht den sicheren Zugriff auf das angegebene Objekt im Storage-Bucket.
5. Vorab signierte URL zum Abrufen eines Firebase-Objekts (retrieve)
Die Funktion getFirebaseObject
unten ruft die vorab signierte URL eines Objekts aus Firebase Storage ab. Sie generiert eine signierte Anfrage, die Ihnen ermöglicht, die Datei sicher zuzugreifen.
// ...Bestehender Code...
export async function getFirebaseObject(Key: string) { try { await setBucketCORS(serviceAccount.storageBucket) return await generateSignedUrl(serviceAccount.storageBucket, Key, { httpMethod: 'GET', expiration: 60 * 60 * 24, }) } catch (e: any) { const tmp = e.message || e.toString() console.log(tmp) return }}
6. Vorab signierte URL zum Hochladen eines Firebase-Objekts (upload)
Die Funktion uploadFirebaseObject
unten ist verantwortlich für die Generierung einer vorab signierten URL zum Hochladen einer Datei auf Firebase Storage. Sie folgt einer ähnlichen Struktur wie die Funktion getFirebaseObject
und generiert eine signierte URL, die Ihnen ermöglicht, Dateien sicher hochzuladen.
// ...Bestehender Code...
export async function uploadFirebaseObject(file: { name: string; type: string }) { try { await setBucketCORS(serviceAccount.storageBucket) return await generateSignedUrl(serviceAccount.storageBucket, file.name, { httpMethod: 'PUT', expiration: 60 * 60 * 24, headers: { 'Content-Type': file.type, }, }) } catch (e: any) { const tmp = e.message || e.toString() console.log(tmp) return }}
7. Erstellen eines Server-Endpunkts (einer API-Route) in Astro
import type { APIContext } from 'astro'import { getFirebaseObject, uploadFirebaseObject } from '../../storage/firebase/index'
// Definieren Sie eine asynchrone Funktion namens GET, die ein Request-Objekt akzeptiert.export async function GET({ request }: APIContext) { // Extrahieren Sie den 'file'-Parameter aus der Anforderungs-URL. const url = new URL(request.url) const file = url.searchParams.get('file') // Überprüfen Sie, ob der 'file'-Parameter in der URL vorhanden ist. if (file) { try { const filePublicURL = await getFirebaseObject(file) // Geben Sie eine Antwort mit der öffentlichen URL des Bildes und einem 200-Statuscode zurück. return new Response(filePublicURL) } catch (error: any) { // Wenn ein Fehler auftritt, protokollieren Sie die Fehlermeldung und geben Sie eine Antwort mit einem 500-Statuscode zurück. const message = error.message || error.toString() console.log(message) return new Response(message, { status: 500 }) } } // Wenn der 'file'-Parameter nicht in der URL gefunden wird, geben Sie eine Antwort mit einem 400-Statuscode zurück. return new Response('Invalid Request.', { status: 400 })}
export async function POST({ request }: APIContext) { // Extrahieren Sie den 'file'-Parameter aus der Anforderungs-URL. const url = new URL(request.url) const type = url.searchParams.get('type') const name = url.searchParams.get('name') if (!type || !name) return new Response('Invalid Request.', {status:400}) try { // Generieren Sie eine zugängliche URL für die hochgeladene Datei // Verwenden Sie diese URL, um einen GET an diesen Endpunkt mit dem file-Abfrageparameter zu senden, der wie unten angegeben ist const publicUploadUrl = await uploadFirebaseObject({ type, name }) // Geben Sie eine Erfolgsmeldung mit einer Nachricht zurück return new Response(publicUploadUrl) } catch (error: any) { // Wenn während des Hochladevorgangs ein Fehler auftritt, geben Sie eine 403-Antwort mit der Fehlermeldung zurück const message = error.message || error.toString() console.log(message) return new Response(message, { status: 500 }) }}
Bereitstellung auf Cloudflare Workers
Um Ihre Anwendung auf Cloudflare Workers bereitstellbar zu machen, erstellen Sie eine Datei namens .assetsignore
im public
-Verzeichnis mit folgendem Inhalt:
_routes.json_worker.js
Als Nächstes müssen Sie das Wrangler CLI verwenden, um Ihre Anwendung auf Cloudflare Workers bereitzustellen. Führen Sie den folgenden Befehl aus, um die Bereitstellung durchzuführen:
npm run build && npx wrangler@latest deploy
Referenzen
Fazit
In diesem Blogbeitrag haben Sie gelernt, wie Sie Firebase Storage mit Astro und Cloudflare Workers für Datei-Uploads und -Abrufe integrieren. Durch die Befolgung der Implementierungsschritte können Sie Dateien sicher in Firebase Storage hochladen und abrufen, wodurch Ihre Webanwendung eine robuste und flexible Speicherlösung erhält.
Wenn Sie bestimmte Abschnitte detaillierter erkunden, bestimmte Konzepte erweitern oder zusätzliche verwandte Themen behandeln möchten, lassen Sie es mich bitte wissen, und ich helfe Ihnen gerne weiter!