Signierte URLs verwenden

Diese Seite bietet eine Übersicht über signierte URLs und eine Anleitung für deren Verwendung mit Cloud CDN. Signierte URLs bieten allen Nutzern, die über die URL verfügen, einen zeitlich begrenzten Zugriff auf Ressourcen, unabhängig davon, ob der Nutzer ein Google-Konto hat.

Eine signierte URL beschränkt die Berechtigung und die Zeit zum Senden einer Anfrage. Abfragestrings von signierten URLs enthalten Authentifizierungsinformationen, damit Nutzer ohne Anmeldedaten bestimmte Aktionen für eine Ressource ausführen können. Wenn Sie eine signierte URL generieren, geben Sie ein Nutzer- oder Dienstkonto an, das ausreichende Berechtigungen hat, um die mit der URL verknüpfte Anfrage auszuführen.

Nachdem Sie eine signierte URL generiert haben, kann sie von allen Nutzern, denen sie bekannt ist, verwendet werden, um innerhalb eines bestimmten Zeitraums bestimmte Aktionen auszuführen, z. B. ein Objekt zu lesen.

Signierte URLs unterstützen auch den optionalen Parameter URLPrefix, mit dem Sie Zugriff auf mehrere URLs basierend auf einem gemeinsamen Präfix gewähren können.

Wenn Sie den Zugriff auf ein bestimmtes URL-Präfix beschränken möchten, können Sie signierte Cookies verwenden.

Hinweis

Führen Sie die folgenden Schritte aus, bevor Sie signierte URLs verwenden:

  • Achten Sie darauf, dass Cloud CDN aktiviert ist. Eine Anleitung finden Sie unter Cloud CDN verwenden. Sie können signierte URLs auf einem Back-End konfigurieren, bevor Sie Cloud CDN aktivieren. Dies wird jedoch erst wirksam, wenn Cloud CDN aktiviert wird.

  • Aktualisieren Sie bei Bedarf auf die neueste Version des Cloud SDK:

    gcloud components update
    

Eine Übersicht finden Sie unter Signierte URLs und signierte Cookies.

Schlüssel für signierte Anfragen konfigurieren

Schlüssel für signierte URLs oder signierte Cookies werden in mehreren Schritten erstellt, die in den folgenden Abschnitten beschrieben werden.

Sicherheitsaspekte

Sie müssen Ihre Ursprungswebserver so konfigurieren, dass die Signaturen bei jeder von ihnen verarbeiteten signierten Anfrage validiert werden und unsignierte Anfragen akzeptiert oder abgelehnt werden.

  • Cloud CDN blockiert keine Anfragen ohne den Abfrageparameter Signature. Anfragen mit ungültigen oder anderen fehlerhaften Anfrageparametern werden abgelehnt.
  • Wenn die Anwendung eine ungültige Signatur erkennt, prüfen Sie, ob Ihre Anwendung mit dem Antwortcode HTTP 403 (Unauthorized) antwortet. HTTP 403-Antwortcodes sind nicht cachefähig.
  • Wenn Ihre Anwendung einen cachefähigen Antwortcode an eine ungültige Anfrage sendet, werden gültige zukünftige Anfragen möglicherweise falsch abgelehnt.

Achten Sie bei Cloud Storage-Back-Ends darauf, dass Sie den öffentlichen Zugriff entfernt haben, durch den Cloud Storage Anfragen ablehnen kann, denen eine gültige Signatur fehlt.

Schlüssel für signierte Anfragen erstellen

Wenn von Cloud CDN signierte URLs und signierte Cookies unterstützt werden sollen, erstellen Sie einen oder mehrere Schlüssel in einem Back-End-Dienst und/oder Back-End-Bucket mit aktiviertem Cloud CDN.

Für jeden Back-End-Dienst oder -Bucket können Sie entsprechend Ihren Sicherheitsanforderungen Schlüssel erstellen und löschen. Für jedes Back-End können gleichzeitig bis zu drei Schlüssel konfiguriert sein. Wir empfehlen, Schlüssel regelmäßig zu rotieren. Löschen Sie dazu den ältesten, fügen einen neuen hinzu und verwenden den neuen Schlüssel beim Signieren von URLs und Cookies.

