在Header中包含V4签名(推荐)

在OSS中,使用HTTP请求的Authorization Header来携带签名信息是进行身份验证的最常见方法。除了使用POST签名和URL签名之外,所有的OSS操作都需要通过Authorization Header来进行身份验证。本文介绍如何使用V4签名算法来在Header中包含签名。

SDK签名实现

OSS SDK已实现自动完成V4签名。采用OSS SDK的方式发起请求,可以免去手动签名的过程。如何在初始化客户端时使用V4签名,以及SDK如何实现V4签名的代码示例如下表所示:

SDK

客户端初始化示例

SDK签名实现

Java

初始化

OSSV4Signer.java

PHP

初始化

SignerV4.php

Node.js

初始化

client.js

Browser.js

初始化

Python

初始化

auth.py

Go

初始化

v4.go

C++

初始化

SignerV4.cc

C

初始化

oss_auth.c

Authorization参数说明

如果上述方式无法满足您的需求,例如使用REST API发起请求时,您需要手动计算SDK签名,需要获取发起REST API请求时所需的Authorization参数。Authorization字段以空格的形式分隔签名版本和签名信息。

Authorization字段

说明

签名版本

仅支持填写OSS4-HMAC-SHA256。

签名信息

以键值对(key=value)的形式呈现。键值对之间用逗号分隔,键与值之间用等号连接。其中,签名信息支持的key包括两个必选字段(CredentialSignature)以及一个可选字段(AdditionalHeaders)。

  • Credential描述具体使用哪个派生密钥,中间以正斜线(/)分隔。

  • Signature描述计算得到的签名值。

  • AdditionalHeaders:允许用户指定计算签名过程中可选的Header,如有多项需以分号(;)分隔,忽略大小写,且按字典序排列。

  • 格式

    Authorization: "OSS4-HMAC-SHA256 Credential=" + AccessKeyId + "/" + SignDate + "/" + SignRegion + "/oss/aliyun_v4_request, " + [ "AdditionalHeaders=" + AdditionalHeadersVal + ", " ] + "Signature=" + SignatureVal
  • 示例

    Authorization: OSS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20231203/cn-hangzhou/oss/aliyun_v4_request, AdditionalHeaders=host;userdefine, Signature=4b663e424d2db9967401ff6ce1c86f8c83cabd77d9908475239d9110642c63fa
说明

通过STS服务获取的临时访问凭证发送请求时,您还需要将获得的security-token值以x-oss-security-token:security-token的形式加入到请求头中。关于如何获取Security-Token的具体操作,请参见AssumeRole - 获取扮演角色的临时身份凭证

签名计算流程

如下是签名计算流程图。

  • 步骤一:通过Http verb等参数构建出的CanonicalReques字符串保留,在之后需要经过Hash转化;

  • 步骤二:将步骤一中获得的CanonicalRequest进行一次sha256Hex转换得到字符串“XXX”,再将"OSS4-HMAC-SHA256"等参数与所得到的字符串“XXX”拼接,构建出StringToSign;

  • 步骤三:根据图示使用HMAC-SHA256格式逐步转换封装参数,构建出SigningKey,最后将SigningKey与步骤二中获得的StringToSign使用HMAC-SHA256格式转换,得到的字符串就是Signature的值。

关于每个步骤的具体参数说明,请参考流程图下方各步骤内容。具体计算过程示例,请参考签名计算示例

image

步骤1:构造CanonicalRequest

按照OSS签名定义的规范,对请求参数进行拼接并使用Hash格式化转换为字符串。

拼接CanonicalRequest格式

HTTP Verb + "\n" +
Canonical URI + "\n" +
Canonical Query String + "\n" +
Canonical Headers + "\n" +
Additional Headers + "\n" +
Hashed PayLoad

各参数说明如下:

说明

如下涉及的专业术语可参考HTTP协议。

参数

类型

是否必选

示例

说明

HTTP Verb

枚举值

PUT

