2013-03-18 15 views
5

Sto utilizzando Java Amazon SDK per lavorare con S3 per l'archiviazione dei file caricati. Vorrei conservare il nome file originale e lo sto mettendo alla fine della chiave, ma sto anche usando la struttura delle directory virtuali - qualcosa come <dirname>/<uuid>/<originalFilename>.Amazon S3 Gli URL preselezionati sfuggono alle barre nella chiave

Il problema è che quando voglio generare un URL per scaricare presigned utilizzando l'API come:

URL url = s3Client.generatePresignedUrl(generatePresignedUrlRequest); 
return url.toExternalForm(); 

L'url SDK sfugge l'intera chiave, comprese le barre. Mentre funziona, significa che il nome del file scaricato include l'intera chiave invece del solo bit del nome file originale alla fine. So che dovrebbe essere possibile farlo senza sfuggire alle barre, ma sto cercando di evitare di riscrivere gran parte del codice già presente nell'SDK. C'è una soluzione comune a questo? So che ho usato app web che seguono lo stesso schema e non hanno il problema della fuga di slash.

+0

Se il bucket dispone di un ACL che consente l'accesso anonimo, è possibile recuperare un file seguendo il seguente schema: //s3.amazonaws.com/ /. E 'quello che stai cercando? –

+0

@JasonSperske È in un secchio privato. –

risposta

1

Sto ancora sperando in una soluzione migliore, ma visto che @aKzenT ha confermato la mia conclusione che non esiste una soluzione per questo ho scritto uno. È solo una semplice sottoclasse di AmazonS3Client. Mi preoccupo che sia fragile perché ho dovuto copiare un sacco di codice dal metodo su cui ho eseguito l'override, ma sembra la soluzione più minimale. Posso confermare che funziona bene nella mia propria base di codice. Ho inviato il codice in un gist, ma per il bene di una risposta completa:

import com.amazonaws.AmazonWebServiceRequest; 
import com.amazonaws.HttpMethod; 
import com.amazonaws.Request; 
import com.amazonaws.auth.AWSCredentials; 
import com.amazonaws.handlers.RequestHandler; 
import com.amazonaws.services.s3.AmazonS3Client; 
import com.amazonaws.services.s3.Headers; 
import com.amazonaws.services.s3.internal.S3QueryStringSigner; 
import com.amazonaws.services.s3.internal.ServiceUtils; 

import java.util.Date; 

/** 
* This class should be a drop in replacement for AmazonS3Client as long as you use the single credential 
* constructor. It could probably be modified to add additional constructors if needed, but this is the one we use. 
* Supporting all of them didn't seem trivial because of some dependencies in the original presignRequest method. 
* 
* The only real purpose of this class is to change the behavior of generating presigned URLs. The original version 
* escaped slashes in the key and this one does not. Pretty url paths are kept intact. 
* 
* @author Russell Leggett 
*/ 
public class PrettyUrlS3Client extends AmazonS3Client{ 
    private AWSCredentials awsCredentials; 

    /** 
    * This constructor is the only one provided because it is only one I needed, and it 
    * retains awsCredentials which might be needed in the presignRequest method 
    * 
    * @param awsCredentials 
    */ 
    public PrettyUrlS3Client(AWSCredentials awsCredentials) { 
     super(awsCredentials); 
     this.awsCredentials = awsCredentials; 
    } 

    /** 
    * WARNING: This method is an override of the AmazonS3Client presignRequest 
    * and copies most of the code. Should be careful of updates to the original. 
    * 
    * @param request 
    * @param methodName 
    * @param bucketName 
    * @param key 
    * @param expiration 
    * @param subResource 
    * @param <T> 
    */ 
    @Override 
    protected <T> void presignRequest(Request<T> request, HttpMethod methodName, String bucketName, String key, Date expiration, String subResource) { 

     // Run any additional request handlers if present 
     if (requestHandlers != null) { 
      for (RequestHandler requestHandler : requestHandlers) { 
       requestHandler.beforeRequest(request); 
      } 
     } 
     String resourcePath = "/" + 
       ((bucketName != null) ? bucketName + "/" : "") + 
       ((key != null) ? keyToEscapedPath(key)/* CHANGED: this is the primary change */ : "") + 
       ((subResource != null) ? "?" + subResource : ""); 

     //the request apparently needs the resource path without a starting '/' 
     request.setResourcePath(resourcePath.substring(1));//CHANGED: needed to match the signature with the URL generated from the request 
     AWSCredentials credentials = awsCredentials; 
     AmazonWebServiceRequest originalRequest = request.getOriginalRequest(); 
     if (originalRequest != null && originalRequest.getRequestCredentials() != null) { 
      credentials = originalRequest.getRequestCredentials(); 
     } 

     new S3QueryStringSigner<T>(methodName.toString(), resourcePath, expiration).sign(request, credentials); 

     // The Amazon S3 DevPay token header is a special exception and can be safely moved 
     // from the request's headers into the query string to ensure that it travels along 
     // with the pre-signed URL when it's sent back to Amazon S3. 
     if (request.getHeaders().containsKey(Headers.SECURITY_TOKEN)) { 
      String value = request.getHeaders().get(Headers.SECURITY_TOKEN); 
      request.addParameter(Headers.SECURITY_TOKEN, value); 
      request.getHeaders().remove(Headers.SECURITY_TOKEN); 
     } 
    } 

