apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

http://ai.baidu.com/docs#/Begin/top

一、访问入口

apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

二、鉴权方式分析

1、鉴权认证方式一 - access_token - 针对HTTP API调用者

apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

2、鉴权认证方式二 - API认证 - 针对RESTful API调用者。

2.1、获取ak和sk

AK(Access Key ID)/SK(Secret Access Key),主要用于对用户的调用行为进行鉴权和认证,相当于百度云API专用的用户名及密码。有关API认证的详细介绍,请参看API认证机制

apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

2.2、使用 认证方式和匿名方式 鉴权

用户可以通过两种方式与百度云进行交互,包括认证方式和匿名方式。

对于认证方式,需要通过使用Access Key Id / Secret Access Key加密的方法来验证某个请求的发送者身份。

Access Key Id(AK)用于标示用户,Secret Access Key(SK)是用户用于加密认证字符串和百度云用来验证认证字符串的密钥,其中SK必须保密,只有用户和百度云知道。

当百度云接收到用户的请求后,系统将使用相同的SK和同样的认证机制生成认证字符串,并与用户请求中包含的认证字符串进行比对。

如果认证字符串相同,系统认为用户拥有指定的操作权限,并执行相关操作;

如果认证字符串不同,系统将忽略该操作并返回错误码。

apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

2.3、认证字符串生成机制

1 生成认证字符串的方法

1.1 概述

在生成认证字符串之前,首先需要生成Signature。为了生成Signature,用户需要首先构建CanonicalRequest并计算SigningKey,具体内容如下图所示:

apigw鉴权分析(1-3)百度 AI - 鉴权方式分析

下表介绍了上图中涉及的函数:

函数名 功能描述
HMAC-SHA256-HEX() 调用HMAC SHA256算法,根据开发者提供的密钥(key)和密文(message)输出密文摘要,并把结果转换为小写形式的十六进制字符串。
Lowercase() 将字符串全部变成小写。
Trim() 去掉字符串开头和结尾的空白字符。
UriEncode() RFC 3986规定,"URI非保留字符"包括以下字符:字母(A-Z,a-z)、数字(0-9)、连字号(-)、点号(.)、下划线(_)、波浪线(~),算法实现如下:
1. 将字符串转换成UTF-8编码的字节流
2. 保留所有“URI非保留字符”原样不变
3. 对其余字节做一次RFC 3986中规定的百分号编码(Percent-encoding),即一个“%”后面跟着两个表示该字节值的十六进制字母,字母一律采用大写形式。
UriEncodeExceptSlash() 与UriEncode() 类似,区别是斜杠(/)不做编码。一个简单的实现方式是先调用UriEncode(),然后把结果中所有的`%2F`都替换为`/`

UriEncode()函数参考代码如下:

public static String uri-encode(CharSequence input, boolean encodeSlash) {;
          StringBuilder result = new StringBuilder();;
          for (int i = 0; i < input.length(); i++) {;
              char ch = input.charAt(i);;
              if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' || ch == '.') {;
                  result.append(ch);;
              } else if (ch == '/') {;
                  result.append(encodeSlash ? "%2F" : ch);;
              } else {;
                  result.append(toHexUTF8(ch));;
              };
          };
          return result.toString();;
};

1.2 生成CanonicalRequest

