2017-06-20 1 views
1

Ich beabsichtige, die Benutzer meiner Anwendung Dateien direkt in GCS hochladen zu lassen, wofür ich eine PUT request mit einer signierten URL verwende. Mein Code in Python wie folgt aussieht:Signatur stimmt nicht überein mit signierter URL mit GCS

def get(self): 
    base_url = 'https://storage.googleapis.com/' + self.get_bucket_name() 
    expiration = utils.unix_time_secs(datetime.now() + timedelta(hours=1)) 

    string_to_sign = ( 'PUT' + "\n" + 
         '' + "\n" + 
         '' + "\n" + 
         str(expiration) + "\n" + 
         '' + "\n" + 
         'my-bucket.appspot.com') 

    signed_string = app_identity.sign_blob(string_to_sign)[1] 
    signature = base64.b64encode(signed_string) 
    signature = urllib.quote(signature, safe='') 
    google_access_id = app_identity.get_service_account_name() 

    params = '?GoogleAccessId={0}&Expires={1}&Signature={2}'.format(
         google_access_id, expiration, signature) 


    self.template_values['base_url'] = base_url 
    self.template_values['params'] = params 
    self.render('testA.html') 

Auf der Client-Seite, verwende ich einen XMLHttpRequest die Datei zu GCS senden:

function gCloud (base_url, params, callback) { 
    var xhr = new XMLHttpRequest(); 
    var file = document.querySelector('#fotos').files[0] 

    //Here goes an event listener (callback) 

    url = base_url + '/' + file.name + params 
    xhr.open("PUT", url, true); 
    xhr.setRequestHeader("Content-Type", file.type) 
    xhr.send(file) 
} 

Das Problem ist, dass, wenn ich diese Anfrage sende ich eine bekommen 403 Unzulässiger Fehler, der besagt, dass die berechnete Signatur nicht mit der von mir angegebenen übereinstimmt. Es ist wahrscheinlich ein dummer Fehler, aber ich kann das nicht zum Laufen bringen. Was könnte das Problem sein?

EDIT:

ich versuchte Google-Cloud-Python, und versucht, diesen Code (das gearbeitet hat):

def get(self): 
    bucket = storage.Client().get_bucket(self.get_bucket_name()) 
    blob = bucket.blob('Yosemite.jpg') 

    expiration = utils.unix_time_secs(datetime.now() + timedelta(hours=1)) 
    base_url = blob.generate_signed_url(expiration, "PUT", 'image/jpeg') 
    self.template_values['base_url'] = base_url 
    self.render('testA.html') 

Das Problem ist jetzt, dass, da 'generate_signed_url' ist ein Verfahren des Blob Klasse es bereits annimmt, ich kenne den eindeutigen Pfad des Objekts, um die URL zu erstellen, wie in Blob = bucket.blob ('Yosemite.jpg'), was ich nicht. Was ist, wenn der Benutzer Space.jpg hochladen möchte? Anstatt my-bucket/Space.jpg zu erstellen, wird Yosemite.jpg überschrieben, also werde ich Yosemite.jpg haben und wenn ich das Bild öffne, wird es tatsächlich Space.jpg sein. Wie kann ich das lösen?

Außerdem, wenn ich Content-Type = 'image-jpeg' nicht in die Funktion 'generate_signed_url' einbeziehe, stimmt die Signatur nicht überein. Was ist, wenn der Benutzer stattdessen ein PNG hochlädt?

Antwort

2

URL-Signatur-Code ist knifflig und notorisch schwer zu debuggen. Glücklicherweise verfügt die google-cloud-Bibliothek von Google über die Funktion "generate_signed_url", die das für Sie erledigt. Ich ermutige Sie sehr, es zu benutzen, anstatt es selbst neu zu schreiben. Here's the documentation.

Auch wenn Sie es nicht verwenden möchten, können Sie die implementation of the signing logic in Python ausprobieren.

Jetzt, wenn Sie es selbst debuggen möchten, ist das Überprüfen der Fehlermeldung sehr nützlich. Es wird eine vollständige Kopie der Zeichenfolge enthalten, von der der Server die Signatur überprüft hat. Drucken Sie Ihr "string_to_sign" und sehen Sie, ob es mit dem Wert übereinstimmt, der vom Server zurückkommt. Wenn nicht, ist das dein Problem. Wenn dies der Fall ist, fahren Sie mit dem eigentlichen Signieren fort.

Mit Blick auf Ihren Code, meine Vermutung ist, dass das Problem möglicherweise, dass Sie URL-Escaping Ihre google_access_id sind, aber ich bin mir nicht sicher.

+0

Danke für Ihre Antwort. Ich habe versucht, die google_access_id zu umgehen, aber das hat es nicht gelöst. Ich habe versucht, die google-cloud-python-Bibliothek zu verwenden und die Frage entsprechend zu bearbeiten, aber von dem, was ich verstehe, nimmt die Funktion an, dass ich Dinge wie den Namen der Datei oder den Inhaltstyp kenne, bevor ich die URL an den Benutzer übergebe. Was kann ich dagegen tun? –

+1

OH. Sie versuchen, die URL auf der Clientseite zu bearbeiten, um einen Objektnamen anzufügen? Das wird nicht funktionieren.Der Hauptpunkt der signierten URLs besteht darin, Clients daran zu hindern, die Anfrage zu bearbeiten. Wenn Sie möchten, dass der Benutzer den Objektnamen angeben kann, gibt es dafür eine weitere Option: signierte POST-Anforderungsrichtliniendokumente. Siehe https://cloud.google.com/storage/docs/xml-api/post-object#policydocument –

+0

Ich verwende POST für diesen Fall, ich werde PUT für die anderen behalten. Vielen Dank für Ihre Antwort! –

1

Es scheint, als ob Sie den Objektnamen Teil der kanonischen Ressource fehlt. Dies ist erforderlich. Es ist nicht möglich, eine PUT-signierte URL zu erstellen, mit der der Benutzer einen Dateinamen auswählen kann. Sie müssen ihn beim Signieren der URL angeben.

Beachten Sie auch, dass der Hauptteil der Antwort für einen SignatureDoesNotMatch-Fehler einige XML mit dem StringToSign enthalten sollte - aka die Zeichenfolge, die der Server erwartet, dass Sie signieren. Sie können dies mit der Zeichenkette vergleichen, die Sie tatsächlich signiert haben, um festzustellen, woher die Abweichung kommt.

+0

Sie haben Recht, das war ein Teil des Problems. Die andere Sache war der Inhalt-Typ, ich musste es spezifizieren, damit es funktioniert, obwohl es optional sein sollte. Jedenfalls benutze ich jetzt die Client-Bibliothek, die sich darum kümmert. Vielen Dank für deine Antwort. –

+0

content-type ist optional, aber die PUT-Anfrage muss mit der Signatur übereinstimmen. Wenn Sie in der Signatur keinen Inhaltstyp angeben, darf die PUT-Anfrage auch nicht den Inhaltstyp enthalten. –

Verwandte Themen