Utilizzare gli URL firmati

Questa pagina fornisce una panoramica degli URL firmati e le istruzioni per utilizzarli con Cloud CDN. Gli URL firmati forniscono accesso alle risorse a tempo limitato a chiunque sia in possesso dell'URL, indipendentemente dal fatto che l'utente abbia un Account Google.

Un URL firmato è un URL che fornisce autorizzazioni e tempo limitati per effettuare una richiesta. Gli URL con firma contengono informazioni di autenticazione nelle rispettive stringhe di query, consentendo così agli utenti senza credenziali di eseguire azioni specifiche su una risorsa. Quando generi un URL firmato, specifichi un utente o un account di servizio che deve disporre di autorizzazioni sufficienti per effettuare la richiesta associata all'URL.

Dopo aver generato un URL firmato, chiunque lo possieda può utilizzarlo per eseguire azioni specifiche (ad esempio leggere un oggetto) entro un periodo di tempo specificato.

Gli URL firmati supportano anche un parametro URLPrefix facoltativo, che ti consente di fornire l'accesso a più URL in base a un prefisso comune.

Se vuoi limitare l'accesso a un prefisso URL specifico, valuta la possibilità di utilizzare i cookie firmati.

Prima di iniziare

Prima di utilizzare gli URL firmati:

  • Assicurati che Cloud CDN sia abilitato. Per istruzioni, vedi Utilizzo di Cloud CDN. Puoi configurare gli URL firmati su un backend prima di abilitare Cloud CDN, ma non hanno effetto finché Cloud CDN non è abilitato.

  • Se necessario, esegui l'aggiornamento all'ultima versione di Google Cloud CLI:

    gcloud components update
    

Per una panoramica, consulta URL e cookie firmati.

Configura le chiavi per richiesta firmata

La creazione di chiavi per gli URL firmati o i cookie firmati richiede diversi passaggi, descritti nelle sezioni seguenti.

Considerazioni sulla sicurezza

Cloud CDN non convalida le richieste nelle seguenti circostanze:

  • La richiesta non è firmata.
  • Il servizio di backend o il bucket di backend per la richiesta non ha Cloud CDN abilitato.

Le richieste firmate devono sempre essere convalidate all'origine prima di pubblicare la risposta. Questo perché le origini possono essere utilizzate per pubblicare un mix di contenuti firmati e non firmati e perché un client potrebbe accedere direttamente all'origine.

  • Cloud CDN non blocca le richieste senza un parametro di query Signature o un cookie HTTP Cloud-CDN-Cookie. Rifiuta le richieste con parametri di richiesta non validi (o comunque malformati).
  • Quando l'applicazione rileva una firma non valida, assicurati che risponda con un codice di risposta HTTP 403 (Unauthorized). I codici di risposta HTTP 403 non sono memorizzabili nella cache.
  • Le risposte alle richieste firmate e non firmate vengono memorizzate separatamente nella cache, pertanto una risposta riuscita a una richiesta firmata valida non viene mai utilizzata per pubblicare una richiesta non firmata.
  • Se la tua applicazione invia un codice di risposta memorizzabile nella cache a una richiesta non valida, le richieste future valide potrebbero essere rifiutate in modo errato.

Per i backend Cloud Storage, assicurati di rimuovere l'accesso pubblico, in modo che Cloud Storage possa rifiutare le richieste a cui manca una firma valida.

La seguente tabella riassume il comportamento.

La richiesta ha una firma Successo della cache Comportamento
No No Inoltra all'origine di backend.
No Pubblica dalla cache.
No Convalida la firma. Se è valido, inoltralo all'origine di backend.
Convalida la firma. Se è valido, pubblicalo dalla cache.

Crea chiavi per richiesta firmata

Per abilitare il supporto di URL firmati e cookie firmati di Cloud CDN, devi creare una o più chiavi in un servizio di backend, un bucket di backend o entrambi abilitati per Cloud CDN.

Per ogni servizio di backend o bucket di backend, puoi creare ed eliminare le chiavi in base alle tue esigenze di sicurezza. Ogni backend può avere fino a tre chiavi configurate contemporaneamente. Ti consigliamo di ruotare periodicamente le chiavi eliminando la più vecchia, aggiungendo una nuova chiave e utilizzandola per firmare URL o cookie.

Puoi utilizzare lo stesso nome della chiave in più servizi di backend e bucket di backend perché ogni insieme di chiavi è indipendente dagli altri. I nomi delle chiavi possono contenere fino a 63 caratteri. Per assegnare un nome alle chiavi, utilizza i caratteri A-Z, a-z, 0-9, _ (trattino basso) e - (trattino).