CanonicalRequest = HTTP Method + "\n" + CanonicalURI + "\n" + CanonicalQueryString + "\n" + CanonicalHeaders,其中:

  • HTTP Method:指HTTP协议中定义的GET、PUT、POST等请求,必须使用全大写的形式。百度云API所涉及的HTTP Method有五种:

    • GET
    • POST
    • PUT
    • DELETE
    • HEAD
  • CanonicalURI:是对URL中的绝对路径进行编码后的结果。要求绝对路径必须以“/”开头,不以“/”开头的需要补充上,空路径为“/”,即CanonicalURI = UriEncodeExceptSlash(Path)

    相关举例:

    若URL为https://bos.cn-n1.baidubce.com/example/测试,则其URL Path为/example/测试,将之规范化得到CanonicalURI = /example/%E6%B5%8B%E8%AF%95

  • CanonicalQueryString:对于URL中的Query String(Query String即URL中“?”后面的“key1 = valve1 & key2 = valve2 ”字符串)进行编码后的结果。

    编码方法为:

    1. 将Query String根据&拆开成若干项,每一项是key=value或者只有key的形式。
    2. 对拆开后的每一项进行如下处理:
      • 对于keyauthorization,直接忽略。
      • 对于只有key的项,转换为UriEncode(key) + "="的形式。
      • 对于key=value的项,转换为 UriEncode(key) + "=" + UriEncode(value) 的形式。这里value可以是空字符串。
    3. 将上面转换后的所有字符串按照字典顺序排序。
    4. 将排序后的字符串按顺序用 & 符号链接起来。

    相关举例:

    若URL为https://bos.cn-n1.baidubce.com/example?text&text1=测试&text10=test,在这个例子中Query String是 text&text1=测试&text10=test

    1. 根据 & 拆开成 text 、text1=测试 和 text10=test 三项。
    2. 对每一项进行处理:
      • text => UriEncode("text") + "=" => text=
      • text1=测试 => UriEncode("text1") + "=" + UriEncode("测试") => text1=%E6%B5%8B%E8%AF%95
      • text10=test => UriEncode("text10") + "=" + UriEncode("test")=> text10=test
    3. 对 text= 、 text1=%E6%B5%8B%E8%AF%95 和 text10=test 按照字典序进行排序。它们有共同前缀text,但是=的ASCII码比所有数字的ASCII码都要大,因此 text1=%E6%B5%8B%E8%AF%95 和 text10=test 排在 text=的前面。同样,text10=test 要排在 text1=%E6%B5%8B%E8%AF%95 之前。最终结果是 text10=test 、text1=%E6%B5%8B%E8%AF%95 、text= 。
    4. 把排序好的三项 text10=test 、 text1=%E6%B5%8B%E8%AF%95 、 text= 用&连接起来得到 text10=test&text1=%E6%B5%8B%E8%AF%95&text=

    说明:

    在这个特殊构造的例子里,我们展示了如何处理只有key的项,非英文的value,以及数字和=进行排序。在实际的BCE API中,因为参数起名是规范的,基本不会遇到这样的排序。正常的排序结果和只按照 key进行排序是完全一致的。算法中有这个约束主要是出于定义严密性的考虑。*

  • CanonicalHeaders:对HTTP请求中的Header部分进行选择性编码的结果。

    您可以自行决定哪些Header 需要编码。百度云API的唯一要求是Host域必须被编码。大多数情况下,我们推荐您对以下Header进行编码:

    • Host
    • Content-Length
    • Content-Type
    • Content-MD5
    • 所有以 x-bce- 开头的Header

    如果这些Header没有全部出现在您的HTTP请求里面,那么没有出现的部分无需进行编码。

    如果您按照我们的推荐范围进行编码,那么认证字符串中的 {signedHeaders} 可以直接留空,无需填写。

    您也可以自行选择自己想要编码的Header。如果您选择了不在推荐范围内的Header进行编码,或者您的HTTP请求包含了推荐范围内的Header但是您选择不对它进行编码,那么您必须在认证字符串中填写 {signedHeaders} 。填写方法为,把所有在这一阶段进行了编码的Header名字转换成全小写之后按照字典序排列,然后用分号(;)连接。

    选择哪些Header进行编码不会影响API的功能,但是如果选择太少则可能遭到中间人攻击。

    对于每个要编码的Header进行如下处理:

    1. 将Header的名字变成全小写。
    2. 将Header的值去掉开头和结尾的空白字符。
    3. 经过上一步之后值为空字符串的Header忽略,其余的转换为 UriEncode(name) + ":" + UriEncode(value) 的形式。
    4. 把上面转换后的所有字符串按照字典序进行排序。
    5. 将排序后的字符串按顺序用\n符号连接起来得到最终的CanonicalQueryHeaders

    注意:很多发送HTTP请求的第三方库,会添加或者删除你指定的header(例如:某些库会删除content-length:0这个header),如果签名错误,请检查你您真实发出的http请求的header,看看是否与签名时的header一样。

    相关举例1:

    不使用signedHeaders默认值。若希望对 Date 进行编码,但是不希望对 x-bce-date 进行编码,则需要用到signedHeaders

    要编码的Header如下:

    Host: bj.bcebos.com
    Date: Mon, 27 Apr 2015 16:23:49 +0800
    Content-Type: text/plain
    Content-Length: 8
    Content-Md5: NFzcPqhviddjRNnSOGo4rw==
    1. 首先把所有名字都改为小写。
      host: bj.bcebos.com
      date: Mon, 27 Apr 2015 16:23:49 +0800
      content-type: text/plain
      content-length: 8
      content-md5: NFzcPqhviddjRNnSOGo4rw==
    2. 将Header的值去掉开头和结尾的空白字符。
      host:bj.bcebos.com
      date:Mon, 27 Apr 2015 16:23:49 +0800
      content-type:text/plain
      content-length:8
      content-md5:NFzcPqhviddjRNnSOGo4rw==
    3. 做UriEncode。
      host:bj.bcebos.com
      date:Mon%2C%2027%20Apr%202015%2016%3A23%3A49%20%2B0800
      content-type:text%2Fplain
      content-length:8
      content-md5:NFzcPqhviddjRNnSOGo4rw==
    4. 把上面转换后的所有字符串按照字典序进行排序。
      content-length:8
      content-md5:NFzcPqhviddjRNnSOGo4rw==
      content-type:text%2Fplain
      date:Mon%2C%2027%20Apr%202015%2016%3A23%3A49%20%2B0800
      host:bj.bcebos.com
    5. 将排序后的字符串按顺序用\n符号连接起来得到最终结果。
      content-length:8
      content-md5:NFzcPqhviddjRNnSOGo4rw==
      content-type:text%2Fplain
      date:Mon%2C%2027%20Apr%202015%2016%3A23%3A49%20%2B0800
      host:bj.bcebos.com

      这时候认证字符串的signedHeaders内容应该是content-length;content-md5;content-type;date;host

    相关举例2:

    CanonicalHeaders的排序和signedHeaders排序不一致。

    CanonicalQueryString的示例一样,CanonicalHeaders也需要考虑排序的特殊情况。因为BCE API存在允许用户自定义Header的情况,所以这里需要特别注意。

    在BOS的PutObject中允许用户上传自定义meta,为了简明介绍,我们省略大部分Header,假设要编码的Headers如下:

    Host: bj.bcebos.com
    x-bce-meta-data: my meta data
    x-bce-meta-data-tag: description

    最终得到的CanonicalHeaders是:

    host:bj.bcebos.com
    x-bce-meta-data-tag:description
    x-bce-meta-data:my%20meta%20data

    signedHeadershost;x-bce-meta-data;x-bce-meta-data-tag

    从字符串比较的角度来说 x-bce-meta-data 应该放在 x-bce-meta-data-tag 之前,因此signedHeaders里面遵循了这个排序。但是CanonicalHeaders中因为在namevalue之间还有一个冒号(:),而冒号的ASCII码值要大于连字号(-)的ASCII码值,因此x-bce-meta-data反而放在了x-bce-meta-data-tag之后。

