297 lines
7.0 KiB
Markdown
297 lines
7.0 KiB
Markdown
|
|
# COS POST 签名算法详解与调试
|
|||
|
|
|
|||
|
|
## 🔍 签名计算步骤(已修正)
|
|||
|
|
|
|||
|
|
根据腾讯云官方文档,COS POST Object 的签名计算步骤如下:
|
|||
|
|
|
|||
|
|
### 步骤 1:生成 KeyTime
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
KeyTime = StartTimestamp;EndTimestamp
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**示例:**
|
|||
|
|
```
|
|||
|
|
1567064374;1567071574
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 步骤 2:构造 Policy(JSON 格式)
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"expiration": "2019-08-29T09:39:34.471Z",
|
|||
|
|
"conditions": [
|
|||
|
|
{ "bucket": "examplebucket-1250000000" },
|
|||
|
|
[ "content-length-range", 1, 10485760 ],
|
|||
|
|
[ "starts-with", "$key", "user_img" ],
|
|||
|
|
{ "q-sign-algorithm": "sha1" },
|
|||
|
|
{ "q-ak": "************************************" },
|
|||
|
|
{ "q-sign-time": "1567064374;1567071574" }
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 步骤 3:生成 SignKey
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
SignKey = HMAC-SHA1(SecretKey, KeyTime)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**注意:** 结果是**十六进制小写字符串**
|
|||
|
|
|
|||
|
|
**示例:**
|
|||
|
|
```
|
|||
|
|
SignKey = HMAC-SHA1("your-secret-key", "1567064374;1567071574")
|
|||
|
|
= "39acc8c9f34ba5b19bce4e965b370cd3f62d2fba"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 步骤 4:生成 StringToSign
|
|||
|
|
|
|||
|
|
**关键:** StringToSign 是 **Policy JSON 原文**的 SHA1 哈希值
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
StringToSign = SHA1(Policy JSON 原文)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**注意:**
|
|||
|
|
- ✅ 使用 **Policy 的 JSON 原文**(未 Base64 编码)
|
|||
|
|
- ✅ 结果是**十六进制小写字符串**
|
|||
|
|
|
|||
|
|
**示例:**
|
|||
|
|
```
|
|||
|
|
Policy JSON: {"expiration":"2019-08-29T09:39:34.471Z","conditions":[...]}
|
|||
|
|
StringToSign = SHA1(Policy JSON)
|
|||
|
|
= "d5d903b8360468bc81c1311f134989bc8c8b5b89"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 步骤 5:生成 Signature
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Signature = HMAC-SHA1(SignKey, StringToSign)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**注意:** 结果是**十六进制小写字符串**
|
|||
|
|
|
|||
|
|
**示例:**
|
|||
|
|
```
|
|||
|
|
Signature = HMAC-SHA1("39acc8c9f34ba5b19bce4e965b370cd3f62d2fba", "d5d903b8360468bc81c1311f134989bc8c8b5b89")
|
|||
|
|
= "7758dc9a832e9d301dca704cacbf9d9f8172abcd"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ 正确的代码实现
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 1. 生成 KeyTime
|
|||
|
|
long currentSeconds = System.currentTimeMillis() / 1000;
|
|||
|
|
long expireSeconds = currentSeconds + 3600;
|
|||
|
|
String keyTime = currentSeconds + ";" + expireSeconds;
|
|||
|
|
|
|||
|
|
// 2. 构造 Policy
|
|||
|
|
Map<String, Object> policy = new HashMap<>();
|
|||
|
|
policy.put("expiration", generateExpiration(3600L));
|
|||
|
|
List<Object> conditions = new ArrayList<>();
|
|||
|
|
conditions.add(Map.of("bucket", "your-bucket"));
|
|||
|
|
conditions.add(Arrays.asList("content-length-range", 1, 10485760));
|
|||
|
|
conditions.add(Arrays.asList("starts-with", "$key", "user_img"));
|
|||
|
|
conditions.add(Map.of("q-sign-algorithm", "sha1"));
|
|||
|
|
conditions.add(Map.of("q-ak", secretId));
|
|||
|
|
conditions.add(Map.of("q-sign-time", keyTime));
|
|||
|
|
policy.put("conditions", conditions);
|
|||
|
|
|
|||
|
|
String jsonPolicy = mapper.writeValueAsString(policy);
|
|||
|
|
|
|||
|
|
// 3. 生成 SignKey
|
|||
|
|
String signKey = hmacSha1(secretKey, keyTime);
|
|||
|
|
|
|||
|
|
// 4. 生成 StringToSign(Policy JSON 原文的 SHA1)
|
|||
|
|
String stringToSign = sha1(jsonPolicy);
|
|||
|
|
|
|||
|
|
// 5. 生成 Signature
|
|||
|
|
String signature = hmacSha1(signKey, stringToSign);
|
|||
|
|
|
|||
|
|
// 6. Base64 编码 Policy(用于表单提交)
|
|||
|
|
String encodedPolicy = Base64.encodeBase64String(jsonPolicy.getBytes(StandardCharsets.UTF_8));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔧 辅助方法
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/**
|
|||
|
|
* HMAC-SHA1(返回十六进制小写字符串)
|
|||
|
|
*/
|
|||
|
|
private String hmacSha1(String key, String data) {
|
|||
|
|
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
|
|||
|
|
Mac mac = Mac.getInstance("HmacSHA1");
|
|||
|
|
mac.init(secretKeySpec);
|
|||
|
|
byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
|||
|
|
return toHexString(hmacBytes); // 十六进制小写
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* SHA1(返回十六进制小写字符串)
|
|||
|
|
*/
|
|||
|
|
private String sha1(String data) {
|
|||
|
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
|||
|
|
byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
|
|||
|
|
return toHexString(hash); // 十六进制小写
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 字节数组转十六进制小写字符串
|
|||
|
|
*/
|
|||
|
|
private String toHexString(byte[] bytes) {
|
|||
|
|
StringBuilder sb = new StringBuilder();
|
|||
|
|
for (byte b : bytes) {
|
|||
|
|
sb.append(String.format("%02x", b));
|
|||
|
|
}
|
|||
|
|
return sb.toString();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 调试步骤
|
|||
|
|
|
|||
|
|
### 1. 启用 DEBUG 日志
|
|||
|
|
|
|||
|
|
在 `application.yml` 中添加:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
logging:
|
|||
|
|
level:
|
|||
|
|
com.dora.service.OssPostSignatureService: DEBUG
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 查看日志输出
|
|||
|
|
|
|||
|
|
重新请求签名接口后,查看日志:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
COS POST signature calculation:
|
|||
|
|
KeyTime: 1733472660;1733476260
|
|||
|
|
Policy JSON: {"expiration":"2025-12-06T12:51:00.000Z","conditions":[...]}
|
|||
|
|
StringToSign (SHA1 of Policy): 93e7e253c53fc6513ce0dc1c0dd34af925ad028f
|
|||
|
|
SignKey: 39acc8c9f34ba5b19bce4e965b370cd3f62d2fba
|
|||
|
|
Signature: 7758dc9a832e9d301dca704cacbf9d9f8172abcd
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 对比 COS 返回的错误
|
|||
|
|
|
|||
|
|
如果 COS 返回签名错误,会显示它计算的 StringToSign:
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<Error>
|
|||
|
|
<Code>SignatureDoesNotMatch</Code>
|
|||
|
|
<Message>The Signature you specified is invalid.</Message>
|
|||
|
|
<StringToSign>93e7e253c53fc6513ce0dc1c0dd34af925ad028f</StringToSign>
|
|||
|
|
</Error>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**对比:**
|
|||
|
|
- 如果 `StringToSign` 相同,说明 Policy 正确,但 Signature 计算错误
|
|||
|
|
- 如果 `StringToSign` 不同,说明 Policy 构造有问题
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚠️ 常见错误
|
|||
|
|
|
|||
|
|
### 错误 1:使用 Base64 编码后的 Policy 计算 StringToSign
|
|||
|
|
|
|||
|
|
❌ **错误:**
|
|||
|
|
```java
|
|||
|
|
String encodedPolicy = Base64.encodeBase64String(jsonPolicy.getBytes());
|
|||
|
|
String stringToSign = sha1(encodedPolicy); // 错误!
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
✅ **正确:**
|
|||
|
|
```java
|
|||
|
|
String stringToSign = sha1(jsonPolicy); // 使用 JSON 原文
|
|||
|
|
String encodedPolicy = Base64.encodeBase64String(jsonPolicy.getBytes());
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 错误 2:HMAC-SHA1 返回 Base64 而不是十六进制
|
|||
|
|
|
|||
|
|
❌ **错误:**
|
|||
|
|
```java
|
|||
|
|
return Base64.encodeBase64String(hmacBytes); // 错误!
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
✅ **正确:**
|
|||
|
|
```java
|
|||
|
|
return toHexString(hmacBytes); // 十六进制小写
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 错误 3:Policy 中缺少必需的签名条件
|
|||
|
|
|
|||
|
|
❌ **错误:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"conditions": [
|
|||
|
|
{ "bucket": "xxx" }
|
|||
|
|
// 缺少 q-sign-algorithm, q-ak, q-sign-time
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
✅ **正确:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"conditions": [
|
|||
|
|
{ "bucket": "xxx" },
|
|||
|
|
{ "q-sign-algorithm": "sha1" },
|
|||
|
|
{ "q-ak": "AKID..." },
|
|||
|
|
{ "q-sign-time": "1567064374;1567071574" }
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📚 参考文档
|
|||
|
|
|
|||
|
|
- [腾讯云 COS POST Object 官方文档](https://cloud.tencent.com/document/product/436/14690)
|
|||
|
|
- [COS 请求签名算法](https://cloud.tencent.com/document/product/436/7778)
|
|||
|
|
- [COS 签名工具(在线验证)](https://cloud.tencent.com/document/product/436/30442)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ 验证清单
|
|||
|
|
|
|||
|
|
在部署前,确认以下几点:
|
|||
|
|
|
|||
|
|
- [ ] KeyTime 格式正确:`StartTimestamp;EndTimestamp`
|
|||
|
|
- [ ] Policy 包含所有必需条件
|
|||
|
|
- [ ] StringToSign = SHA1(Policy JSON 原文)
|
|||
|
|
- [ ] SignKey = HMAC-SHA1(SecretKey, KeyTime)
|
|||
|
|
- [ ] Signature = HMAC-SHA1(SignKey, StringToSign)
|
|||
|
|
- [ ] 所有哈希值都是十六进制小写字符串
|
|||
|
|
- [ ] 表单提交时包含所有必需字段
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 总结
|
|||
|
|
|
|||
|
|
**关键点:**
|
|||
|
|
1. StringToSign 是 **Policy JSON 原文**的 SHA1,不是 Base64 后的
|
|||
|
|
2. 所有哈希值都是**十六进制小写字符串**,不是 Base64
|
|||
|
|
3. Policy 中必须包含 `q-sign-algorithm`, `q-ak`, `q-sign-time` 条件
|
|||
|
|
4. 表单中必须包含 `q-key-time` 字段
|
|||
|
|
|
|||
|
|
**修复后重新编译部署即可!**
|