5

アップロードされたファイルを保存するために、Java Amazon SDK を使用して S3 と連携しています。元のファイル名を保持したいので、キーの最後に置きますが、仮想ディレクトリ構造も使用しています- <dirname>/<uuid>/<originalFilename>.

問題は、次のような API を使用してダウンロードするための署名付き URL を生成したい場合です。

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

SDK の URL は、スラッシュを含むキー全体をエスケープします。それでも機能しますが、ダウンロードされたファイルの名前には、末尾の元のファイル名のビットだけではなく、キー全体が含まれることを意味します。スラッシュをエスケープせずにこれを行うことが可能であることはわかっていますが、SDK に既に含まれている多くのコードを書き直さないようにしています。これに対する一般的な解決策はありますか?同じパターンに従い、スラッシュ エスケープの問題がない Web アプリを使用したことがあります。

4

3 に答える 3

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 ) を使用してプレフィックスを付けることをお勧めします。ただし、リソースキーが署名と一致しないため、これはエラーになります。

次の問題も関連している可能性があります。提案された回避策をチェックアウトしませんでした。

Amazon sdk を使用して、バニティ ドメインの事前署名済み Amazon S3 URL を生成する方法は?

Amazon は以前に同様のバグを認識して修正しました: https://forums.aws.amazon.com/thread.jspa?messageID=418537

なので、次のバージョンで修正されることを願っています。

于 2013-03-21T17:03:13.820 に答える
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","~");
    }
}

更新~ で発生していた問題を修正するために要点とこのコードを更新しました。標準クライアントでも発生していましたが、~ をアンエスケープすると直りました。詳細については要点を参照し、さらに変更を加える可能性がある場合は追跡してください。

于 2013-03-22T13:32:20.567 に答える
1

Java SDK のバージョン 1.4.3 では、この問題が修正されたようです。先ほど修正されたのかもしれませんが、1.4.3では正常に動作することが確認できました。

于 2013-05-07T14:46:49.050 に答える