Quando crei le chiavi, assicurati di mantenerle al sicuro, perché chiunque ne abbia una può creare URL firmati o cookie firmati che Cloud CDN accetta finché la chiave non viene eliminata da Cloud CDN. Le chiavi vengono archiviate sul computer in cui generi gli URL firmati o i cookie firmati. Cloud CDN archivia anche le chiavi per verificare le firme delle richieste.

Per mantenere segrete le chiavi, i valori delle chiavi non sono inclusi nelle risposte a nessuna richiesta API. Se perdi una chiave, devi crearne una nuova.

Per creare una chiave di richiesta firmata:

Console

  1. Nella console Google Cloud , vai alla pagina Cloud CDN.

    Vai a Cloud CDN

  2. Fai clic sul nome dell'origine a cui vuoi aggiungere la chiave.
  3. Nella pagina Dettagli origine, fai clic sul pulsante Modifica.
  4. Nella sezione Informazioni di base sull'origine, fai clic su Avanti per aprire la sezione Regole host e percorso.
  5. Nella sezione Regole host e percorso, fai clic su Avanti per aprire la sezione Rendimento della cache.
  6. Nella sezione Contenuti con limitazioni, seleziona Limita accesso con URL e cookie firmati.
  7. Fai clic su Aggiungi chiave di firma.

    1. Specifica un nome univoco per la nuova chiave di firma.
    2. Nella sezione Metodo di creazione della chiave, seleziona Genera automaticamente. In alternativa, fai clic su Inserisci e poi specifica un valore della chiave di firma.

      Per la prima opzione, copia il valore della chiave di firma generata automaticamente in un file privato, che puoi utilizzare per creare URL firmati.

    3. Fai clic su Fine.

    4. Nella sezione Età massima voce cache, inserisci un valore e poi seleziona un'unità di tempo.

  8. Fai clic su Fine.

gcloud

Lo strumento a riga di comando gcloud legge le chiavi da un file locale che specifichi. Il file della chiave deve essere creato generando 128 bit fortemente casuali, codificandoli con base64, quindi sostituendo il carattere + con - e il carattere / con _. Per ulteriori informazioni, consulta RFC 4648. È fondamentale che la chiave sia fortemente casuale. Su un sistema di tipo UNIX, puoi generare una chiave fortemente casuale e archiviarla nel file della chiave con il seguente comando:

head -c 16 /dev/urandom | base64 | tr +/ -_ > KEY_FILE_NAME

Per aggiungere la chiave a un servizio di backend:

gcloud compute backend-services \
   add-signed-url-key BACKEND_NAME \
   --key-name KEY_NAME \
   --key-file KEY_FILE_NAME

Per aggiungere la chiave a un bucket di backend:

gcloud compute backend-buckets \
   add-signed-url-key BACKEND_NAME \
   --key-name KEY_NAME \
   --key-file KEY_FILE_NAME

Configura le autorizzazioni Cloud Storage

Se utilizzi Cloud Storage e hai limitato chi può leggere gli oggetti, devi concedere a Cloud CDN l'autorizzazione a leggere gli oggetti aggiungendo il account di servizio Cloud CDN agli elenchi di controllo degli accessi di Cloud Storage.

Non è necessario creare il account di servizio. L'account di servizio viene creato automaticamente la prima volta che aggiungi una chiave a un bucket di backend in un progetto.

Prima di eseguire il comando seguente, aggiungi almeno una chiave a un bucket di backend nel tuo progetto. In caso contrario, il comando non va a buon fine e viene visualizzato un errore perché il account di servizio di riempimento della cache di Cloud CDN non viene creato finché non aggiungi una o più chiavi per il progetto.

gcloud storage buckets add-iam-policy-binding gs://BUCKET \
  --member=serviceAccount:service-PROJECT_NUM@cloud-cdn-fill.iam.gserviceaccount.com \
  --role=roles/storage.objectViewer

Sostituisci PROJECT_NUM con il numero del tuo progetto e BUCKET con il tuo bucket di archiviazione.

Il account di servizio Cloud CDN service-PROJECT_NUM@cloud-cdn-fill.iam.gserviceaccount.com non viene visualizzato nell'elenco dei service account del tuo progetto. Questo perché l'account di servizio Cloud CDN è di proprietà di Cloud CDN, non del tuo progetto.

Per saperne di più sui numeri di progetto, consulta la sezione Individuare l'ID e il numero di progetto nella documentazione della guida della console Google Cloud .

Personalizzare il tempo massimo di memorizzazione nella cache

Cloud CDN memorizza nella cache le risposte alle richieste firmate indipendentemente dall'intestazione Cache-Control del backend. Il tempo massimo per cui le risposte possono essere memorizzate nella cache senza convalida è impostato dal flag signed-url-cache-max-age, che è impostato su un'ora per impostazione predefinita e può essere modificato come mostrato qui.

