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