0%

AWS S3 SignatureDoesNotMatch

AWS S3 踩坑

第一次用S3,记录一下

1. Key在哪里

  • IAM 设置一个用户组,权限勾选S3 的选项
  • 生成一个专属用户, 划分到用户组
  • 用户处生成密钥,那AccessKey 和 SecretKey

2. 桶策略配置

1
2
3
4
5
6
7
8
9
10
11
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::桶名称/*"
}
]
}

3. Spring Boot怎么连接

  • Maven 找到AWS SDK S3 导入
    1
    2
    3
    4
    5
    6
    <!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-s3 -->
    <dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-s3</artifactId>
    <version>1.12.696</version>
    </dependency>
  • application 填写Region,两个Key
    1
    2
    3
    4
    5
    6
    amazon:
    s3:
    accessKey:
    secretKey:
    region:
    bucketName:
  • 构建单例工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
    /**
* AmazonS3工具类
*/
@Slf4j
@Component
public class AmazonS3Util {

private static String accessKey;
private static String secretKey;
private static String region;


@Value("${amazon.s3.accessKey}")
public void setAccessKey(String accessKey) {
AmazonS3Util.accessKey = accessKey;
}

@Value("${amazon.s3.secretKey}")
public void setSecretKey(String secretKey) {
AmazonS3Util.secretKey = secretKey;
}

@Value("${amazon.s3.region}")
public void setRegion(String region) {
AmazonS3Util.region = region;
}

@Value("${amazon.s3.bucketName}")
public String bucketName;

public static volatile AmazonS3 client;

/**
* singleton
*
* @return OkHttpClient
*/
public static AmazonS3 getInstance() {
if (Objects.isNull(client)) {
synchronized (AmazonS3.class) {
if (Objects.isNull(client)) {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
ClientConfiguration conf = new ClientConfiguration();
// 设置AmazonS3使用的最大连接数
conf.setMaxConnections(200);
// 设置socket超时时间
conf.setSocketTimeout(10000);
// 设置失败请求重试次数
conf.setMaxErrorRetry(1);
// 如果要用https协议,请加上下面语句
conf.setProtocol(Protocol.HTTPS);
// 设置加密版本
conf.setSignerOverride("AWSS3V4SignerType");
return AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.withClientConfiguration(conf)
.build();
}
}
}
return client;
}

public String uploadFile(String contextType, String fileName, MultipartFile file) {
try (InputStream input = new ByteArrayInputStream(file.getBytes())) {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(contextType);
metadata.setHttpExpiresDate(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365)));
metadata.setContentLength(input.available());
fileName = UUID.randomUUID().toString().replace("-", "") + fileName;
getInstance().putObject(bucketName, fileName, input, metadata);
return fileName;
} catch (Exception e) {
log.error("上传文件失败", e);
}
return null;
}

public String generateResignedUrl(String fileName) {
URL url = getInstance().generatePresignedUrl(bucketName, fileName, new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7)), HttpMethod.GET);
return url.toString();
}
}

4. 公网访问文件,是否必须关闭网络访问

屏蔽公共访问权限(存储桶设置) 阻止所有公开访问,获取授权访问可以调用 generatePresignedUrl 但是有时效,S3 SDK获取公网链接,最长7天

5. 使用时遇到的坑

本来想构建几个目录,划分不同职责,之前公司华为云、阿里云上都可以这么做,但是S3 这里有问题,我可以在bucket下增删除查改数据,但是如果我新建目录 image/ 再在这个目录下进行数据CURD操作,则报 SignatureDoesNotMatch 错误.

1
2
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>

我一直以为是我Key有问题,因为一开始我测试文件是放bucket下的,新增目录肯定是我的问题,但是后面在github上发现有人说,配置目录后就是会这样的。

规避方法

文件名添加UUID前缀,放在bucket下。
如果有其他方法,请告知我,谢谢。

其他记录

  1. MultipartFile 中文名乱码,用ISO_8859_1 读取文件名并使用UTF8记录
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static String getFileName(MultipartFile file) {
    try {
    // 尝试解码
    return new String(Objects.requireNonNull(file.getOriginalFilename()).getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
    } catch (Exception e) {
    // 返回原名
    return file.getOriginalFilename();
    }
    }