Sie können denselben Schlüsselnamen in mehreren Back-End-Diensten und -Buckets verwenden, da jeder Schlüsselsatz von den anderen unabhängig ist. Schlüsselnamen können bis zu 63 Zeichen lang sein. Verwenden Sie zum Benennen Ihrer Schlüssel die Zeichen A–Z, a–z, 0–9, _ (Unterstrich) und - (Bindestrich).

Achten Sie beim Erstellen von Schlüsseln darauf, diese sicher aufzubewahren. Jeder, der einen Ihrer Schlüssel kennt, kann signierte URLs oder signierte Cookies erstellen, die von Cloud CDN akzeptiert werden, bis der Schlüssel aus Cloud CDN gelöscht wird. Die Schlüssel werden auf dem Computer gespeichert, auf dem Sie die signierten URLs generieren. Cloud CDN speichert die Schlüssel auch, um Anfragesignaturen zu prüfen.

Damit die Schlüssel geheim bleiben, werden die Schlüssel/Wert-Paare nicht in Antworten auf API-Anfragen aufgenommen. Wenn Sie einen Schlüssel verlieren, müssen Sie einen neuen erstellen.

Führen Sie die folgenden Schritte aus, um Schlüssel zu erstellen.

Console

  1. Rufen Sie in der Google Cloud Console die Seite Cloud CDN auf.

    Zur Cloud CDN-Seite

  2. Klicken Sie auf Ursprung hinzufügen.
  3. Wählen Sie einen HTTP(S)-Load-Balancer als Ursprung aus.
  4. Wählen Sie Back-End-Dienste oder Back-End-Buckets aus. Für jeden davon:
    1. Klicken Sie auf Konfigurieren und dann auf Signaturschlüssel hinzufügen.
    2. Benennen Sie den neuen Signaturschlüssel unter Name.
    3. Wählen Sie unter Methode zur Schlüsselerstellung die Option Automatisch generieren oder Selbst eingeben aus.
    4. Wenn Sie einen eigenen Schlüssel eingeben, nutzen Sie dazu das Textfeld.
    5. Klicken Sie auf Fertig.
    6. Geben Sie unter Höchstalter der Cache-Einträge einen Wert an und wählen Sie eine Einheit aus der Drop-down-Liste aus. Sie können zwischen Sekunde, Minute, Stunde und Tag wählen. Das maximal mögliche Alter beträgt drei (3) Tage.
  5. Klicken Sie auf Speichern.
  6. Klicken Sie auf Hinzufügen.

gcloud

Das gcloud-Befehlszeilentool liest Schlüssel aus einer von Ihnen angegebenen lokalen Datei. Die Schlüsseldatei muss erstellt werden. Dazu müssen stark zufällige 128 Bit generiert und mit base64 codiert werden. Anschließend muss das Zeichen + durch - und das Zeichen / durch _ ersetzt werden. Weitere Informationen finden Sie unter RFC 4648. Es ist besonders wichtig, dass der Schlüssel stark zufällig ist. Auf einem UNIX-ähnlichen System können Sie einen stark zufälligen Schlüssel generieren und mit dem folgenden Befehl in der Schlüsseldatei speichern:

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

So fügen Sie den Schlüssel zu einem Back-End-Dienst hinzu:

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

So fügen Sie den Schlüssel zu einem Back-End-Bucket hinzu:

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

Cloud Storage-Berechtigungen konfigurieren

Wenn Sie Cloud Storage verwenden und eingeschränkt haben, wer die Objekte lesen kann, müssen Sie Cloud CDN zum Lesen der Objekte berechtigen. Fügen Sie dazu das Cloud CDN-Dienstkonto den ACLs von Cloud Storage hinzu.

Sie müssen das Dienstkonto nicht erstellen. Das Dienstkonto wird automatisch erstellt, wenn Sie einem Back-End-Bucket eines Projekts zum ersten Mal einen Schlüssel hinzufügen.

Fügen Sie mindestens einen Schlüssel zu einem Back-End-Bucket in Ihrem Projekt hinzu, bevor Sie den folgenden Befehl ausführen. Ansonsten schlägt der Befehl mit einem Fehler fehl, da das Dienstkonto von Cloud CDN mit Cache-Füllung erst erstellt wird, wenn Sie mindestens einen Schlüssel für das Projekt hinzufügen. Ersetzen Sie PROJECT_NUM durch die Projektnummer und BUCKET durch den Storage-Bucket.

gsutil iam ch \
  serviceAccount:service-PROJECT_NUM@cloud-cdn-fill.iam.gserviceaccount.com:objectViewer \
  gs://BUCKET