HTTP请求的Method,包含PUT、GET、POST、HEAD、DELETE、OPTIONS等。

Canonical URI

字符串

/examplebucket/exampleobject

URI进行UrIEncode后的字符串,其中正斜线(/)不需要编码。

  • 如果URL中不包含QueryString,则从正斜线(/)开始到URL末尾。

  • 如果URL中包含QueryString,则从正斜线(/)开始到问号(?)结束。

根据请求URI包含的资源有差异,Canonical URI的填写方法说明如下:

说明

Canonical URI需要加入Bucket的描述信息,通常情况下Bucket是体现在域名上的。

  • 如果请求的URI中既包含Bucket也包含Object,则Canonical URI填写示例为

    /examplebucket/exampleobject

  • 如果请求的URI中只包含Bucket不包含Object,则Canonical URI填写示例为/examplebucket/

  • 如果请求的URI中不包含Bucket且不包含Object,则Canonical URI填写示例为/

Canonical Query String

字符串

UrlEncode("marker") + "=" + UrlEncode("someMarker") + "&" + UrlEncode("max-keys") + "=" + UrlEncode("20") + "&" + UrlEncode("prefix") + "=" + UrlEncode("somePrefix")

针对QueryString执行UrIEncode后的字符串,单独对key和value进行编码。

  • 按QueryString的key进行排序,先编码,再排序。如果有多个相同的key,按照原来添加的顺序放置即可,中间使用&进行连接。

  • 只有key没有value的情况下,只添加 key即可。

  • 如果没有QueryString,则只需要放置空字符串“”,末尾仍然需要有换行符。

Canonical Headers

字符串

host:cname.com

x-oss-content-sha256:UNSIGNED-PARYLOAD

x-oss-date:20231203T121212Z

对请求Header的列表格式化后的字符串,各个Header之间需要添加换行符分隔。

  • 单个Header中的key和value通过冒号:分隔,Header与Header之间通过换行符分隔。

  • Header的key必须小写,value必须经过Trim(去除头尾的空格)。

  • 按Header中key的字典序进行排列。

  • 请求时间通过x-oss-date来描述,要求格式必须是ISO8601标准时间格式(示例值为20231203T121212Z)。

Canonical Headers包含以下两类:

  • 必须存在且参与签名的Header包括:

    • x-oss-content-sha256(其值为UNSIGNED-PARYLOAD)

    • AdditionalHeader指定必须存在且参与签名的Header

  • 如果存在则加入签名的Header包括:

    • Content-Type

    • Content-MD5

    • x-oss-*

Additional Headers

字符串

content-length;host

除了Content-Type、Content-MD5、x-oss-*以外,其他需要加入签名的Header。所有的Header必须是小写,且按照字典序排列。

Hashed PayLoad

字符串

UNSIGNED-PAYLOAD

仅支持UNSIGNED-PAYLOAD。

示例

"GET" | "PUT" | ... + "\n" +
UrlEncode(<Resource>) + "\n" +
UrlEncode(<QueryParam1>) + "=" + UrlEncode(<Value>) + "&" + UrlEncode(<QueryParam2>) + "\n" +
Lowercase(<HeaderName1>) + ":" + Trim(<value>) + "\n" + Lowercase(<HeaderName2>) + ":" + Trim(<value>) + "\n" + 
Lowercase(<AdditionalHeaderName1>) + ";" + Lowercase(<AdditionalHeaderName2>) + "\n" +
UNSIGNED-PAYLOAD

步骤2:构造待签名字符串(StringToSign)

