签名&鉴权
认证方式、签名计算步骤与示例代码说明。
说明
- API 需要通过设置 Authorization 请求头进行鉴权。
- API 携带 Body 时,HTTP 请求头需要设置 Content-Type 为 application/json。
- API 请求地址为
https://api.dizcloud.com
鉴权流程
1. 构建签名字符串
鉴权字符串由三部分组成:
1.1 Host
s1 = "Host:" + " " + req.Hostreq.Host 表示请求的 Host 部分。
1.2 Method + URI + Query
当未携带 `query` 参数时:
s2 = req.Method + " " + req.URL.Path当携带 `query` 参数时:
s2 = req.Method + " " + req.URL.Path + "?" + req.URL.RawQueryreq.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 + ":" + signature3. 设置 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;
}