Das Cloud CDN-Dienstkonto service-PROJECT_NUM@cloud-cdn-fill.iam.gserviceaccount.com ist nicht in der Liste der Dienstkonten in Ihrem Projekt enthalten. Dies liegt daran, dass das Cloud CDN-Dienstkonto zu Cloud CDN gehört und nicht zu Ihrem Projekt.

Weitere Informationen zu Projektnummern finden Sie unter Projekt-ID und Projektnummer finden in der Hilfe zur Google Cloud Console.

Maximale Cache-Zeit optional anpassen

Cloud CDN speichert Antworten für signierte Anfragen unabhängig vom Header Cache-Control des Back-Ends. Die maximale Zeit, für die Antworten ohne erneute Validierung im Cache gespeichert werden können, wird durch das Flag signed-url-cache-max-age festgelegt. Sie beträgt standardmäßig eine Stunde und kann wie hier gezeigt geändert werden.

Führen Sie einen der folgenden Befehle aus, um die maximale Cache-Zeit für einen Back-End-Dienst oder -Bucket festzulegen:

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

Schlüsselnamen für signierte Anfragen auflisten

Führen Sie einen der folgenden Befehle aus, um die Schlüssel in einem Back-End-Dienst oder -Bucket aufzulisten:

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

Schlüssel für signierte Anfragen löschen

Wenn mit einem bestimmten Schlüssel signierte URLs nicht mehr akzeptiert werden sollen, führen Sie einen der folgenden Befehle aus, um diesen Schlüssel aus dem Back-End-Dienst oder Back-End-Bucket zu löschen:

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

URLs signieren

Im letzten Schritt werden die URLs signiert und verteilt. Sie können URLs mit dem Befehl gcloud compute sign-url oder durch selbst geschriebenen Code signieren. Wenn Sie eine große Anzahl signierter URLs benötigen, ist benutzerdefinierter Code effizienter.

Signierte URLs erstellen

Folgen Sie dieser Anleitung, um signierte URLs mit dem Befehl gcloud compute sign-url zu erstellen. Bei diesem Schritt wird vorausgesetzt, dass Sie die Schlüssel bereits erstellt haben.

Console

Sie können mit der Cloud Console keine signierten URLs erstellen. Sie können das gcloud-Befehlszeilentool verwenden oder benutzerdefinierten Code mit den folgenden Beispielen schreiben.

gcloud

Das gcloud-Befehlszeilentool enthält einen Befehl zum Signieren von URLs. Der Befehl implementiert den im Abschnitt zum Schreiben eigenen Codes beschriebenen Algorithmus.

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

Dieser Befehl liest und decodiert den base64url-codierten Schlüsselwert aus KEY_FILE_NAME und gibt dann eine signierte URL aus, die Sie für GET- oder HEAD-Anfragen für die angegebene URL verwenden können.

Beispiel:

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

URL muss eine gültige URL mit einer Pfadkomponente sein. http://example.com ist zum Beispiel ungültig, aber https://example.com/ und https://example.com/whatever sind beide gültige URLs.

Wenn das optionale Flag --validate angegeben wird, sendet dieser Befehl eine HEAD-Anfrage mit der resultierenden URL und gibt den HTTP-Antwortcode aus. Wenn die signierte URL korrekt ist, stimmt der Antwortcode mit dem Ergebniscode überein, der von Ihrem Back-End gesendet wird. Wenn der Antwortcode nicht identisch ist, prüfen Sie noch einmal KEY_NAME sowie den Inhalt der angegebenen Datei und achten Sie darauf, dass der Wert von TIME_UNTIL_EXPIRATION mindestens einige Sekunden beträgt.

Wenn das Flag --validate nicht angegeben ist, wird Folgendes nicht bestätigt:

  • Die Eingaben
  • Die generierte URL
  • Die generierte signierte URL

Signierte URLs programmatisch erstellen

Die folgenden Codebeispiele zeigen, wie signierte URLs programmatisch erstellt werden.

Go

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
}

// 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
}

// readKeyFile reads the base64url-encoded key file and decodes it.
func readKeyFile(path string) ([]byte, error) {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read key file: %+v", err)
	}
	d := make([]byte, base64.URLEncoding.DecodedLen(len(b)))
	n, err := base64.URLEncoding.Decode(d, b)
	if err != nil {
		return nil, fmt.Errorf("failed to base64url decode: %+v", err)
	}
	return d[:n], nil
}