Per impostare il tempo massimo di memorizzazione nella cache per un servizio di backend o un bucket di backend, esegui uno dei seguenti comandi:

gcloud compute backend-services update BACKEND_NAME
  --signed-url-cache-max-age MAX_AGE
gcloud compute backend-buckets update BACKEND_NAME
  --signed-url-cache-max-age MAX_AGE

Elenca i nomi delle chiavi per richiesta firmata

Per elencare le chiavi su un servizio di backend o un bucket di backend, esegui uno dei seguenti comandi:

gcloud compute backend-services describe BACKEND_NAME
gcloud compute backend-buckets describe BACKEND_NAME

Eliminare le chiavi per richiesta firmata

Quando gli URL firmati da una determinata chiave non devono più essere onorati, esegui uno dei seguenti comandi per eliminare la chiave dal servizio di backend o dal bucket di backend:

gcloud compute backend-services \
   delete-signed-url-key BACKEND_NAME --key-name KEY_NAME
gcloud compute backend-buckets \
   delete-signed-url-key BACKEND_NAME --key-name KEY_NAME

Firma degli URL

L'ultimo passaggio consiste nel firmare gli URL e distribuirli. Puoi firmare gli URL utilizzando il comando gcloud compute sign-url o il codice che scrivi tu. Se hai bisogno di molti URL firmati, il codice personalizzato offre prestazioni migliori.

Creare URL firmati

Utilizza queste istruzioni per creare URL firmati utilizzando il comando gcloud compute sign-url. Questo passaggio presuppone che tu abbia già creato le chiavi.

Console

Non puoi creare URL firmati utilizzando la console Google Cloud . Puoi utilizzare Google Cloud CLI o scrivere codice personalizzato utilizzando gli esempi riportati di seguito.

gcloud

Google Cloud CLI include un comando per firmare gli URL. Il comando implementa l'algoritmo descritto nella sezione relativa alla scrittura del codice.

gcloud compute sign-url \
  "URL" \
  --key-name KEY_NAME \
  --key-file KEY_FILE_NAME \
  --expires-in TIME_UNTIL_EXPIRATION \
  [--validate]

Questo comando legge e decodifica il valore della chiave con codifica base64url da KEY_FILE_NAME e poi restituisce un URL firmato che puoi utilizzare per le richieste GET o HEAD per l'URL specificato.

Ad esempio:

gcloud compute sign-url \
  "https://example.com/media/video.mp4" \
  --key-name my-test-key \
  --expires-in 30m \
  --key-file sign-url-key-file

URL deve essere un URL valido con un componente di percorso. Ad esempio, http://example.com non è valido, ma https://example.com/ e https://example.com/whatever sono entrambi URL validi.

Se viene fornito il flag facoltativo --validate, questo comando invia una richiesta HEAD con l'URL risultante e stampa il codice di risposta HTTP. Se l'URL firmato è corretto, il codice di risposta è uguale al codice di risultato inviato dal backend. Se il codice di risposta non è lo stesso, controlla di nuovo KEY_NAME e i contenuti del file specificato e assicurati che il valore di TIME_UNTIL_EXPIRATION sia di almeno qualche secondo.

Se non viene fornita la bandiera --validate, non vengono verificati:

  • Gli input
  • L'URL generato
  • L'URL firmato generato

Creare URL firmati in modo programmatico

I seguenti esempi di codice mostrano come creare URL firmati in modo programmatico.

Vai

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"time"
)

// SignURL creates a signed URL for an endpoint on Cloud CDN.
//
// - url must start with "https://" and should not have the "Expires", "KeyName", or "Signature"
// query parameters.
// - key should be in raw form (not base64url-encoded) which is 16-bytes long.
// - keyName must match a key added to the backend service or bucket.
func signURL(url, keyName string, key []byte, expiration time.Time) string {
	sep := "?"
	if strings.Contains(url, "?") {
		sep = "&"
	}
	url += sep
	url += fmt.Sprintf("Expires=%d", expiration.Unix())
	url += fmt.Sprintf("&KeyName=%s", keyName)

	mac := hmac.New(sha1.New, key)
	mac.Write([]byte(url))
	sig := base64.URLEncoding.EncodeToString(mac.Sum(nil))
	url += fmt.Sprintf("&Signature=%s", sig)
	return url
}

Ruby