1.3 生成SigningKey

SigningKey = HMAC-SHA256-HEX(sk, authStringPrefix),其中:

  • sk为用户的Secret Access Key,可以通过在控制台中进行查询,关于SK的获取方法,请参看获取AK/SK
  • authStringPrefix代表认证字符串的前缀部分,即:bce-auth-v1/{accessKeyId}/{timestamp}/{expirationPeriodInSeconds}

1.4 生成Signature

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

1.5 生成认证字符串

认证字符串 = bce-auth-v1/{accessKeyId}/{timestamp}/{expirationPeriodInSeconds}/{signedHeaders}/{signature}

  • timestamp:签名生效UTC时间,格式为yyyy-mm-ddThh:mm:ssZ,例如:2015-04-27T08:23:49Z,默认值为当前时间。
  • expirationPeriodInSeconds:签名有效期限,从timestamp所指定的时间开始计算,时间为秒,默认值为1800秒(30)分钟。
  • signedHeaders:签名算法中涉及到的HTTP头域列表。HTTP头域名字一律要求小写且头域名字之间用分号(;)分隔,如host;range;x-bce-date。列表按照字典序排列。当signedHeaders为空时表示取默认值。

2 认证字符串生成举例

为了便于用户理解认证字符串的生成过程,我们将使用一个例子来详细介绍算法的生成步骤。

假设用户向北京的BOS集群使用UploadPart接口上传一个文件的最后一个Part,内容为Example

  • Bucket name:test
  • Object key:myfolder/readme.txt
  • uploadId:a44cc9bab11cbd156984767aad637851
  • partNumber:9
  • Access Key ID:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  • Secret Access Key:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
  • 时间:北京时间2015年4月27日16点23分49秒(转换为UTC时间是2015年4月27日8点23分49秒)

则其HTTP请求如下:

PUT /v1/test/myfolder/readme.txt?partNumber=9&uploadId=a44cc9bab11cbd156984767aad637851 HTTP/1.1
Host: bj.bcebos.com
Date: Mon, 27 Apr 2015 16:23:49 +0800
Content-Type: text/plain
Content-Length: 8
Content-Md5: NFzcPqhviddjRNnSOGo4rw==
x-bce-date: 2015-04-27T08:23:49Z Example

A. 生成CanonicalRequest

PUT
/v1/test/myfolder/readme.txt
partNumber=9&uploadId=a44cc9bab11cbd156984767aad637851
content-length:8
content-md5:NFzcPqhviddjRNnSOGo4rw==
content-type:text%2Fplain
host:bj.bcebos.com
x-bce-date:2015-04-27T08%3A23%3A49Z

B. 生成SigningKey

SigningKey = HMAC-SHA256-HEX(sk, authStringPrefix) 
= HMAC-SHA256-HEX("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "bce-auth-v1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/2015-04-27T08:23:49Z/1800")
最终得到:SigningKey = 1d5ce5f464064cbee060330d973218821825ac6952368a482a592e6615aef479

C. 生成Signature

Signature = HMAC-SHA256-HEX(SigningKey, CanonicalRequest)
= 8566237931756474409b68828a8175d0a3dde00359560e5cf6adccdb09a195e0

D. 生成认证字符串

Authorization = bce-auth-v1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/2015-04-27T08:23:49Z/1800//b02547aedd91dc438d6bb6d1be68b35917828271b340b9e7d1a7bcd051869054

注意:

超时时间1800与签名结果之间为两个”/”,含义是使用默认签名方式,signedHeaders内容留空。

三、分析结论

1、方式一:签名方式

2、方式二:oath2方式

上一篇:ICG游戏:尼姆游戏异或解法的证明


下一篇:POJ 1006 Biorhythnms(中国剩余定理)