将其他三个参数与步骤一中格式化后的请求再进行拼接,拼接完成后再通过Hash转换格式化,得到待签名的字符串。

  • 格式

    "OSS4-HMAC-SHA256" + "\n" +
    TimeStamp + "\n" +
    Scope + "\n" +
    Hex(SHA256Hash(<CanonicalRequest>))

    参数说明如下:

    参数

    类型

    是否必选

    示例

    说明

    OSS4-HMAC-SHA256

    枚举值

    OSS4-HMAC-SHA25

    指明使用的签名哈希算法,取值必须是OSS4-HMAC-SHA256。

    TimeStamp

    字符串

    20231203T121212Z

    指明当前的UTC时间,格式必须是 ISO8601。

    Scope

    字符串

    20231203/cn-hangzhou/oss/aliyun_v4_request

    指明获取Region派生密钥的参数集,格式如下:

    <SigningDate>/<SigningRegion>/oss/aliyun_v4_request
    • SigningDate:填写请求的日期。

    • SigningRegion:填写请求所在的Region。

    • oss:请求的服务名称,固定为oss。

    • aliyun_v4_request:请求的版本说明,固定为aliyun_v4_request。

    CanonicalRequest

    字符串

    PUT

    /examplebucket/exampleobject

    content-md5:eB5eJF1ptWaXm4bijSPyxw

    content-type:text/html

    host:examplebucket.oss-cn-hangzhou.aliyuncs.com

    x-oss-content-sha256:UNSIGNED-PAYLOAD

    x-oss-date:20231203T121212Z

    x-oss-meta-author:alice

    x-oss-meta-magic:abracadabra

    host

    UNSIGNED-PAYLOAD

    上一步骤构造出来的字符串。

  • 示例

    "OSS4-HMAC-SHA256" + "\n" +
    FormatISO8601 + "\n" +
    20231203/cn-hangzhou/oss/aliyun_v4_request + "\n" +
    Hex(SHA256Hash(<CanonicalRequest>))

步骤3:计算Signature

使用签名密钥对待签名字符串进行计算。

  1. 计算SigningKey,使用HMAC-SHA256格式逐步封装,具体参考如下示例。

    说明

    计算过程中HMAC-SHA256所需参数,第一个为KEY,第二个是MESSAGE;

    其中SK为用户个人密钥,Date为当期时间(格式参照ISO8601,例如20231203),Region为请求所在的地区。

    DateKey = HMAC-SHA256("aliyun_v4" + SK, Date);
    DateRegionKey = HMAC-SHA256(DateKey, Region);
    DateRegionServiceKey = HMAC-SHA256(DateRegionKey, "oss");
    SigningKey = HMAC-SHA256(DateRegionServiceKey, "aliyun_v4_request");

    上面这些计算步骤也可以使用一个式子完成封装,示例如下:

    SigningKey = HMAC-SHA256(HMAC-SHA256(HMAC-SHA256(HMAC-SHA256("aliyun_v4" + SK, Date), Region), "oss"), "aliyun_v4_request");
  2. 将上面计算得到的SigningKey与步骤2计算得到的StringToSign进行HMAC-SHA256格式转换,得到Signature。

    Signature = HEX(HMAC-SHA256(SigningKey, StringToSign))

签名计算示例