def signed_url url:, key_name:, key:, expiration:
  # url        = "URL of the endpoint served by Cloud CDN"
  # key_name   = "Name of the signing key added to the Google Cloud Storage bucket or service"
  # key        = "Signing key as urlsafe base64 encoded string"
  # expiration = Ruby Time object with expiration time

  require "base64"
  require "openssl"
  require "time"

  # Decode the URL safe base64 encode key
  decoded_key = Base64.urlsafe_decode64 key

  # Get UTC time in seconds
  expiration_utc = expiration.utc.to_i

  # Determine which separator makes sense given a URL
  separator = "?"
  separator = "&" if url.include? "?"

  # Concatenate url with expected query parameters Expires and KeyName
  url = "#{url}#{separator}Expires=#{expiration_utc}&KeyName=#{key_name}"

  # Sign the url using the key and url safe base64 encode the signature
  signature         = OpenSSL::HMAC.digest "SHA1", decoded_key, url
  encoded_signature = Base64.urlsafe_encode64 signature

  # Concatenate the URL and encoded signature
  signed_url = "#{url}&Signature=#{encoded_signature}"
end

.NET

        /// <summary>
        /// Creates signed URL for Google Cloud SDN
        /// More details about order of operations is here: 
        /// <see cref="https://cloud.google.com/cdn/docs/using-signed-urls#programmatically_creating_signed_urls"/>
        /// </summary>
        /// <param name="url">The Url to sign. This URL can't include Expires and KeyName query parameters in it</param>
        /// <param name="keyName">The name of the key used to sign the URL</param>
        /// <param name="encodedKey">The key used to sign the Url</param>
        /// <param name="expirationTime">Expiration time of the signature</param>
        /// <returns>Signed Url that is valid until {expirationTime}</returns>
        public static string CreateSignedUrl(string url, string keyName, string encodedKey, DateTime expirationTime)
        {
            var builder = new UriBuilder(url);

            long unixTimestampExpiration = ToUnixTime(expirationTime);

            char queryParam = string.IsNullOrEmpty(builder.Query) ? '?' : '&';
            builder.Query += $"{queryParam}Expires={unixTimestampExpiration}&KeyName={keyName}".ToString();

            // Key is passed as base64url encoded
            byte[] decodedKey = Base64UrlDecode(encodedKey);

            // Computes HMAC SHA-1 hash of the URL using the key
            byte[] hash = ComputeHash(decodedKey, builder.Uri.AbsoluteUri);
            string encodedHash = Base64UrlEncode(hash);

            builder.Query += $"&Signature={encodedHash}";
            return builder.Uri.AbsoluteUri;
        }

        private static long ToUnixTime(DateTime date)
        {
            var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            return Convert.ToInt64((date - epoch).TotalSeconds);
        }

        private static byte[] Base64UrlDecode(string arg)
        {
            string s = arg;
            s = s.Replace('-', '+'); // 62nd char of encoding
            s = s.Replace('_', '/'); // 63rd char of encoding

            return Convert.FromBase64String(s); // Standard base64 decoder
        }

        private static string Base64UrlEncode(byte[] inputBytes)
        {
            var output = Convert.ToBase64String(inputBytes);

            output = output.Replace('+', '-')      // 62nd char of encoding
                           .Replace('/', '_');     // 63rd char of encoding

            return output;
        }

        private static byte[] ComputeHash(byte[] secretKey, string signatureString)
        {
            var enc = Encoding.ASCII;
            using (HMACSHA1 hmac = new HMACSHA1(secretKey))
            {
                hmac.Initialize();

                byte[] buffer = enc.GetBytes(signatureString);

                return hmac.ComputeHash(buffer);
            }
        }

Java

/** Samples to create a signed URL for a Cloud CDN endpoint */
public class SignedUrls {

  /**
   * Creates a signed URL for a Cloud CDN endpoint with the given key
   * URL must start with http:// or https://, and must contain a forward
   * slash (/) after the hostname.
   *
   * @param url the Cloud CDN endpoint to sign
   * @param key url signing key uploaded to the backend service/bucket, as a 16-byte array
   * @param keyName the name of the signing key added to the back end bucket or service
   * @param expirationTime the date that the signed URL expires
   * @return a properly formatted signed URL
   * @throws InvalidKeyException when there is an error generating the signature for the input key
   * @throws NoSuchAlgorithmException when HmacSHA1 algorithm is not available in the environment
   */
  public static String signUrl(String url,
                               byte[] key,
                               String keyName,
                               Date expirationTime)
          throws InvalidKeyException, NoSuchAlgorithmException {

    final long unixTime = expirationTime.getTime() / 1000;

    String urlToSign = url
                        + (url.contains("?") ? "&" : "?")
                        + "Expires=" + unixTime
                        + "&KeyName=" + keyName;

    String encoded = SignedUrls.getSignature(key, urlToSign);
    return urlToSign + "&Signature=" + encoded;
  }