    /** 
    * A simple utility method which url escapes an S3 key, but leaves the 
    * slashes (/) unescaped so they can stay part of the url. 
    * @param key 
    * @return 
    */ 
    public static String keyToEscapedPath(String key){ 
     String[] keyParts = key.split("/"); 
     StringBuilder result = new StringBuilder(); 
     for(String part : keyParts){ 
      if(result.length()>0){ 
       result.append("/"); 
      } 
      result.append(ServiceUtils.urlEncode(part)); 
     } 
     return result.toString().replaceAll("%7E","~"); 
    } 
} 

UPDATE ho aggiornato il succo e questo codice per risolvere un problema che stavo avendo con ~ 's. Stava accadendo anche usando il client standard, ma senza scappare l'ho risolto. Vedi la sintesi per ulteriori dettagli/traccia eventuali ulteriori modifiche che potrei fare.

7

Si tratta di un bug nel corrente Java SDK:

Se si guarda alla https://github.com/aws/aws-sdk-java/blob/master/src/main/java/com/amazonaws/services/s3/AmazonS3Client.java#L2820

Il metodo presignRequest che è chiamato internamente ha il seguente codice:

String resourcePath = "/" + 
     ((bucketName != null) ? bucketName + "/" : "") + 
     ((key != null) ? ServiceUtils.urlEncode(key) : "") + 
     ((subResource != null) ? "?" + subResource : ""); 

La chiave è URL codificato qui prima di firmare quale penso sia l'errore.

Potrebbe essere possibile ereditare dallo AmazonS3Client e sovrascrivere la funzione per risolvere il problema.

In alcuni punti si consiglia di utilizzare url.getQuery() e aggiungere questo valore al proprio awsURL originale (https://forums.aws.amazon.com/thread.jspa?messageID=356271). Tuttavia, come hai detto tu stesso, questo produrrà un errore, perché la chiave della risorsa non corrisponderà alla firma.

Il seguente problema potrebbe anche essere correlato, non ho controllato la workarround proposto:

How to generate pre-signed Amazon S3 url for a vanity domain, using amazon sdk?

Amazon riconosciuto e corretto un bug simile prima: https://forums.aws.amazon.com/thread.jspa?messageID=418537

Così lo spero essere risolto nella prossima versione.

+0

Hai provato questo? Ho fatto qualcosa di simile e ho ottenuto una firma 403 non corrisponde. Lo stesso è stato descritto in quel post sul forum. "Sfortunatamente non è del tutto corretto dire che si può fare in entrambi i modi: va bene se si sta utilizzando l'URL generato dall'SDK Java in modo generico.Sfortunatamente se si consegna l'URL a un'app .NET. usa la sua classe WebRequest standard per consumare l'URL, .NET decodifica il% 2F su a/e la richiesta fallisce con un 403 - La firma non corrisponde. " –

+0

Come ho capito il tuo post funziona se usi manualmente gli slaes, ma non vuoi scrivere il codice per farlo, no? L'uso di url.getQuery dovrebbe fare esattamente questo. – aKzenT

+0

Non è possibile combinare. Quando viene chiamato generatePresignedUrl, prende l'intera chiave e la scappa, quindi crea una firma utilizzando la chiave di escape. Non è possibile utilizzare la firma dalla chiave di escape e combinarla con la chiave senza escape nell'URL. Ipoteticamente, se fosse possibile eseguire il codice della firma senza eseguire l'escape, è possibile utilizzare la chiave senza eseguire l'escape. Mi stavo chiedendo se qualcuno ha un modo semplice per farlo. –

1

versione 1.4.3 dell'SDK Java sembra aver risolto questo problema. Forse, è stato corretto in precedenza, ma posso confermare che funziona correttamente in 1.4.3.