2013-03-18 13 views
5

私はJava Amazon SDKを使用して、アップロードされたファイルを格納するS3を操作しています。私は元のファイル名を保持したい、私はキーの最後に入れているが、私はまた、仮想ディレクトリ構造を使用しています - <dirname>/<uuid>/<originalFilename>のようなもの。Amazon S3予約済みURLがキーのスラッシュをエスケープ

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

SDKのURLがスラッシュを含む全体のキーを、エスケープ:

の問題は、私のようなAPIを使用してダウンロードするためのpresigned URLを生成したいときということです。それでも動作しますが、ダウンロードされるファイルの名前には、最後に元のファイル名ビットの代わりにキー全体が含まれることを意味します。スラッシュをエスケープせずにこれを行うことが可能であるはずですが、私は既にSDKのコードの多くを書き直すことを避けようとしています。これに共通の解決策はありますか?私は、同じパターンに従ったスラッシュエスケープ問題を持たないWebアプリケーションを使用したことを知っています。

+0

バケットに匿名アクセスを許可するACLがある場合、次のパターンでファイルを取得できます。//s3.amazonaws.com/ /<フルキー名とスラッシュ>それはあなたが探しているものですか? –

+0

@JasonSperskeこれはプライベートバケットにあります。 –

答えて

1

私はまだこれよりも良い解決策を望んでいますが、@ aKzenTとして見て、私はこのための既存の解決策がないという結論を確認しました。 AmazonS3Clientの単純なサブクラスです。私は過度の方法から多くのコードをコピーしなければならなかったので、それは脆いと心配しますが、それは最も簡単な解決策のようです。自分のコードベースで正常に動作することを確認できます。私はgistでコードを掲載、しかし完全な答えのために:

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私が要点と私は〜さんに持っていた問題を修正するには、このコードを更新しました。標準のクライアントを使用していても発生していましたが、〜を固定解除しています。詳細については、gistを参照してください。それ以上の変更を追跡してください。

7

これは、現在のJava SDKのバグです:

あなたはhttps://github.com/aws/aws-sdk-java/blob/master/src/main/java/com/amazonaws/services/s3/AmazonS3Client.java#L2820

を見れば、内部的に呼び出されるメソッドpresignRequestは、次のコードを持っています

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

キーはURLです私はエラーと思われる署名前にここにエンコードされています。

AmazonS3Clientから継承し、これを修正する機能を無効にすることができます。

url.getQuery()を使用し、元のawsURL(https://forums.aws.amazon.com/thread.jspa?messageID=356271)の接頭辞を付けることをお勧めします。しかし、リソースキーが署名と一致しないため、これがエラーになります。

次のような問題にも関連するかもしれない、私が提案したworkarroundをチェックアウトしませんでした。だから私はそれを願っています https://forums.aws.amazon.com/thread.jspa?messageID=418537

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

Amazonは前に同様のバグを認識し、固定しました次のバージョンで修正されます。

+0

これは試しましたか?私は何か似たようなことをして、403の署名が一致しない。同じことがフォーラムのポストで説明されました。 "残念ながら、両方の方法で対応できるとは言いませんが、Java SDKで生成されたURLを一般的な方法で使用していれば問題ありません。残念ながら、そのURLを.NETアプリに渡してURLを消費するために標準のWebRequestクラスを使用し、.NETは%2Fを/にデコードし、要求は403 - Signatureが一致しないと失敗します。 –

+0

私は自分の投稿を理解しているので、手動でスラッシュを使用しても機能しますが、これを行うためのコードを書く必要はありません。 url.getQueryを使用すると正確にこれを行う必要があります。 – aKzenT

+0

混在させることはできません。 generatePresignedUrlが呼び出されると、キー全体を取り出してエスケープし、エスケープされたキーを使用してシグネチャを作成します。エスケープされたキーのシグネチャは使用できず、URLのエスケープされていないキーと組み合わせることはできません。仮に、エスケープせずに署名コードを実行できる場合は、エスケープせずにキーを使用することができます。誰かがこれをやる簡単な方法を持っているのかどうか疑問に思っていただけです。 –

1

Java SDKのバージョン1.4.3ではこの問題が修正されているようです。おそらく、以前は修正されていましたが、1.4.3で正しく動作していることを確認できます。

関連する問題