  public static String getSignature(byte[] privateKey, String input)
      throws InvalidKeyException, NoSuchAlgorithmException {

    final String algorithm = "HmacSHA1";
    final int offset = 0;
    Key key = new SecretKeySpec(privateKey, offset, privateKey.length, algorithm);
    Mac mac = Mac.getInstance(algorithm);
    mac.init(key);
    return  Base64.getUrlEncoder().encodeToString(mac.doFinal(input.getBytes()));
  }

Python

import argparse
import base64
from datetime import datetime, timezone
import hashlib
import hmac
from urllib.parse import parse_qs, urlsplit


def sign_url(
    url: str,
    key_name: str,
    base64_key: str,
    expiration_time: datetime,
) -> str:
    """Gets the Signed URL string for the specified URL and configuration.

    Args:
        url: URL to sign.
        key_name: name of the signing key.
        base64_key: signing key as a base64 encoded string.
        expiration_time: expiration time as time-zone aware datetime.

    Returns:
        Returns the Signed URL appended with the query parameters based on the
        specified configuration.
    """
    stripped_url = url.strip()
    parsed_url = urlsplit(stripped_url)
    query_params = parse_qs(parsed_url.query, keep_blank_values=True)
    epoch = datetime.fromtimestamp(0, timezone.utc)
    expiration_timestamp = int((expiration_time - epoch).total_seconds())
    decoded_key = base64.urlsafe_b64decode(base64_key)

    url_to_sign = f"{stripped_url}{'&' if query_params else '?'}Expires={expiration_timestamp}&KeyName={key_name}"

    digest = hmac.new(decoded_key, url_to_sign.encode("utf-8"), hashlib.sha1).digest()
    signature = base64.urlsafe_b64encode(digest).decode("utf-8")

    return f"{url_to_sign}&Signature={signature}"

PHP

/**
 * Decodes base64url (RFC4648 Section 5) string
 *
 * @param string $input base64url encoded string
 *
 * @return string
 */
function base64url_decode($input)
{
    $input .= str_repeat('=', (4 - strlen($input) % 4) % 4);
    return base64_decode(strtr($input, '-_', '+/'), true);
}

/**
* Encodes a string with base64url (RFC4648 Section 5)
* Keeps the '=' padding by default.
*
* @param string $input   String to be encoded
* @param bool   $padding Keep the '=' padding
*
* @return string
*/
function base64url_encode($input, $padding = true)
{
    $output = strtr(base64_encode($input), '+/', '-_');
    return ($padding) ? $output : str_replace('=', '',  $output);
}

/**
 * Creates signed URL for Google Cloud CDN
 * Details about order of operations: https://cloud.google.com/cdn/docs/using-signed-urls#creating_signed_urls
 *
 * Example function invocation (In production store the key safely with other secrets):
 *
 *     <?php
 *     $base64UrlKey = 'wpLL7f4VB9RNe_WI0BBGmA=='; // head -c 16 /dev/urandom | base64 | tr +/ -_
 *     $signedUrl = sign_url('https://example.com/foo', 'my-key', $base64UrlKey, time() + 1800);
 *     echo $signedUrl;
 *     ?>
 *
 * @param string $url             URL of the endpoint served by Cloud CDN
 * @param string $keyName         Name of the signing key added to the Google Cloud Storage bucket or service
 * @param string $base64UrlKey    Signing key as base64url (RFC4648 Section 5) encoded string
 * @param int    $expirationTime  Expiration time as a UNIX timestamp (GMT, e.g. time())
 *
 * @return string
 */
function sign_url($url, $keyName, $base64UrlKey, $expirationTime)
{
    // Decode the key
    $decodedKey = base64url_decode($base64UrlKey);

    // Determine which separator makes sense given a URL
    $separator = (strpos($url, '?') === false) ? '?' : '&';

    // Concatenate url with expected query parameters Expires and KeyName
    $url = "{$url}{$separator}Expires={$expirationTime}&KeyName={$keyName}";

    // Sign the url using the key and encode the signature using base64url
    $signature = hash_hmac('sha1', $url, $decodedKey, true);
    $encodedSignature = base64url_encode($signature);

    // Concatenate the URL and encoded signature
    return "{$url}&Signature={$encodedSignature}";
}

Creare URL firmati a livello di programmazione con un prefisso URL

Gli esempi di codice riportati di seguito mostrano come creare in modo programmatico URL firmati con un prefisso URL.

Vai

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"time"
)

