Logo德胜云开发者文档

签名&鉴权

认证方式、签名计算步骤与示例代码说明。

说明

  • API 需要通过设置 Authorization 请求头进行鉴权。
  • API 携带 Body 时,HTTP 请求头需要设置 Content-Type 为 application/json。
  • API 请求地址为 https://api.dizcloud.com

鉴权流程

1. 构建签名字符串

鉴权字符串由三部分组成:

1.1 Host

s1 = "Host:" + " " + req.Host

req.Host 表示请求的 Host 部分。

1.2 Method + URI + Query

当未携带 `query` 参数时:

s2 = req.Method + " " + req.URL.Path

当携带 `query` 参数时:

s2 = req.Method + " " + req.URL.Path + "?" + req.URL.RawQuery

req.Method 表示请求方法,如:GET、POST 等,注意这里是大写;req.URL.Path 表示请求的 PATH 部分,行如:"/foo/bar";req.URL.RawQuery 表示编码后的查询参数,采用 key=value 的形式,多个参数以 "&" 符号连接。

1.3 Body(可选)

body 部分可选,只有 body 存在时才参与鉴权。

假设这里携带 JSON string body(即请求体原始内容字符串):

s3 = {"foo": 1, "bar": 2}

这三部分用 "\n" 符号连接起来构成一个最终的鉴权字符串。

signStr = s1 + "\n" + s2 + "\n" + s3

如果不携带 body,则:

signStr = s1 + "\n" + s2 + "\n"

2. 计算鉴权 Token

Token 的形式为:

accessKeyID:signature

其中 accessKeyID 替换为 accessKeySecret 配套的 KeyID,signature 表示具体的签名值。

签名值的计算采用 HMAC 加 SHA-1,签名结果需要进行 base64 编码:

h := hmac.New(sha1.New, AccessKeySecret)
h.Write([]byte(signStr))
signature := base64.URLEncoding.EncodeToString(h.Sum(nil))
token := AccessKeyID + ":" + signature

3. 设置 Token 到请求头

req.Header.Set("Authorization", token)

鉴权示例

示例请求信息

假设当前请求信息为:

Host: api.dizcloud.com

Method: POST

URI: /api/foo

Query: foo=1&bar=hello

Body: {"content": 123}

则 s1 、s2、s3 分别为:

s1: Host: api.dizcloud.com
s2: POST /api/foo?foo=1&bar=hello
s3: {"content": 123}

假设 AccessKeyID、AccessKeySecret 分别为:

const AccessKeyID = "accessKeyID"
const AccessKeySecret = "accessKeySecret"

计算得到最终的 token 为:

accessKeyID:JnHNAjpYQSV70A9IFVRINHIDrZc=

参考实现

Golang 签名函数参考

// 对数据进行签名, 生成token
func GenerateSignToken(req *http.Request) (token string, err error) {
    data, err := generateData(req)
    if err != nil {
        return
    }
    token = sign(data)
    return
}

// sign 对数据进行签名
func sign(data []byte) (token string) {
    h := hmac.New(sha1.New, []byte(AccessKeySecret))
    h.Write(data)
    sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
    return fmt.Sprintf("%s:%s", AccessKeyID, sign)
}

func generateData(req *http.Request) (data []byte, err error) {
    u := req.URL
    // part1 host
    s := "Host: " + req.Host
    s += "\n"
    // part2 method + uri + 参数
    s += fmt.Sprintf("%s %s", req.Method, u.Path)
    if u.RawQuery != "" {
        s += "?"
        s += u.RawQuery
    }
    s += "\n"
    data = []byte(s)
    // part3 body
    if incBody(req) {
        b, rErr := bytesFromRequestBody(req)
        if rErr != nil {
            err = rErr
            return
        }
        req.Body = io.NopCloser(bytes.NewReader(b))
        data = append(data, b...)
    }
    return
}

func incBody(req *http.Request) bool {
    contentType := req.Header.Get("Content-Type")
    return req.Body != nil && contentType == "application/json"
}

func bytesFromRequestBody(r *http.Request) (b []byte, err error) {
    if r.ContentLength == 0 {
        return
    }
    if r.ContentLength > 0 {
        b = make([]byte, int(r.ContentLength))
        _, err = io.ReadFull(r.Body, b)
        return
    }
    return io.ReadAll(r.Body)
}

Java 签名函数参考

// 示例URL: http://api.dizcloud.com/foo/bar?name=world&age=10
// POST body: {"age":10,"name":"world"}

private static String buildToken() throws Exception {
    String ak = "ak";
    String sk = "sk";
    String part1 = String.format("Host: %s", "api.dizcloud.com");
    String part2 = String.format("%s %s?%s", "POST", "/foo/bar", "name=world&age=10");
    String part3 = "{\"age\":10,\"name\":\"world\"}";
    String signSource = part1 + "\n" + part2 + "\n" + part3;
    Mac mac = Mac.getInstance("HmacSHA1");
    SecretKeySpec spec = new SecretKeySpec(sk.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
    mac.init(spec);
    byte[] sign = mac.doFinal(signSource.getBytes());
    String encodedSign = Base64.getUrlEncoder().encodeToString(sign);
    return ak + ":" + encodedSign;
}

On this page