func generateSignedURLs(w io.Writer) error {
	// The path to a file containing the base64-encoded signing key
	keyPath := os.Getenv("KEY_PATH")

	// Note: consider using the GCP Secret Manager for managing access to your
	// signing key(s).
	key, err := readKeyFile(keyPath)
	if err != nil {
		return err
	}

	// Generate a signed URL for a specific URL
	url := signURL("https://example.com/media/1234.m3e8", "my-key", key, time.Now().Add(time.Hour*24))
	fmt.Fprintln(w, url)

	// Generate a signed URL for
	urlPrefix, err := signURLWithPrefix("https://www.google.com/", "my-key", key, time.Unix(1549751401, 0))
	if err != nil {
		return err
	}

	fmt.Fprintln(w, urlPrefix)

	return nil
}

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

/**
 * 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

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

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

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

    url_pattern = u'{url}{separator}Expires={expires}&KeyName={key_name}'

    url_to_sign = url_pattern.format(
            url=stripped_url,
            separator='&' if query_params else '?',
            expires=expiration_timestamp,
            key_name=key_name)

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

    signed_url = u'{url}&Signature={signature}'.format(
            url=url_to_sign, signature=signature)

    print(signed_url)

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

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

    Returns:
        Returns the Signed URL appended with the query parameters based on the
        specified URL prefix and configuration.
    """
    stripped_url = url.strip()
    parsed_url = urllib.parse.urlsplit(stripped_url)
    query_params = urllib.parse.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.datetime.utcfromtimestamp(0)
    expiration_timestamp = int((expiration_time - epoch).total_seconds())
    decoded_key = base64.urlsafe_b64decode(base64_key)

    policy_pattern = u'URLPrefix={encoded_url_prefix}&Expires={expires}&KeyName={key_name}'
    policy = policy_pattern.format(
            encoded_url_prefix=encoded_url_prefix,
            expires=expiration_timestamp,
            key_name=key_name)

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

    signed_url = u'{url}{separator}{policy}&Signature={signature}'.format(
            url=stripped_url,
            separator='&' if query_params else '?',
            policy=policy,
            signature=signature)

    print(signed_url)

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}";
}

Benutzerdefinierte signierte URLs generieren

Wenn Sie eigenen Code zum Generieren signierter URLs schreiben, besteht das Ziel darin, URLs im folgenden Format oder Algorithmus zu erstellen. Bei allen URL-Parametern muss die Groß-/Kleinschreibung beachtet werden und sie müssen in dieser Reihenfolge angegeben sein:

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

Führen Sie die folgenden Schritte aus, um signierte URLs zu erstellen:

  1. Achten Sie darauf, dass die URL zum Signieren nicht den Abfrageparameter Signature enthält.

  2. Ermitteln Sie, wann die URL abläuft, und fügen Sie den Abfrageparameter Expires mit dem gewünschten Ablaufzeitpunkt in UTC-Zeit (Anzahl der Sekunden seit 1970-01-01 00:00:00 UTC) an. Für eine maximale Sicherheit setzen Sie den Wert auf den kürzesten Zeitraum, der für Ihren Anwendungsfall möglich ist. Je länger eine signierte URL gültig ist, desto größer ist die Gefahr, dass der Nutzer, dem Sie die URL zuweisen, sie versehentlich oder absichtlich mit anderen teilt.

  3. Legen Sie den Schlüsselnamen fest. Die URL muss mit einem Schlüssel des Back-End-Dienstes oder Back-End-Buckets signiert sein, der die URL bereitstellt. Am besten verwenden Sie den zuletzt hinzugefügten Schlüssel für die Schlüsselrotation. Fügen Sie den Schlüssel zur URL hinzu, indem Sie &KeyName=KEY_NAME anhängen. Ersetzen Sie KEY_NAME durch den Namen des ausgewählten Schlüssels, der unter Signierte Anfrageschlüssel erstellen erstellt wurde.

  4. Signieren Sie die URL. Erstellen Sie die signierte URL mit den folgenden Schritten. Beachten Sie die direkt vor Schritt 1 gezeigte Reihenfolge der Abfrageparameter. Auch die Groß-/Kleinschreibung in der signierten URL darf nicht geändert werden.

    a. Hashen Sie die gesamte URL, einschließlich http:// oder https:// am Anfang und &KeyName... am Ende, mit HMAC-SHA1. Verwenden Sie dabei den geheimen Schlüssel, der dem oben gewählten Schlüsselnamen entspricht. Nutzen Sie außerdem den geheimen 16-Rohbyte-Schlüssel und nicht den base64url-codierten Schlüssel. Decodieren Sie den Schlüssel wenn nötig.

    b. Verwenden Sie base64url encoded, um das Ergebnis zu codieren.

    c. Hängen Sie an die URL &Signature= an, gefolgt von der codierten Signatur.

URL-Präfixe für signierte URLs verwenden

Anstatt die vollständige Anfrage-URL mit den Abfrageparametern Expires und KeyName zu signieren, können Sie stattdessen nur die Abfrageparameter URLPrefix, Expires und KeyName signieren. Dadurch kann eine bestimmte Kombination aus den Suchparametern URLPrefix, Expires, KeyName und Signature wortwörtlich für mehrere URLs wiederverwendet werden, die mit dem URLPrefix übereinstimmen. So muss nicht für jede einzelne URL eine neue Signatur erstellt werden.

Im folgenden Beispiel zeigt der markierte Text die Parameter an, die Sie signieren. Signature wird wie üblich als abschließender Abfrageparameter angehängt.

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

URLPrefix gibt ein URL-sicheres base64-codiertes URL-Präfix an, das alle Pfade umfasst, für die das Cookie gültig sein soll.

Ein URLPrefix codiert ein Schema (entweder http:// oder https://), einen FQDN und einen optionalen Pfad. Den Pfad mit einem / zu beenden, ist optional, wird jedoch empfohlen. Das Präfix darf keine Suchparameter oder Fragmente wie ? oder # enthalten.

https://media.example.com/videos ordnet beispielsweise Anfragen den folgenden Elementen zu:

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

Der Pfad des Präfixes wird als Text-Teilstring und nicht als Verzeichnispfad verwendet. Das Präfix https://example.com/data gewährt beispielsweise Zugriff auf die folgenden beiden Elemente:

  • /data/file1
  • /database

Zur Vermeidung dieses Fehlers empfehlen wir, alle Präfixe mit / zu beenden, es sei denn, Sie müssen das Präfix absichtlich mit einem Teilnamen wie https://media.example.com/videos/123 beenden, um Zugriff auf Folgendes zu gewähren:

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

Wenn die angeforderte URL nicht mit URLPrefix übereinstimmt, lehnt Cloud CDN die Anfrage ab und gibt den HTTP-Fehler HTTP 403 an den Client zurück.

Öffentlichen Zugriff auf den Cloud Storage-Bucket aufheben

Damit signierte URLs Inhalte ordnungsgemäß schützen können, darf der Ursprungsserver keinen öffentlichen Zugriff auf die entsprechenden Inhalte gewähren. Bei Cloud Storage-Buckets werden Objekte in der Regel vorübergehend zu Testzwecken veröffentlicht. Nachdem die signierten URLs aktiviert wurden, müssen die LESEBERECHTIGUNGEN allUsers (und ggf. allAuthenticatedUsers) für den Bucket entfernt werden (mit anderen Worten die IAM-Rolle Storage-Objekt-Betrachter).

Wenn Sie den öffentlichen Zugriff auf den Bucket deaktiviert haben, können einzelne Nutzer dennoch auf Cloud Storage ohne signierte URLs zugreifen, sofern sie eine Zugriffsberechtigung haben, z. B. die Berechtigung INHABER:

Wenn Sie den öffentlichen READ-Zugriff allUsers auf einen Cloud Storage-Bucket entfernen möchten, machen Sie die unter Alle Objekte in einem Bucket öffentlich lesbar machen beschriebenen Aktionen rückgängig.

Signierte URLs verteilen und verwenden

Die vom gcloud-Befehlszeilentool zurückgegebene oder von Ihrem benutzerdefinierten Code erstellte URL kann Ihren Anforderungen entsprechend verteilt werden. Wir empfehlen, nur HTTPS-URLs zu signieren, da HTTPS für eine sichere Übertragung sorgt und dadurch verhindert, dass die Signature-Komponente der signierten URL abgefangen wird. Ebenso sollten Sie die signierten URLs über sichere Transportprotokolle wie TLS/HTTPS verteilen.