// SignURLWithPrefix creates a signed URL prefix for an endpoint on Cloud CDN.
// Prefixes allow access to any URL with the same prefix, and can be useful for
// granting access broader content without signing multiple URLs.
//
// - urlPrefix must start with "https://" and should not include query parameters.
// - key should be in raw form (not base64url-encoded) which is 16-bytes long.
// - keyName must match a key added to the backend service or bucket.
func signURLWithPrefix(urlPrefix, keyName string, key []byte, expiration time.Time) (string, error) {
	if strings.Contains(urlPrefix, "?") {
		return "", fmt.Errorf("urlPrefix must not include query params: %s", urlPrefix)
	}

	encodedURLPrefix := base64.URLEncoding.EncodeToString([]byte(urlPrefix))
	input := fmt.Sprintf("URLPrefix=%s&Expires=%d&KeyName=%s",
		encodedURLPrefix, expiration.Unix(), keyName)

	mac := hmac.New(sha1.New, key)
	mac.Write([]byte(input))
	sig := base64.URLEncoding.EncodeToString(mac.Sum(nil))

	signedValue := fmt.Sprintf("%s&Signature=%s", input, sig)

	return signedValue, nil
}

Java

import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class SignedUrlWithPrefix {

  public static void main(String[] args) throws Exception {
    // TODO(developer): Replace these variables before running the sample.

    // The name of the signing key must match a key added to the back end bucket or service.
    String keyName = "YOUR-KEY-NAME";
    // Path to the URL signing key uploaded to the backend service/bucket.
    String keyPath = "/path/to/key";
    // The date that the signed URL expires.
    long expirationTime = ZonedDateTime.now().plusDays(1).toEpochSecond();
    // URL of request
    String requestUrl = "https://media.example.com/videos/id/main.m3u8?userID=abc123&starting_profile=1";
    // URL prefix to sign as a string. URL prefix must start with either "http://" or "https://"
    // and must not include query parameters.
    String urlPrefix = "https://media.example.com/videos/";

    // Read the key as a base64 url-safe encoded string, then convert to byte array.
    // Key used in signing must be in raw form (not base64url-encoded).
    String base64String = new String(Files.readAllBytes(Paths.get(keyPath)),
        StandardCharsets.UTF_8);
    byte[] keyBytes = Base64.getUrlDecoder().decode(base64String);

    // Sign the url with prefix
    String signUrlWithPrefixResult = signUrlWithPrefix(requestUrl,
        urlPrefix, keyBytes, keyName, expirationTime);
    System.out.println(signUrlWithPrefixResult);
  }

  // Creates a signed URL with a URL prefix for a Cloud CDN endpoint with the given key. Prefixes
  // allow access to any URL with the same prefix, and can be useful for granting access broader
  // content without signing multiple URLs.
  static String signUrlWithPrefix(String requestUrl, String urlPrefix, byte[] key, String keyName,
      long expirationTime)
      throws InvalidKeyException, NoSuchAlgorithmException {

    // Validate input URL prefix.
    try {
      URL validatedUrlPrefix = new URL(urlPrefix);
      if (!validatedUrlPrefix.getProtocol().startsWith("http")) {
        throw new IllegalArgumentException(
            "urlPrefix must start with either http:// or https://: " + urlPrefix);
      }
      if (validatedUrlPrefix.getQuery() != null) {
        throw new IllegalArgumentException("urlPrefix must not include query params: " + urlPrefix);
      }
    } catch (MalformedURLException e) {
      throw new IllegalArgumentException("urlPrefix malformed: " + urlPrefix);
    }

    String encodedUrlPrefix = Base64.getUrlEncoder().encodeToString(urlPrefix.getBytes(
        StandardCharsets.UTF_8));
    String urlToSign = "URLPrefix=" + encodedUrlPrefix
        + "&Expires=" + expirationTime
        + "&KeyName=" + keyName;

    String encoded = getSignatureForUrl(key, urlToSign);
    return requestUrl + "&" + urlToSign + "&Signature=" + encoded;
  }

  // Creates signature for input url with private key.
  private static String getSignatureForUrl(byte[] privateKey, String input)
      throws InvalidKeyException, NoSuchAlgorithmException {

    final String algorithm = "HmacSHA1";
    final int offset = 0;
    Key key = new SecretKeySpec(privateKey, offset, privateKey.length, algorithm);
    Mac mac = Mac.getInstance(algorithm);
    mac.init(key);
    return Base64.getUrlEncoder()
        .encodeToString(mac.doFinal(input.getBytes(StandardCharsets.UTF_8)));
  }
}

Python

import argparse
import base64
from datetime import datetime, timezone
import hashlib
import hmac
from urllib.parse import parse_qs, urlsplit


