POST簽名是指在使用PostObject上傳方式時,為保證上傳請求的安全性,OSS要求每個上傳請求都攜帶一個簽名(Signature)。在POST V4簽名中,Signature是通過存取金鑰(AccessKey Secret)以及目前時間和Region等資訊對一系列請求參數(包括上傳策略policy、到期時間等)進行加密計算得出。應用伺服器產生簽名後,將其與上傳策略policy等資訊一併提供給用戶端,用戶端使用這些資訊構造上傳請求。OSS收到上傳請求後會驗證簽名的有效性,只有簽名驗證通過的請求會被接受,簽名驗證未通過的請求將被拒絕。
POST簽名介紹
HTTP POST請求通過整合V4簽名演算法強化安全性。表單和policy扮演著關鍵角色,用於確保上傳請求的合法性和安全性。
表單
表單是POST請求中實際攜帶的欄位集合,用於傳遞檔案本身及其相關的中繼資料資訊。以下是POST V4簽名專屬的表單元素,其他公用表單元素,請參見PostObject表單元素。
欄位 | 類型 | 是否必選 | 描述 |
x-oss-signature-version | 字串 | 是 | 指定簽名的版本和演算法,固定值為OSS4-HMAC-SHA256。 |
x-oss-credential | 字串 | 是 | 指明衍生金鑰的參數集,格式如下:
|
x-oss-date | 字串 | 是 | 請求的時間,其格式遵循ISO 8601日期和時間標準,例如
|
x-oss-signature | 字串 | 是 | 用於簽名認證的描述資訊。其值為通過HMAC-SHA256雜湊演算法對Base64編碼後的policy字串進行加密計算,然後將經過HMAC-SHA256計算得出的二進位hash值轉換成十六進位的形式。 |
policy
policy表單域是一種安全性原則,用於定義使用者通過HTML表單上傳檔案到OSS時的許可權限制和約束條件。policy表單域通過JSON格式定義,通過多項參數限制上傳操作,例如允許上傳的Bucket名稱、Object首碼、有效期間、允許的HTTP方法、上傳內容的大小限制、內容類型限制等。
policy中必須包含expiration和conditions欄位。以下樣本conditions欄位中包含了非必選欄位,例如x-oss-security-token,該欄位僅在通過STS構造POST簽名時使用。如果使用了長期密鑰AccessKey構造POST簽名,則無需包含x-oss-security-token欄位。
{
"expiration": "2023-12-03T13:00:00.000Z",
"conditions": [
{"bucket": "examplebucket"},
{"x-oss-signature-version": "OSS4-HMAC-SHA256"},
{"x-oss-credential": "AKIDEXAMPLE/20231203/cn-hangzhou/oss/aliyun_v4_request"},
{"x-oss-security-token": "CAIS******"},
{"x-oss-date": "20231203T121212Z"},
["content-length-range", 1, 10],
["eq", "$success_action_status", "201"],
["starts-with", "$key", "user/eric/"],
["in", "$content-type", ["image/jpg", "image/png"]],
["not-in", "$cache-control", ["no-cache"]]
]
}
policy詳細說明如下:
expiration
用於指定policy的到期時間,以ISO8601 GMT時間表示。例如指定為
2023-12-03T13:00:00.000Z
,表示必須在2023年12月03日13點之前發起POST請求。conditions
用於指定POST請求表單域的合法值。
欄位
類型
是否必選
描述
Conditions匹配方式
bucket
字串
否
Bucket名稱。
bucket
x-oss-signature-version
字串
是
指定簽名的版本和演算法,固定值為OSS4-HMAC-SHA256。
x-oss-signature-version
x-oss-credential
字串
是
指明衍生金鑰的參數集,格式如下:
<AccessKeyId>/<date>/<region>/oss/aliyun_v4_request
AccessKeyId:填寫存取金鑰中的AccessKey ID。
date:填寫請求的日期,
region:填寫阿里雲通用Region ID,樣本值為cn-hangzhou。
oss:請求的服務名稱,固定值為oss。
aliyun_v4_request:請求的版本說明,固定值為aliyun_v4_request。
x-oss-credential
x-oss-security-token
字串
否
僅當使用STS構造POST簽名時,才需要設定此參數。您可以通過調用STS服務的AssumeRole介面擷取安全性權杖。
x-oss-security-token
x-oss-date
字串
是
請求的時間,其格式遵循ISO 8601日期和時間標準,例如
20231203T121212Z
。允許向後位移15分鐘:即實際伺服器接收到請求的時間與
x-oss-date
攜帶的時間相比,可以晚最多15分鐘。這種設計主要是考慮到網路傳輸延遲以及用戶端和伺服器之間可能存在的時間同步誤差,確保即使用戶端時間稍有偏差,也能在一定範圍內正常處理請求。最多允許7天有效期間:從
x-oss-date
指定的時間開始計算,請求的有效期間不能超過7天。如果用戶端發送的請求中的x-oss-date
值表明該請求已經到期(超過7天),則OSS服務端會拒絕該請求,並返回錯誤資訊。這種設計是為了保證請求的時效性和安全性,防止惡意提交舊的、已簽名的請求。x-oss-date
指定的時間作為StringToSign中的TimeStamp,取值必須與建立SigningKey中的Date是同一天,且要求與表單中的x-oss-date取值保持一致。
x-oss-date
content-length-range
字串
否
上傳Object的最小和最大允許大小,單位為位元組。
content-length-range
success_action_status
字串
否
上傳成功後的返回狀態代碼。
eq、starts-with、in和not-in
key
字串
否
上傳的Object名稱。
eq、starts-with、in和not-in
content-type
字串
否
限制上傳的檔案類型。
eq、starts-with、in和not-in
cache-control
字串
否
指定Object的緩衝行為。
eq、starts-with、in和not-in
簽名計算過程
建立utf-8編碼的policy。
構造StringToSign。
將policy進行Base64編碼,產生一個安全傳輸的字串,作為待簽名字串(StringToSign)。
計算SigningKey。
採用HMAC-SHA256雜湊演算法對StringToSign進行加密計算。計算過程中,使用與OSS帳號關聯的SigningKey作為雜湊運算的密鑰。
計算Signature。
將經過HMAC-SHA256計算得出的二進位雜湊值轉化為十六進位形式,所得到的十六進位字串即為Signature,用於驗證請求的完整性和合法性。
POST簽名計算完整範例程式碼
以上述提供的policy為例,通過Java範例程式碼示範POST簽名計算的完整過程。
使用AK、SK計算簽名
import com.aliyun.oss.common.utils.BinaryUtil;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class Demo {
public static void main(String[] args) throws Exception {
// 運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
String accesskeyid = System.getenv().get("OSS_ACCESS_KEY_ID");
String accesskeysecret = System.getenv().get("OSS_ACCESS_KEY_SECRET");
// 步驟1:建立policy。
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> policy = new HashMap<>();
policy.put("expiration", "2024-12-03T13:00:00.000Z");
List<Object> conditions = new ArrayList<>();
Map<String, String> bucketCondition = new HashMap<>();
bucketCondition.put("bucket", "examplebucket");
conditions.add(bucketCondition);
Map<String, String> signatureVersionCondition = new HashMap<>();
signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256");
conditions.add(signatureVersionCondition);
Map<String, String> credentialCondition = new HashMap<>();
credentialCondition.put("x-oss-credential", "accesskeyid/20241203/cn-hangzhou/oss/aliyun_v4_request"); // 替換為實際的 access key id
conditions.add(credentialCondition);
Map<String, String> dateCondition = new HashMap<>();
dateCondition.put("x-oss-date", "20241203T121212Z");
conditions.add(dateCondition);
conditions.add(Arrays.asList("content-length-range", 1, 10));
conditions.add(Arrays.asList("eq", "$success_action_status", "201"));
conditions.add(Arrays.asList("starts-with", "$key", "user/eric/"));
conditions.add(Arrays.asList("in", "$content-type", Arrays.asList("image/jpg", "image/png")));
conditions.add(Arrays.asList("not-in", "$cache-control", Arrays.asList("no-cache")));
policy.put("conditions", conditions);
String jsonPolicy = mapper.writeValueAsString(policy);
// 步驟2:構造待簽名字串(StringToSign)。
String stringToSign = new String(Base64.encodeBase64(jsonPolicy.getBytes()));
// 步驟3:計算SigningKey。
byte[] dateKey = hmacsha256(("aliyun_v4" + accesskeysecret).getBytes(), "20231203");
byte[] dateRegionKey = hmacsha256(dateKey, "cn-hangzhou");
byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss");
byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request");
// 步驟4:計算Signature。
byte[] result = hmacsha256(signingKey, stringToSign);
String signature = BinaryUtil.toHex(result);
System.out.println("signature:" + signature);
}
public static byte[] hmacsha256(byte[] key, String data) {
try {
// 初始化HMAC密鑰規格,指定演算法為HMAC-SHA256並使用提供的密鑰。
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256");
// 擷取Mac執行個體,並通過getInstance方法指定使用HMAC-SHA256演算法。
Mac mac = Mac.getInstance("HmacSHA256");
// 使用密鑰初始化Mac對象。
mac.init(secretKeySpec);
// 執行HMAC計算,通過doFinal方法接收需要計算的資料並返回計算結果的數組。
byte[] hmacBytes = mac.doFinal(data.getBytes());
return hmacBytes;
} catch (Exception e) {
throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
}
}
}
返回結果如下:
signature:3908473f7dbfb79a102eaaa44ca1edec8d7058ce3bd1c624d59eb437463bd5d6
使用臨時訪問憑證計算簽名
使用臨時訪問憑證STS構造POST簽名時,還需要通過調用STS服務的AssumeRole介面擷取安全性權杖。
import com.aliyun.oss.common.utils.BinaryUtil;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.aliyun.sts20150401.models.AssumeRoleResponse;
import com.aliyun.sts20150401.models.AssumeRoleResponseBody;
import com.aliyun.tea.TeaException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.*;
public class Demo {
//初始化STS Client
public static com.aliyun.sts20150401.Client createStsClient() throws Exception {
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
// 必填,請確保代碼運行環境設定了環境變數 OSS_ACCESS_KEY_ID。
.setAccessKeyId(System.getenv("OSS_ACCESS_KEY_ID"))
// 必填,請確保代碼運行環境設定了環境變數 OSS_ACCESS_KEY_SECRET。
.setAccessKeySecret(System.getenv("OSS_ACCESS_KEY_SECRET"));
// Endpoint
config.endpoint = "sts.cn-hangzhou.aliyuncs.com";
return new com.aliyun.sts20150401.Client(config);
}
//擷取STS臨時憑證
public static AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials getCredential() throws Exception {
com.aliyun.sts20150401.Client client = Demo.createStsClient();
com.aliyun.sts20150401.models.AssumeRoleRequest assumeRoleRequest = new com.aliyun.sts20150401.models.AssumeRoleRequest()
// 必填,請確保代碼運行環境設定了環境變數 OSS_STS_ROLE_ARN。
.setRoleArn(System.getenv("OSS_STS_ROLE_ARN"))
.setRoleSessionName("role_session_name");// 自訂會話名稱
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
try {
// 複製代碼運行請自行列印 API 的傳回值
AssumeRoleResponse response = client.assumeRoleWithOptions(assumeRoleRequest, runtime);
// credentials裡包含了後續要用到的AccessKeyId、AccessKeySecret和SecurityToken。
return response.body.credentials;
} catch (TeaException error) {
// 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
// 錯誤 message
System.out.println(error.getMessage());
// 診斷地址
System.out.println(error.getData().get("Recommend"));
com.aliyun.teautil.Common.assertAsString(error.message);
} catch (Exception _error) {
TeaException error = new TeaException(_error.getMessage(), _error);
// 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。
// 錯誤 message
System.out.println(error.getMessage());
// 診斷地址
System.out.println(error.getData().get("Recommend"));
com.aliyun.teautil.Common.assertAsString(error.message);
}
return null;
}
public static void main(String[] args) throws Exception {
AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials sts_data = getCredential();
String accesskeyid = sts_data.accessKeyId;
String accesskeysecret = sts_data.accessKeySecret;
String securitytoken = sts_data.securityToken;
// 步驟1:建立policy。
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> policy = new HashMap<>();
policy.put("expiration", "2024-12-03T13:00:00.000Z");
List<Object> conditions = new ArrayList<>();
Map<String, String> bucketCondition = new HashMap<>();
bucketCondition.put("bucket", "examplebucket");
conditions.add(bucketCondition);
Map<String, String> signatureVersionCondition = new HashMap<>();
signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256");
conditions.add(signatureVersionCondition);
Map<String, String> securityTokenCondition = new HashMap<>();
securityTokenCondition.put("x-oss-security-token", securitytoken);
conditions.add(securityTokenCondition);
Map<String, String> credentialCondition = new HashMap<>();
credentialCondition.put("x-oss-credential", "accesskeyid/20241203/cn-hangzhou/oss/aliyun_v4_request");
conditions.add(credentialCondition);
Map<String, String> dateCondition = new HashMap<>();
dateCondition.put("x-oss-date", "20241203T121212Z");
conditions.add(dateCondition);
conditions.add(Arrays.asList("content-length-range", 1, 10));
conditions.add(Arrays.asList("eq", "$success_action_status", "201"));
conditions.add(Arrays.asList("starts-with", "$key", "user/eric/"));
conditions.add(Arrays.asList("in", "$content-type", Arrays.asList("image/jpg", "image/png")));
conditions.add(Arrays.asList("not-in", "$cache-control", Arrays.asList("no-cache")));
policy.put("conditions", conditions);
String jsonPolicy = mapper.writeValueAsString(policy);
// 步驟2:構造待簽名字串(StringToSign)。
String stringToSign = new String(Base64.encodeBase64(jsonPolicy.getBytes()));
// 步驟3:計算SigningKey。
byte[] dateKey = hmacsha256(("aliyun_v4" + accesskeysecret).getBytes(), "20231203");
byte[] dateRegionKey = hmacsha256(dateKey, "cn-hangzhou");
byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss");
byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request");
// 步驟4:計算Signature。
byte[] result = hmacsha256(signingKey, stringToSign);
String signature = BinaryUtil.toHex(result);
System.out.println("signature:" + signature);
}
public static byte[] hmacsha256(byte[] key, String data) {
try {
// 初始化HMAC密鑰規格,指定演算法為HMAC-SHA256並使用提供的密鑰。
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256");
// 擷取Mac執行個體,並通過getInstance方法指定使用HMAC-SHA256演算法。
Mac mac = Mac.getInstance("HmacSHA256");
// 使用密鑰初始化Mac對象。
mac.init(secretKeySpec);
// 執行HMAC計算,通過doFinal方法接收需要計算的資料並返回計算結果的數組。
byte[] hmacBytes = mac.doFinal(data.getBytes());
return hmacBytes;
} catch (Exception e) {
throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
}
}
}
返回結果如下:
signature:1e09438f7ad01af6b3e144b42c98929c68f8d090ce07f4c277b18d8b62d0aa02