全部产品
Search
文档中心

对象存储 OSS:POST V4签名

更新时间:Dec 09, 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,用于验证请求的完整性和合法性。

image

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