2013-03-18 17 views
5

Używam Java Amazon SDK do pracy z S3 do przechowywania przesłanych plików. Chciałbym zachować oryginalną nazwę pliku i umieszczam ją na końcu klucza, ale używam również struktury katalogów wirtualnych - coś takiego jak <dirname>/<uuid>/<originalFilename>.Amazon S3 Presigned URLs uciekają z ukośników w kluczu

Problem polega na tym, że gdy chcę, aby wygenerować presigned URL do pobierania za pomocą interfejsu API jak:

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

SDK url ucieka cały klucz, łącznie z ukośniki. Chociaż nadal działa, oznacza to, że nazwa pobieranego pliku zawiera cały klucz, a nie tylko oryginalny bit nazwy pliku na końcu. Wiem, że powinno być możliwe to zrobić bez ucieczki z ukośników, ale staram się uniknąć przepisywania dużej ilości kodu już w SDK. Czy istnieje powszechne rozwiązanie tego problemu? Wiem, że korzystałem z aplikacji internetowych, które mają ten sam wzór i nie mają problemu z ucieczką.

+0

Jeśli w zasobniku znajduje się lista ACL zezwalająca na dostęp anonimowy, można pobrać plik według następującego wzoru: //s3.amazonaws.com/ /. Czy tego właśnie szukasz? –

+0

@JasonSperske Jest w prywatnym wiaderku. –

Odpowiedz

1

Nadal mam nadzieję na lepsze rozwiązanie niż to, ale widząc jako @aKzenT potwierdziła moja konkluzja, że ​​nie istnieje rozwiązanie dla tego, napisałem jeden. To tylko prosta podklasa AmazonS3Client. Obawiam się, że jest to krucha, ponieważ musiałem skopiować dużo kodu z metody, którą przejąłem, ale wydaje się to najbardziej minimalne rozwiązanie. Mogę potwierdzić, że działa dobrze w mojej własnej bazie kodu. Napisałem kod w gist, ale w trosce o pełną odpowiedź:

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 zaktualizowałem GIST i ten kod, aby rozwiązać problem miałem mający z ~ 's. Występował nawet przy użyciu standardowego klienta, ale cofnięcie go - naprawiło. Zobacz więcej szczegółów/śledzić wszelkie dalsze zmiany, które mogę wprowadzić. Wydaje się, że ten problem został rozwiązany przez

7

Jest to błąd w bieżącym Java SDK:

Jeśli spojrzeć na https://github.com/aws/aws-sdk-java/blob/master/src/main/java/com/amazonaws/services/s3/AmazonS3Client.java#L2820

Sposób presignRequest który nazywa się wewnętrznie ma następujący kod:

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

Kluczem jest URL kodowane tutaj przed podpisaniem, co moim zdaniem jest błędem.

Możesz być w stanie odziedziczyć po AmazonS3Client i zastąpić funkcję, aby to naprawić.

W niektórych miejscach sugeruje się użycie url.getQuery() i poprzedzanie tego oryginalnym awsurl (https://forums.aws.amazon.com/thread.jspa?messageID=356271). Jednak, jak sam powiedziałeś, spowoduje to błąd, ponieważ klucz zasobów nie będzie zgodny z podpisem.

Poniższy problem może być również związane, nie sprawdzić proponowaną workarround:

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

Amazon rozpoznawane i stałe podobnego błędu przed: https://forums.aws.amazon.com/thread.jspa?messageID=418537

Więc mam nadzieję, że będzie naprawić w następnej wersji.

+0

Czy próbowałeś tego? Zrobiłem coś podobnego i podpis 403 się nie zgadza. To samo zostało opisane w tym poście na forum. "Niestety nie można powiedzieć, że można to zrobić w obie strony: dobrze, jeśli używasz adresu URL generowanego przez pakiet Java SDK w sposób ogólny. Niestety, jeśli podasz adres URL aplikacji .NET i używa standardowej klasy WebRequest do spożywania adresu URL, .NET dekoduje% 2F do a/i żądanie kończy się niepowodzeniem z 403 - Podpis nie pasuje. " –

+0

Jak zrozumiałem twój post, działa on, jeśli ręcznie używasz urządzeń slave, ale nie chcesz pisać kodu, aby to zrobić, nie? Użycie url.getQuery powinno zrobić dokładnie to. – aKzenT

+0

Nie można łączyć ze sobą. Po wywołaniu generatePresignedUrl pobiera cały klucz i wymyka się z niego, a następnie tworzy podpis za pomocą klawisza Escaped. Nie można użyć podpisu z kluczem Escaped i połączyć go z kluczem bez zmiany znaczenia w adresie URL. Hipotetycznie, gdybyś mógł wykonać kod podpisu bez ucieczki, mógłbyś użyć klucza bez ucieczki. Właśnie zastanawiałem się, czy ktoś ma łatwy sposób robienia tego. –

1

wersja 1.4.3 pakietu Java SDK. Być może zostało to naprawione wcześniej, ale mogę potwierdzić, że działa poprawnie w 1.4.3.