mirror of
https://gitee.com/lxp135/minio-plus.git
synced 2025-12-06 17:08:26 +08:00
解决 minio 桶中的文件的内容类型 Content-Type 不能正确保存的问题。
This commit is contained in:
parent
e016db9936
commit
73fff94968
@ -802,6 +802,8 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
||||
fileKey = IdUtil.fastSimpleUUID();
|
||||
// 存储路径
|
||||
storagePath = CommonUtil.getPathByDate();
|
||||
// MIME类型
|
||||
String fileMimeType = FileUtil.getMimeType(bo.getFullFileName());
|
||||
|
||||
// 存储桶
|
||||
bucketName = StorageBucketEnums.getBucketByFileSuffix(suffix);
|
||||
@ -809,7 +811,7 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
||||
minioS3Client.makeBucket(bucketName);
|
||||
|
||||
// 创建分片请求,获取uploadId
|
||||
uploadId = minioS3Client.createMultipartUpload(bucketName, CommonUtil.getObjectName(bo.getFileMd5()));
|
||||
uploadId = minioS3Client.createMultipartUpload(bucketName, CommonUtil.getObjectName(bo.getFileMd5()),fileMimeType);
|
||||
long start = 0;
|
||||
for (Integer partNumber = 1; partNumber <= chunkNum; partNumber++) {
|
||||
FileCheckResultVo.Part part = this.buildResultPart(bucketName, CommonUtil.getObjectName(bo.getFileMd5()), uploadId, bo.getFileSize(), start, partNumber);
|
||||
|
||||
@ -29,9 +29,10 @@ public interface MinioS3Client {
|
||||
* 创建上传任务
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称(含路径)
|
||||
* @param contentType 内容类型
|
||||
* @return UploadId 上传任务编号
|
||||
*/
|
||||
String createMultipartUpload(String bucketName, String objectName);
|
||||
String createMultipartUpload(String bucketName, String objectName,String contentType);
|
||||
|
||||
/**
|
||||
* 合并分片
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package org.liuxp.minioplus.s3.official;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.minio.*;
|
||||
import io.minio.errors.InsufficientDataException;
|
||||
import io.minio.errors.InternalException;
|
||||
@ -107,9 +110,15 @@ public class MinioS3ClientImpl implements MinioS3Client {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createMultipartUpload(String bucketName, String objectName) {
|
||||
public String createMultipartUpload(String bucketName, String objectName, String contentType) {
|
||||
|
||||
Multimap<String, String> reqParams = HashMultimap.create();
|
||||
if(CharSequenceUtil.isNotBlank(contentType)){
|
||||
reqParams.put("Content-Type", contentType);
|
||||
}
|
||||
|
||||
try {
|
||||
CreateMultipartUploadResponse createMultipartUploadResponse = this.getClient().createMultipartUpload(bucketName, null, objectName, null, null);
|
||||
CreateMultipartUploadResponse createMultipartUploadResponse = this.getClient().createMultipartUpload(bucketName, null, objectName, reqParams,null );
|
||||
return createMultipartUploadResponse.result().uploadId();
|
||||
} catch (Exception e) {
|
||||
log.error(LOG_TEMPLATE, MinioPlusErrorCode.CREATE_MULTIPART_UPLOAD_FAILED.getMessage(), e.getMessage(), e);
|
||||
|
||||
@ -1,154 +0,0 @@
|
||||
/*
|
||||
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.Locale;
|
||||
|
||||
/** Various global static functions used. */
|
||||
public class Digest {
|
||||
// MD5 hash of zero length byte array.
|
||||
public static final String ZERO_MD5_HASH = "1B2M2Y8AsgTpgAmY7PhCfg==";
|
||||
// SHA-256 hash of zero length byte array.
|
||||
public static final String ZERO_SHA256_HASH =
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
||||
|
||||
/** Private constructor. */
|
||||
private Digest() {}
|
||||
|
||||
/** Returns MD5 hash of byte array. */
|
||||
public static String md5Hash(byte[] data, int length) throws NoSuchAlgorithmException {
|
||||
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
|
||||
md5Digest.update(data, 0, length);
|
||||
return Base64.getEncoder().encodeToString(md5Digest.digest());
|
||||
}
|
||||
|
||||
/** Returns SHA-256 hash of byte array. */
|
||||
public static String sha256Hash(byte[] data, int length) throws NoSuchAlgorithmException {
|
||||
MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");
|
||||
sha256Digest.update((byte[]) data, 0, length);
|
||||
return BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
/** Returns SHA-256 hash of given string. */
|
||||
public static String sha256Hash(String string) throws NoSuchAlgorithmException {
|
||||
byte[] data = string.getBytes(StandardCharsets.UTF_8);
|
||||
return sha256Hash(data, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns SHA-256 and MD5 hashes of given data and it's length.
|
||||
*
|
||||
* @param data must be {@link RandomAccessFile}, {@link BufferedInputStream} or byte array.
|
||||
* @param len length of data to be read for hash calculation.
|
||||
* @deprecated This method is no longer supported.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String[] sha256Md5Hashes(Object data, int len)
|
||||
throws NoSuchAlgorithmException, IOException, RuntimeException {
|
||||
MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");
|
||||
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
|
||||
|
||||
if (data instanceof BufferedInputStream || data instanceof RandomAccessFile) {
|
||||
updateDigests(data, len, sha256Digest, md5Digest);
|
||||
} else if (data instanceof byte[]) {
|
||||
sha256Digest.update((byte[]) data, 0, len);
|
||||
md5Digest.update((byte[]) data, 0, len);
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Unknown data source to calculate SHA-256 hash. This should not happen, "
|
||||
+ "please report this issue at https://github.com/minio/minio-java/issues",
|
||||
null);
|
||||
}
|
||||
|
||||
return new String[] {
|
||||
BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(Locale.US),
|
||||
BaseEncoding.base64().encode(md5Digest.digest())
|
||||
};
|
||||
}
|
||||
|
||||
/** Updated MessageDigest with bytes read from file and stream. */
|
||||
private static int updateDigests(
|
||||
Object inputStream, int len, MessageDigest sha256Digest, MessageDigest md5Digest)
|
||||
throws IOException, RuntimeException {
|
||||
RandomAccessFile file = null;
|
||||
BufferedInputStream stream = null;
|
||||
if (inputStream instanceof RandomAccessFile) {
|
||||
file = (RandomAccessFile) inputStream;
|
||||
} else if (inputStream instanceof BufferedInputStream) {
|
||||
stream = (BufferedInputStream) inputStream;
|
||||
}
|
||||
|
||||
// hold current position of file/stream to reset back to this position.
|
||||
long pos = 0;
|
||||
if (file != null) {
|
||||
pos = file.getFilePointer();
|
||||
} else {
|
||||
stream.mark(len);
|
||||
}
|
||||
|
||||
// 16KiB buffer for optimization
|
||||
byte[] buf = new byte[16384];
|
||||
int bytesToRead = buf.length;
|
||||
int bytesRead = 0;
|
||||
int totalBytesRead = 0;
|
||||
while (totalBytesRead < len) {
|
||||
if ((len - totalBytesRead) < bytesToRead) {
|
||||
bytesToRead = len - totalBytesRead;
|
||||
}
|
||||
|
||||
if (file != null) {
|
||||
bytesRead = file.read(buf, 0, bytesToRead);
|
||||
} else {
|
||||
bytesRead = stream.read(buf, 0, bytesToRead);
|
||||
}
|
||||
|
||||
if (bytesRead < 0) {
|
||||
// reached EOF
|
||||
throw new RuntimeException(
|
||||
"Insufficient data. bytes read " + totalBytesRead + " expected " + len);
|
||||
}
|
||||
|
||||
if (bytesRead > 0) {
|
||||
if (sha256Digest != null) {
|
||||
sha256Digest.update(buf, 0, bytesRead);
|
||||
}
|
||||
|
||||
if (md5Digest != null) {
|
||||
md5Digest.update(buf, 0, bytesRead);
|
||||
}
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
// reset back to saved position.
|
||||
if (file != null) {
|
||||
file.seek(pos);
|
||||
} else {
|
||||
stream.reset();
|
||||
}
|
||||
|
||||
return totalBytesRead;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,70 +0,0 @@
|
||||
/*
|
||||
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2016 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import com.google.common.escape.Escaper;
|
||||
import com.google.common.net.UrlEscapers;
|
||||
|
||||
public class S3Escaper {
|
||||
private static final Escaper ESCAPER = UrlEscapers.urlPathSegmentEscaper();
|
||||
|
||||
/** Returns S3 encoded string. */
|
||||
public static String encode(String str) {
|
||||
if (str == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return ESCAPER
|
||||
.escape(str)
|
||||
.replaceAll("\\!", "%21")
|
||||
.replaceAll("\\$", "%24")
|
||||
.replaceAll("\\&", "%26")
|
||||
.replaceAll("\\'", "%27")
|
||||
.replaceAll("\\(", "%28")
|
||||
.replaceAll("\\)", "%29")
|
||||
.replaceAll("\\*", "%2A")
|
||||
.replaceAll("\\+", "%2B")
|
||||
.replaceAll("\\,", "%2C")
|
||||
.replaceAll("\\/", "%2F")
|
||||
.replaceAll("\\:", "%3A")
|
||||
.replaceAll("\\;", "%3B")
|
||||
.replaceAll("\\=", "%3D")
|
||||
.replaceAll("\\@", "%40")
|
||||
.replaceAll("\\[", "%5B")
|
||||
.replaceAll("\\]", "%5D");
|
||||
}
|
||||
|
||||
/** Returns S3 encoded string of given path where multiple '/' are trimmed. */
|
||||
public static String encodePath(String path) {
|
||||
final StringBuilder encodedPath = new StringBuilder();
|
||||
for (String pathSegment : path.split("/")) {
|
||||
if (!pathSegment.isEmpty()) {
|
||||
if (encodedPath.length() > 0) {
|
||||
encodedPath.append("/");
|
||||
}
|
||||
encodedPath.append(S3Escaper.encode(pathSegment));
|
||||
}
|
||||
}
|
||||
|
||||
if (path.startsWith("/")) {
|
||||
encodedPath.insert(0, "/");
|
||||
}
|
||||
if (path.endsWith("/")) {
|
||||
encodedPath.append("/");
|
||||
}
|
||||
|
||||
return encodedPath.toString();
|
||||
}
|
||||
}
|
||||
@ -1,420 +0,0 @@
|
||||
/*
|
||||
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Amazon AWS S3 signature V4 signer. */
|
||||
public class Signer {
|
||||
//
|
||||
// Excerpts from @lsegal - https://github.com/aws/aws-sdk-js/issues/659#issuecomment-120477258
|
||||
//
|
||||
// * User-Agent
|
||||
// This is ignored from signing because signing this causes problems with generating pre-signed
|
||||
// URLs (that are executed by other agents) or when customers pass requests through proxies, which
|
||||
// may modify the user-agent.
|
||||
//
|
||||
// * Content-Length
|
||||
// This is ignored from signing because generating a pre-signed URL should not provide a
|
||||
// content-length constraint, specifically when vending a S3 pre-signed PUT URL. The corollary to
|
||||
// this is that when sending regular requests (non-pre-signed), the signature contains a checksum
|
||||
// of the body, which implicitly validates the payload length (since changing the number of bytes
|
||||
// would change the checksum) and therefore this header is not valuable in the signature.
|
||||
//
|
||||
// * Content-Type
|
||||
// Signing this header causes quite a number of problems in browser environments, where browsers
|
||||
// like to modify and normalize the content-type header in different ways. There is more
|
||||
// information on this in https://github.com/aws/aws-sdk-js/issues/244. Avoiding this field
|
||||
// simplifies logic and reduces the possibility of future bugs.
|
||||
//
|
||||
// * Authorization
|
||||
// Is skipped for obvious reasons.
|
||||
//
|
||||
// * Accept-Encoding
|
||||
// Some S3 servers like Hitachi Content Platform do not honour this header for signature
|
||||
// calculation.
|
||||
//
|
||||
private static final Set<String> IGNORED_HEADERS =
|
||||
ImmutableSet.of(
|
||||
"accept-encoding", "authorization", "content-type", "content-length", "user-agent");
|
||||
private static final Set<String> PRESIGN_IGNORED_HEADERS =
|
||||
ImmutableSet.of(
|
||||
"accept-encoding",
|
||||
"authorization",
|
||||
"content-type",
|
||||
"content-length",
|
||||
"user-agent",
|
||||
"content-md5",
|
||||
"x-amz-content-sha256",
|
||||
"x-amz-date",
|
||||
"x-amz-security-token");
|
||||
|
||||
private Request request;
|
||||
private String contentSha256;
|
||||
private ZonedDateTime date;
|
||||
private String region;
|
||||
private String accessKey;
|
||||
private String secretKey;
|
||||
private String prevSignature;
|
||||
|
||||
private String scope;
|
||||
private Map<String, String> canonicalHeaders;
|
||||
private String signedHeaders;
|
||||
private HttpUrl url;
|
||||
private String canonicalQueryString;
|
||||
private String canonicalRequest;
|
||||
private String canonicalRequestHash;
|
||||
private String stringToSign;
|
||||
private byte[] signingKey;
|
||||
private String signature;
|
||||
private String authorization;
|
||||
|
||||
/**
|
||||
* Create new Signer object for V4.
|
||||
*
|
||||
* @param request HTTP Request object.
|
||||
* @param contentSha256 SHA-256 hash of request payload.
|
||||
* @param date Date to be used to sign the request.
|
||||
* @param region Amazon AWS region for the request.
|
||||
* @param accessKey Access Key string.
|
||||
* @param secretKey Secret Key string.
|
||||
* @param prevSignature Previous signature of chunk upload.
|
||||
*/
|
||||
private Signer(
|
||||
Request request,
|
||||
String contentSha256,
|
||||
ZonedDateTime date,
|
||||
String region,
|
||||
String accessKey,
|
||||
String secretKey,
|
||||
String prevSignature) {
|
||||
this.request = request;
|
||||
this.contentSha256 = contentSha256;
|
||||
this.date = date;
|
||||
this.region = region;
|
||||
this.accessKey = accessKey;
|
||||
this.secretKey = secretKey;
|
||||
this.prevSignature = prevSignature;
|
||||
}
|
||||
|
||||
private void setScope(String serviceName) {
|
||||
this.scope =
|
||||
this.date.format(Time.SIGNER_DATE_FORMAT)
|
||||
+ "/"
|
||||
+ this.region
|
||||
+ "/"
|
||||
+ serviceName
|
||||
+ "/aws4_request";
|
||||
}
|
||||
|
||||
private void setCanonicalHeaders(Set<String> ignored_headers) {
|
||||
this.canonicalHeaders = new TreeMap<>();
|
||||
|
||||
Headers headers = this.request.headers();
|
||||
for (String name : headers.names()) {
|
||||
String signedHeader = name.toLowerCase(Locale.US);
|
||||
if (!ignored_headers.contains(signedHeader)) {
|
||||
// Convert and add header values as per
|
||||
// https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
||||
// * Header having multiple values should be converted to comma separated values.
|
||||
// * Multi-spaced value of header should be trimmed to single spaced value.
|
||||
this.canonicalHeaders.put(
|
||||
signedHeader,
|
||||
headers.values(name).stream()
|
||||
.map(
|
||||
value -> {
|
||||
return value.replaceAll("( +)", " ");
|
||||
})
|
||||
.collect(Collectors.joining(",")));
|
||||
}
|
||||
}
|
||||
|
||||
this.signedHeaders = Joiner.on(";").join(this.canonicalHeaders.keySet());
|
||||
}
|
||||
|
||||
private void setCanonicalQueryString() {
|
||||
String encodedQuery = this.url.encodedQuery();
|
||||
if (encodedQuery == null) {
|
||||
this.canonicalQueryString = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Building a multimap which only order keys, ordering values is not performed
|
||||
// until MinIO server supports it.
|
||||
Multimap<String, String> signedQueryParams =
|
||||
MultimapBuilder.treeKeys().arrayListValues().build();
|
||||
|
||||
for (String queryParam : encodedQuery.split("&")) {
|
||||
String[] tokens = queryParam.split("=");
|
||||
if (tokens.length > 1) {
|
||||
signedQueryParams.put(tokens[0], tokens[1]);
|
||||
} else {
|
||||
signedQueryParams.put(tokens[0], "");
|
||||
}
|
||||
}
|
||||
|
||||
this.canonicalQueryString =
|
||||
Joiner.on("&").withKeyValueSeparator("=").join(signedQueryParams.entries());
|
||||
}
|
||||
|
||||
private void setCanonicalRequest() throws NoSuchAlgorithmException {
|
||||
setCanonicalHeaders(IGNORED_HEADERS);
|
||||
this.url = this.request.url();
|
||||
setCanonicalQueryString();
|
||||
|
||||
// CanonicalRequest =
|
||||
// HTTPRequestMethod + '\n' +
|
||||
// CanonicalURI + '\n' +
|
||||
// CanonicalQueryString + '\n' +
|
||||
// CanonicalHeaders + '\n' +
|
||||
// SignedHeaders + '\n' +
|
||||
// HexEncode(Hash(RequestPayload))
|
||||
this.canonicalRequest =
|
||||
this.request.method()
|
||||
+ "\n"
|
||||
+ this.url.encodedPath()
|
||||
+ "\n"
|
||||
+ this.canonicalQueryString
|
||||
+ "\n"
|
||||
+ Joiner.on("\n").withKeyValueSeparator(":").join(this.canonicalHeaders)
|
||||
+ "\n\n"
|
||||
+ this.signedHeaders
|
||||
+ "\n"
|
||||
+ this.contentSha256;
|
||||
|
||||
this.canonicalRequestHash = Digest.sha256Hash(this.canonicalRequest);
|
||||
}
|
||||
|
||||
private void setStringToSign() {
|
||||
this.stringToSign =
|
||||
"AWS4-HMAC-SHA256"
|
||||
+ "\n"
|
||||
+ this.date.format(Time.AMZ_DATE_FORMAT)
|
||||
+ "\n"
|
||||
+ this.scope
|
||||
+ "\n"
|
||||
+ this.canonicalRequestHash;
|
||||
}
|
||||
|
||||
private void setChunkStringToSign() throws NoSuchAlgorithmException {
|
||||
this.stringToSign =
|
||||
"AWS4-HMAC-SHA256-PAYLOAD"
|
||||
+ "\n"
|
||||
+ this.date.format(Time.AMZ_DATE_FORMAT)
|
||||
+ "\n"
|
||||
+ this.scope
|
||||
+ "\n"
|
||||
+ this.prevSignature
|
||||
+ "\n"
|
||||
+ Digest.sha256Hash("")
|
||||
+ "\n"
|
||||
+ this.contentSha256;
|
||||
}
|
||||
|
||||
private void setSigningKey(String serviceName)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
String aws4SecretKey = "AWS4" + this.secretKey;
|
||||
|
||||
byte[] dateKey =
|
||||
sumHmac(
|
||||
aws4SecretKey.getBytes(StandardCharsets.UTF_8),
|
||||
this.date.format(Time.SIGNER_DATE_FORMAT).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
byte[] dateRegionKey = sumHmac(dateKey, this.region.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
byte[] dateRegionServiceKey =
|
||||
sumHmac(dateRegionKey, serviceName.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
this.signingKey =
|
||||
sumHmac(dateRegionServiceKey, "aws4_request".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private void setSignature() throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
byte[] digest = sumHmac(this.signingKey, this.stringToSign.getBytes(StandardCharsets.UTF_8));
|
||||
this.signature = BaseEncoding.base16().encode(digest).toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
private void setAuthorization() {
|
||||
this.authorization =
|
||||
"AWS4-HMAC-SHA256 Credential="
|
||||
+ this.accessKey
|
||||
+ "/"
|
||||
+ this.scope
|
||||
+ ", SignedHeaders="
|
||||
+ this.signedHeaders
|
||||
+ ", Signature="
|
||||
+ this.signature;
|
||||
}
|
||||
|
||||
/** Returns chunk signature calculated using given arguments. */
|
||||
public static String getChunkSignature(
|
||||
String chunkSha256, ZonedDateTime date, String region, String secretKey, String prevSignature)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Signer signer = new Signer(null, chunkSha256, date, region, null, secretKey, prevSignature);
|
||||
signer.setScope("s3");
|
||||
signer.setChunkStringToSign();
|
||||
signer.setSigningKey("s3");
|
||||
signer.setSignature();
|
||||
|
||||
return signer.signature;
|
||||
}
|
||||
|
||||
/** Returns signed request object for given request, region, access key and secret key. */
|
||||
private static Request signV4(
|
||||
String serviceName,
|
||||
Request request,
|
||||
String region,
|
||||
String accessKey,
|
||||
String secretKey,
|
||||
String contentSha256)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
ZonedDateTime date = ZonedDateTime.parse(request.header("x-amz-date"), Time.AMZ_DATE_FORMAT);
|
||||
|
||||
Signer signer = new Signer(request, contentSha256, date, region, accessKey, secretKey, null);
|
||||
signer.setScope(serviceName);
|
||||
signer.setCanonicalRequest();
|
||||
signer.setStringToSign();
|
||||
signer.setSigningKey(serviceName);
|
||||
signer.setSignature();
|
||||
signer.setAuthorization();
|
||||
|
||||
return request.newBuilder().header("Authorization", signer.authorization).build();
|
||||
}
|
||||
|
||||
/** Returns signed request of given request for S3 service. */
|
||||
public static Request signV4S3(
|
||||
Request request, String region, String accessKey, String secretKey, String contentSha256)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
return signV4("s3", request, region, accessKey, secretKey, contentSha256);
|
||||
}
|
||||
|
||||
/** Returns signed request of given request for STS service. */
|
||||
public static Request signV4Sts(
|
||||
Request request, String region, String accessKey, String secretKey, String contentSha256)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
return signV4("sts", request, region, accessKey, secretKey, contentSha256);
|
||||
}
|
||||
|
||||
private void setPresignCanonicalRequest(int expires) throws NoSuchAlgorithmException {
|
||||
setCanonicalHeaders(PRESIGN_IGNORED_HEADERS);
|
||||
|
||||
HttpUrl.Builder urlBuilder = this.request.url().newBuilder();
|
||||
urlBuilder.addEncodedQueryParameter(
|
||||
S3Escaper.encode("X-Amz-Algorithm"), S3Escaper.encode("AWS4-HMAC-SHA256"));
|
||||
urlBuilder.addEncodedQueryParameter(
|
||||
S3Escaper.encode("X-Amz-Credential"), S3Escaper.encode(this.accessKey + "/" + this.scope));
|
||||
urlBuilder.addEncodedQueryParameter(
|
||||
S3Escaper.encode("X-Amz-Date"), S3Escaper.encode(this.date.format(Time.AMZ_DATE_FORMAT)));
|
||||
urlBuilder.addEncodedQueryParameter(
|
||||
S3Escaper.encode("X-Amz-Expires"), S3Escaper.encode(Integer.toString(expires)));
|
||||
urlBuilder.addEncodedQueryParameter(
|
||||
S3Escaper.encode("X-Amz-SignedHeaders"), S3Escaper.encode(this.signedHeaders));
|
||||
this.url = urlBuilder.build();
|
||||
|
||||
setCanonicalQueryString();
|
||||
|
||||
this.canonicalRequest =
|
||||
this.request.method()
|
||||
+ "\n"
|
||||
+ this.url.encodedPath()
|
||||
+ "\n"
|
||||
+ this.canonicalQueryString
|
||||
+ "\n"
|
||||
+ Joiner.on("\n").withKeyValueSeparator(":").join(this.canonicalHeaders)
|
||||
+ "\n\n"
|
||||
+ this.signedHeaders
|
||||
+ "\n"
|
||||
+ this.contentSha256;
|
||||
|
||||
this.canonicalRequestHash = Digest.sha256Hash(this.canonicalRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pre-signed HttpUrl object for given request, region, access key, secret key and expires
|
||||
* time.
|
||||
*/
|
||||
public static HttpUrl presignV4(
|
||||
Request request, String region, String accessKey, String secretKey, int expires)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
String contentSha256 = "UNSIGNED-PAYLOAD";
|
||||
ZonedDateTime date = ZonedDateTime.parse(request.header("x-amz-date"), Time.AMZ_DATE_FORMAT);
|
||||
|
||||
Signer signer = new Signer(request, contentSha256, date, region, accessKey, secretKey, null);
|
||||
signer.setScope("s3");
|
||||
signer.setPresignCanonicalRequest(expires);
|
||||
signer.setStringToSign();
|
||||
signer.setSigningKey("s3");
|
||||
signer.setSignature();
|
||||
|
||||
return signer
|
||||
.url
|
||||
.newBuilder()
|
||||
.addEncodedQueryParameter(
|
||||
S3Escaper.encode("X-Amz-Signature"), S3Escaper.encode(signer.signature))
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Returns credential string of given access key, date and region. */
|
||||
public static String credential(String accessKey, ZonedDateTime date, String region) {
|
||||
return accessKey
|
||||
+ "/"
|
||||
+ date.format(Time.SIGNER_DATE_FORMAT)
|
||||
+ "/"
|
||||
+ region
|
||||
+ "/s3/aws4_request";
|
||||
}
|
||||
|
||||
/** Returns pre-signed post policy string for given stringToSign, secret key, date and region. */
|
||||
public static String postPresignV4(
|
||||
String stringToSign, String secretKey, ZonedDateTime date, String region)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Signer signer = new Signer(null, null, date, region, null, secretKey, null);
|
||||
signer.stringToSign = stringToSign;
|
||||
signer.setSigningKey("s3");
|
||||
signer.setSignature();
|
||||
|
||||
return signer.signature;
|
||||
}
|
||||
|
||||
/** Returns HMacSHA256 digest of given key and data. */
|
||||
public static byte[] sumHmac(byte[] key, byte[] data)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
||||
mac.update(data);
|
||||
|
||||
return mac.doFinal();
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage,
|
||||
* (C) 2020 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
|
||||
/** Time formatters for S3 APIs. */
|
||||
public class Time {
|
||||
public static final ZoneId UTC = ZoneId.of("Z");
|
||||
|
||||
public static final DateTimeFormatter AMZ_DATE_FORMAT =
|
||||
DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'", Locale.US).withZone(UTC);
|
||||
|
||||
public static final DateTimeFormatter RESPONSE_DATE_FORMAT =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH':'mm':'ss'.'SSS'Z'", Locale.US).withZone(UTC);
|
||||
|
||||
// Formatted string is convertible to LocalDate only, not to LocalDateTime or ZonedDateTime.
|
||||
// Below example shows how to use this to get ZonedDateTime.
|
||||
// LocalDate.parse("20200225", SIGNER_DATE_FORMAT).atStartOfDay(UTC);
|
||||
public static final DateTimeFormatter SIGNER_DATE_FORMAT =
|
||||
DateTimeFormatter.ofPattern("yyyyMMdd", Locale.US).withZone(UTC);
|
||||
|
||||
public static final DateTimeFormatter HTTP_HEADER_DATE_FORMAT =
|
||||
DateTimeFormatter.ofPattern("EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'", Locale.US).withZone(UTC);
|
||||
|
||||
public static final DateTimeFormatter EXPIRATION_DATE_FORMAT = RESPONSE_DATE_FORMAT;
|
||||
|
||||
private Time() {}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user