以PutObject为例,演示如何在Header包含V4签名。

  • 计算示例参数说明

    参数

    AccessKeyId

    accesskeyid

    AccessKeySecret

    accesskeysecret

    Timestamp

    20231203T121212Z

    Bucket

    examplebucket

    Object

    exampleobject

    Region

    cn-hangzhou

  • PutObject

    PUT /exampleobject HTTP/1.1
    Content-MD5: eB5eJF1ptWaXm4bijSPyxw
    Content-Type: text/html
    Date: Sun, 03 Dec 2023 12:12:12 GMT
    Host: examplebucket.oss-cn-hangzhou.aliyuncs.com
    Authorization: SignatureToBeCalculated
    x-oss-date: 20231203T121212Z 
    x-oss-meta-author: alice
    x-oss-meta-magic: abracadabra
    x-oss-content-sha256: UNSIGNED-PAYLOAD
  • 在Header包含V4签名的步骤如下:

    1. 构造出的CanonicalRequest格式如下。

      PUT
      /examplebucket/exampleobject
      
      content-md5:eB5eJF1ptWaXm4bijSPyxw
      content-type:text/html
      host:examplebucket.oss-cn-hangzhou.aliyuncs.com
      x-oss-content-sha256:UNSIGNED-PAYLOAD
      x-oss-date:20231203T121212Z
      x-oss-meta-author:alice
      x-oss-meta-magic:abracadabra
      
      host
      UNSIGNED-PAYLOAD
    2. 构造待签名字符串(StringToSign)格式如下。

      OSS4-HMAC-SHA256
      20231203T121212Z
      20231203/cn-hangzhou/oss/aliyun_v4_request
      129b14df88496f434606e999e35dee010ea1cecfd3ddc378e5ed4989609c1db3
    3. 计算签名。

      1. 计算SigningKey。

        HMAC-SHA256(HMAC-SHA256(HMAC-SHA256(HMAC-SHA256("aliyun_v4" + "accesskeysecret", "20231203"), "cn-hangzhou"), "oss"), "aliyun_v4_request");
      2. 通过计算公式逐层计算得到Signature,结果格式示例如下。

        说明

        计算公式为Signature = HEX(HMAC-SHA256(Signingkey,StringToSign));

        4b663e424d2db9967401ff6ce1c86f8c83cabd77d9908475239d9110642c63fa
    4. 将签名添加到Authorization中。

      OSS4-HMAC-SHA256 Credential=accesskeyid/20231203/cn-hangzhou/oss/aliyun_v4_request,AdditionalHeaders=host,Signature=4b663e424d2db9967401ff6ce1c86f8c83cabd77d9908475239d9110642c63fa

  • 代码示例

import com.aliyun.oss.common.utils.BinaryUtil;
import org.apache.commons.codec.digest.DigestUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;


/**
 * Signature Demo
 */
public class Demo1 {

    /**
     * 签名计算工具
     *
     * @return Authorization
     */
    public static void main(String[] args) throws Exception {
        // 步骤1:构造CanonicalRequest
        String canonicalRequest =
                "PUT\n" +
                        "/examplebucket/exampleobject\n" +
                        "\n" +
                        "content-md5:eB5eJF1ptWaXm4bijSPyxw\n" +
                        "content-type:text/html\n" +
                        "host:examplebucket.oss-cn-hangzhou.aliyuncs.com\n" +
                        "x-oss-content-sha256:UNSIGNED-PAYLOAD\n" +
                        "x-oss-date:20231203T121212Z\n" +
                        "x-oss-meta-author:alice\n" +
                        "x-oss-meta-magic:abracadabra\n" +
                        "\n" +
                        "host\n" +
                        "UNSIGNED-PAYLOAD";

        // 步骤2:构造待签名字符串(StringToSign)
        String stringToSign = "OSS4-HMAC-SHA256\n" +
                "20231203T121212Z\n" +
                "20231203/cn-hangzhou/oss/aliyun_v4_request\n" +
                DigestUtils.sha256Hex(canonicalRequest);

        // 步骤3:计算Signature。
        // "accesskeysecret"填入RAM用户的AccessKeySecret,data参数填入实际日期如"20231203”
        byte[] dateKey = hmacsha256(("aliyun_v4" + "accesskeysecret").getBytes(), "20231203");
        // 参数填入所在地区,如所在地域为杭州则填入"cn-hangzhou”
        byte[] dateRegionKey = hmacsha256(dateKey, "cn-hangzhou");
        byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss");
        byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request");

        byte[] result = hmacsha256(signingKey, stringToSign);
        String signature = BinaryUtil.toHex(result);
        System.out.println("signature:" + signature);

        String authorization = "OSS4-HMAC-SHA256 " +
                "Credential=accesskeyid/20231203/cn-hangzhou/oss/aliyun_v4_request," +
                "AdditionalHeaders=host," +
                "Signature=" + signature;

        System.out.println("Authorization:" + authorization);
    }

    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);
        }
    }
}