全部產品
Search
文件中心

Object Storage Service:POST V4簽名

更新時間:Dec 10, 2024

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

字串

指明衍生金鑰的參數集,格式如下:

<AccessKeyId>/<date>/<region>/oss/aliyun_v4_request
  • AccessKeyId:填寫存取金鑰中的AccessKey ID。

  • date:填寫請求的日期,格式是YYYYMMDD,樣本值為20231203。

  • region:填寫阿里雲通用Region ID,樣本值為cn-hangzhou。

  • oss:請求的服務名稱,固定值為oss。

  • aliyun_v4_request:請求的版本說明,固定值為aliyun_v4_request。

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是同一天,且要求與policy中的x-oss-date取值保持一致。

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

Conditions匹配方式

Conditions匹配方式

描述

content-length-range

指定所允許上傳的檔案最大和最小範圍,例如允許的檔案大小為1~10位元組,則可以寫為["content-length-range", 1, 10]。

eq

表單域的值必須精確匹配Conditions中聲明的值。例如指定key表單域的值必須為a:{"key": "a"} ,則可以寫為["eq", "$key", "a"]。

starts-with

表單域的值必須以指定首碼開始。例如,如果要指定key的值以user/user1開始,則可以寫為["starts-with", "$key", "user/user1"]。

in

以字串列表的形式指定需包含的檢查元素。例如,通過PostObject介面上傳圖片時,需校正上傳的檔案為圖片類型,但允許多種格式的圖片,則通過in語義指定為["in", "$content-type", ["image/jpg", "image/png"]]。

not-in

以字串列表的形式指定需排除的檢查元素。例如,通過PostObject介面上傳時需要指定Object緩衝行為,且不支援no-cache的形式,則通過not-in語義指定為["not-in", "$cache-control", ["no-cache"]]。

policy逸出字元

在Post policy中$表示變數。如果要描述$,需要使用逸出字元\$。下表描述了在Post policy的JSON中需要進行轉義的字元。

逸出字元

描述

\/

斜杠

\\

反斜線

\”

雙引號

\$

美元符

\b

空格

\f

換頁

\n

換行

\r

斷行符號

\t

水平定位字元

\uxxxx

Unicode字元

簽名計算過程

  1. 建立utf-8編碼的policy。

  2. 構造StringToSign。

    將policy進行Base64編碼,產生一個安全傳輸的字串,作為待簽名字串(StringToSign)。

  3. 計算SigningKey。

    採用HMAC-SHA256雜湊演算法對StringToSign進行加密計算。計算過程中,使用與OSS帳號關聯的SigningKey作為雜湊運算的密鑰。

  4. 計算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