通过 AWS-Java SDK 为浏览器签署 S3 上传请求

Sign S3 upload requests for the browser via the AWS-Java SDK

我有一个使用 EmberJS 构建的前端客户端,特别是 ember-uploader 用于处理将文件直接上传到 S3。我被卡住的地方是,在发送到亚马逊之前,我似乎无法使用我的后端服务器(java Dropwizard 微服务)正确签署请求。

我知道我可以创建一个 GeneratePresignedUrlRequest 但我正在使用的前端库特别需要从服务器返回一个 json 对象,所以我试图拆分那个 GeneratePresignedUrlRequest 变成一个对象。

目前一切似乎都很好,但我缺少 policy,因为我无法锻炼如何正确创建它。

private SignRequestObject createSignRequestObject(List<NameValuePair> valuePairs) {
    SignRequestObject request = new SignRequestObject();

    request.setKey("test.txt");
    request.setBucket("test-bucket");
    request.setPolicy("?");

    for (NameValuePair pairs : valuePairs) {
        if (pairs.getName().equals("X-Amz-Credential")) {
            request.setCredentials(pairs.getValue());
        }

        if (pairs.getName().equals("X-Amz-Signature")) {
            request.setSignature(pairs.getValue());
        }

        if (pairs.getName().equals("X-Amz-Algorithm")) {
            request.setAlgorithm(pairs.getValue());
        }

        if (pairs.getName().equals("X-Amz-Date")) {
            request.setDate(pairs.getValue());
        }
    }

    return request;
}

值对来自 GeneratePresignedUrlRequest

private String createSignedUrl() {
    GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest("test-bucket", "test.txt");
    generatePresignedUrlRequest.setMethod(HttpMethod.PUT);

    return amazonS3.generatePresignedUrl(generatePresignedUrlRequest).toString();
}

根据 ember-uploader 的 wiki,我希望策略对象看起来像这样:

// Ruby example, but shouldn't matter
        {
          expiration: @expires,
          conditions: [
            { bucket: 'sandbox' },
            { acl: 'public-read' },
            { expires: @expires },
            { success_action_status: '201' },
            [ 'starts-with', '$key', '' ],
            [ 'starts-with', '$Content-Type', '' ],
            [ 'starts-with', '$Cache-Control', '' ],
            [ 'content-length-range', 0, 524288000 ]
          ]
      )

我应该尝试自己构建它还是 aws-sdk 有这方面的方法?我一直看到 AWS Signature Version 4,但也不知道如何使用它。

当尝试通过浏览器上传时,我从亚马逊收到了 403。

我解决了这个问题并为它写了一个小的 guice 模块。然后根据对后端的获取请求从存储库 class 调用它。

// 资源

public class SignResource {

    private final SignRepository repository;

    @Inject
    public SignResource(SignRepository repository) {
        this.repository = repository;
    }

    @GET
    public Response signPOST(@QueryParam("type") String type) {
        String signRequest = repository.signRequest(type);
        return Response.status(Response.Status.OK).entity(signRequest).build();
    }
}

// 存储库

public class SignRepository {

    @Inject
    private SignService signService;

    public SignRepository() {
    }

    public String signRequest(String contentType) {
        return signService.signRequest(contentType);
    }
}

// 实施

public class SignServiceImpl implements SignService {

    private String awsBucket;
    private String awsAccessKey;
    private String awsSecretKey;

    SignServiceImpl(AmazonConfiguration amazon) {
        awsSecretKey = amazon.getSecret();
        awsAccessKey = amazon.getAccess();
        awsBucket = amazon.getBucket();
    }

    @Override
    public String signRequest(String contentType) {
        final String randomFileName = createRandomName();

        String policy = createPolicy(randomFileName, contentType);

        SignRequest signRequest = new SignRequest();
        signRequest.setAwsAccessKeyId(awsAccessKey);
        signRequest.setPolicy(policy);
        signRequest.setSignature(ServiceUtils.signWithHmacSha1(awsSecretKey, policy));
        signRequest.setBucket(awsBucket);
        signRequest.setKey(randomFileName);
        signRequest.setAcl("public-read");
        signRequest.setContentType(contentType);
        signRequest.setExpires(createExpireTime().toString());
        signRequest.setSuccessActionStatus("201");

        return createJsonString(signRequest);
    }

    private String createPolicy(String randomFileName, String contentType) {
        try {
            String[] conditions = {
                S3Service.generatePostPolicyCondition_Equality("bucket", awsBucket),
                S3Service.generatePostPolicyCondition_Equality("key", randomFileName),
                S3Service.generatePostPolicyCondition_Equality("acl", "public-read"),
                S3Service.generatePostPolicyCondition_Equality("expires", createExpireTime().toString()),
                S3Service.generatePostPolicyCondition_Equality("content-Type", contentType),
                S3Service.generatePostPolicyCondition_Equality("success_action_status", "201"),
                S3Service.generatePostPolicyCondition_AllowAnyValue("cache-control")
            };

            String policyDocument = "{\"expiration\": \"" + ServiceUtils.formatIso8601Date(createExpireTime()) + "\", \"conditions\": [" + ServiceUtils.join(conditions, ",") + "]}";

            return ServiceUtils.toBase64(policyDocument.getBytes(Constants.DEFAULT_ENCODING));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return null;
    }

    private String createRandomName() {
        return UUID.randomUUID().toString();
    }

    private Date createExpireTime() {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.HOUR, 24);

        return cal.getTime();
    }

    private String createJsonString(SignRequest request) {
        ObjectMapper mapper = new ObjectMapper();
        String json = null;

        try {
            json = mapper.writeValueAsString(request);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        return json;
    }
}

// 服务

public interface SignService {
    String signRequest(String contentType);
}

// 模块

public class SignServiceModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(SignService.class).toProvider(SignServiceProvider.class).asEagerSingleton();
    }
}

// 提供商

public class SignServiceProvider implements Provider<SignService> {

    @Inject
    private SwordfishConfiguration configuration;

    @Override
    public SignService get() {
        return new SignServiceImpl(configuration.getAmazon());
    }
}