def sign_url_prefix(
    url: str,
    url_prefix: str,
    key_name: str,
    base64_key: str,
    expiration_time: datetime,
) -> str:
    """Gets the Signed URL string for the specified URL prefix and configuration.

    Args:
        url: URL of request.
        url_prefix: URL prefix to sign.
        key_name: name of the signing key.
        base64_key: signing key as a base64 encoded string.
        expiration_time: expiration time as time-zone aware datetime.

    Returns:
        Returns the Signed URL appended with the query parameters based on the
        specified URL prefix and configuration.
    """
    stripped_url = url.strip()
    parsed_url = urlsplit(stripped_url)
    query_params = parse_qs(parsed_url.query, keep_blank_values=True)
    encoded_url_prefix = base64.urlsafe_b64encode(
        url_prefix.strip().encode("utf-8")
    ).decode("utf-8")
    epoch = datetime.fromtimestamp(0, timezone.utc)
    expiration_timestamp = int((expiration_time - epoch).total_seconds())
    decoded_key = base64.urlsafe_b64decode(base64_key)

    policy = f"URLPrefix={encoded_url_prefix}&Expires={expiration_timestamp}&KeyName={key_name}"

    digest = hmac.new(decoded_key, policy.encode("utf-8"), hashlib.sha1).digest()
    signature = base64.urlsafe_b64encode(digest).decode("utf-8")

    return f"{stripped_url}{'&' if query_params else '?'}{policy}&Signature={signature}"

Generare URL firmati personalizzati

Quando scrivi il tuo codice per generare URL firmati, il tuo obiettivo è creare URL con il seguente formato o algoritmo; tutti i parametri URL sono sensibili alle maiuscole e devono essere nell'ordine mostrato:

https://example.com/foo?Expires=EXPIRATION&KeyName=KEY_NAME&Signature=SIGNATURE

Per generare URL firmati:

  1. Assicurati che l'URL per la firma non contenga un parametro di query Signature.

  2. Determina la scadenza dell'URL e aggiungi un parametro di query Expires con il tempo di scadenza richiesto in ora UTC (il numero di secondi trascorsi dal 1° gennaio 1970 alle ore 00:00:00 UTC). Per massimizzare la sicurezza, imposta il valore sul periodo di tempo più breve possibile per il tuo caso d'uso. Più a lungo è valido un URL firmato, maggiore è il rischio che l'utente a cui lo dai lo condivida con altri, accidentalmente o meno.

  3. Imposta il nome della chiave. L'URL deve essere firmato con una chiave del servizio di backend o del bucket di backend che lo pubblica. È consigliabile utilizzare la chiave aggiunta più di recente perrotazione della chiavei. Aggiungi la chiave all'URL aggiungendo &KeyName=KEY_NAME. Sostituisci KEY_NAME con il nome della chiave scelta creata in Creazione di chiavi di richiesta firmate.

  4. Firma l'URL. Crea l'URL firmato seguendo questi passaggi. Assicurati che i parametri di ricerca siano nell'ordine mostrato immediatamente prima del passaggio 1 e che la distinzione tra maiuscole e minuscole dell'URL firmato non cambi.

    a. Esegui l'hashing dell'intero URL (inclusi http:// o https:// all'inizio e &KeyName... alla fine) con HMAC-SHA1 utilizzando la chiave segreta corrispondente al nome della chiave scelto in precedenza. Utilizza la chiave segreta non codificata in base64url, ma quella non elaborata di 16 byte. Decodificalo se necessario.

    b. Utilizza la codifica base64url per codificare il risultato.

    c. Aggiungi &Signature= all'URL, seguito dalla firma codificata. Non convertire i caratteri = finali della firma nella forma codificata a percentuale, %3D.

Utilizzare i prefissi URL per gli URL firmati

Invece di firmare l'URL della richiesta completo con i parametri di query Expires e KeyName, puoi firmare solo i parametri di query URLPrefix, Expires e KeyName. Ciò consente di riutilizzare letteralmente una determinata combinazione di parametri di ricerca URLPrefix, Expires, KeyName e Signature in più URL che corrispondono a URLPrefix, evitando la necessità di creare una nuova firma per ogni URL distinto.

Nell'esempio seguente, il testo evidenziato mostra i parametri che firmi. Signature viene aggiunto come parametro di ricerca finale, come di consueto.

https://media.example.com/videos/id/master.m3u8?userID=abc123&starting_profile=1&URLPrefix=aHR0cHM6Ly9tZWRpYS5leGFtcGxlLmNvbS92aWRlb3Mv&Expires=1566268009&KeyName=mySigningKey&Signature=8NBSdQGzvDftrOIa3WHpp646Iis=

A differenza della firma di un URL di richiesta completo, quando firmi con URLPrefix non firmi alcunparametri di ricercay, quindiparametri di ricercay possono essere inclusi liberamente nell'URL. A differenza delle firme URL di richiesta complete, questi parametri di ricerca aggiuntivi possono essere visualizzati sia prima che dopo iparametri di ricercay che compongono la firma. Di conseguenza, anche il seguente è un URL valido con un prefisso URL firmato:

https://media.example.com/videos/id/master.m3u8?userID=abc123&URLPrefix=aHR0cHM6Ly9tZWRpYS5leGFtcGxlLmNvbS92aWRlb3Mv&Expires=1566268009&KeyName=mySigningKey&Signature=8NBSdQGzvDftrOIa3WHpp646Iis=&starting_profile=1

URLPrefix indica un prefisso URL con codifica Base64 sicura per gli URL che include tutti i percorsi per cui la firma deve essere valida.

Un URLPrefix codifica uno schema (http:// o https://), un FQDN e un percorso facoltativo. Terminare il percorso con un / è facoltativo ma consigliato. Il prefisso non deve includere parametri di ricerca o frammenti come ? o #.

Ad esempio, https://media.example.com/videos corrisponde alle richieste per entrambi i seguenti elementi:

  • https://media.example.com/videos?video_id=138183&user_id=138138
  • https://media.example.com/videos/137138595?quality=low

Il percorso del prefisso viene utilizzato come sottostringa di testo, non come percorso di directory. Ad esempio, il prefisso https://example.com/data concede l'accesso a entrambi i seguenti elementi:

  • /data/file1
  • /database

Per evitare questo errore, ti consigliamo di terminare tutti i prefissi con /, a meno che tu non scelga intenzionalmente di terminare il prefisso con un nome file parziale come https://media.example.com/videos/123 per concedere l'accesso a:

  • /videos/123_chunk1
  • /videos/123_chunk2
  • /videos/123_chunkN

Se l'URL richiesto non corrisponde a URLPrefix, Cloud CDN rifiuta la richiesta e restituisce un errore HTTP 403 al client.

Convalidare gli URL firmati

La procedura di convalida di un URL firmato è essenzialmente la stessa di generazione di un URL firmato. Ad esempio, supponiamo di voler convalidare il seguente URL firmato:

https://example.com/PATH?Expires=EXPIRATION&KeyName=KEY_NAME&Signature=SIGNATURE

Puoi utilizzare la chiave segreta denominata da KEY_NAME per generare in modo indipendente la firma per il seguente URL:

https://example.com/PATH?Expires=EXPIRATION&KeyName=KEY_NAME

A questo punto puoi verificare che corrisponda a SIGNATURE.

Supponiamo di voler convalidare un URL firmato che contiene un URLPrefix, come mostrato qui:

https://example.com/PATH?URLPrefix=URL_PREFIX&Expires=EXPIRATION&KeyName=KEY_NAME&Signature=SIGNATURE

Innanzitutto, verifica che il valore decodificato in base64 di URL_PREFIX sia un prefisso di https://example.com/PATH. In questo caso, puoi calcolare la firma per quanto segue:

URLPrefix=URL_PREFIX&Expires=EXPIRATION&KeyName=KEY_NAME

A questo punto puoi verificare che corrisponda a SIGNATURE.

Per i metodi di firma basati su URL, in cui la firma fa parte dei parametri della query o è incorporata come componente del percorso dell'URL, la firma e i parametri correlati vengono rimossi dall'URL prima che la richiesta venga inviata all'origine. In questo modo, la firma non causa problemi di routing quando l'origine gestisce la richiesta. Per convalidare queste richieste, puoi esaminare l'intestazione della richiesta x-client-request-url, che include l'URL della richiesta client originale (firmata) prima della rimozione dei componenti firmati.

Rimuovere l'accesso pubblico al bucket Cloud Storage

Affinché gli URL firmati proteggano correttamente i contenuti, è importante che il server di origine non conceda l'accesso pubblico a questi contenuti. Quando utilizzi un bucket Cloud Storage, un approccio comune consiste nel rendere pubblici gli oggetti temporaneamente a scopo di test. Dopo aver attivato gli URL firmati, è importante rimuovere le autorizzazioni di LETTURA allUsers (e allAuthenticatedUsers, se applicabile) (ovvero il ruolo Identity and Access Management Visualizzatore oggetti Storage) sul bucket.

Dopo aver disattivato l'accesso pubblico al bucket, i singoli utenti possono comunque accedere a Cloud Storage senza URL firmati se dispongono dell'autorizzazione di accesso, ad esempio l'autorizzazione PROPRIETARIO.

Per rimuovere l'accesso allUsers READ pubblico a un bucket Cloud Storage, esegui l'operazione descritta in Rendere leggibili pubblicamente tutti gli oggetti in un bucket.

Distribuire e utilizzare gli URL firmati

L'URL restituito da Google Cloud CLI o prodotto dal tuo codice personalizzato può essere distribuito in base alle tue esigenze. Ti consigliamo di firmare solo gli URL HTTPS, perché HTTPS fornisce un trasporto sicuro che impedisce l'intercettazione del componente Signature dell'URL firmato. Analogamente, assicurati di distribuire gli URL firmati tramite protocolli di trasporto sicuri come TLS/HTTPS.