簡介
應用基於HTTP POST或HTTP GET請求發送Open API調用請求時,為了確保應用與百度REST服務器之間的安全通信,防止Secret Key盜用、數據篡改等惡意攻擊行為,百度REST服務器使用了參數簽名機制。應用在調用百度Open API之前,需要為其所有請求參數計算一個MD5簽名,並追加到請求參數中,參數名為“sign”。百度REST服務器在接收到請求時會重新計算簽名,並判斷其值是否與應用傳遞過來的sign參數值一致,以此判定當前Open API調用請求是否是被第三者偽造或篡改。
應用在調用Open API之前需要通過 百度OAuth2.0服務獲得用戶或平台的授權,獲取到授權后將會拿到以下3個重要參數:
- access_token:基於https調用Open API時所需要的訪問授權碼;
- session_key:基於http調用Open API時所需要的訪問授權碼;
- session_secret:基於http調用Open API時計算參數簽名用的簽名密鑰。
其中,session_secret這個參數就是做參數簽名時所需要的簽名密鑰。這與Facebook、人人網等平台稍微有所區別,這兩個平台在做參數簽名時所用的簽名密鑰一般有2個:
- 如果是通過應用服務端調用Open API,則注冊應用時所拿到的應用密鑰(即API Key)就是參數簽名密鑰;
- 如果是通過JavaScript、ActionScript等客戶端語言調用Open API,則應用獲取到用戶授權后所拿到的Session Secret就是參數簽名密鑰。當然,通過服務端調用Open API時也可以用Session Secret作為簽名密鑰。
簽名算法
假設參與參數簽名計算的請求參數分別是“k1”、“k2”、“k3”,它們的值分別是“v1”、“v2”、“v3”,則參數簽名計算方法如下:
- 將請求參數格式化為“key=value”格式,即“k1=v1”、“k2=v2”、“k3=v3”;
- 將格式化好的參數鍵值對以字典序升序排列后,拼接在一起,即“k1=v1k2=v2k3=v3”;
- 在拼接好的字符串末尾追加上應用通過百度OAuth2.0協議獲取Access Token時所獲取到的session_secret參數值;
- 上述字符串的MD5值即為簽名的值。
注意:計算簽名時的請求參數中不要包含sign(簽名)參數,因為sign參數的值此時還不知道,有待計算。
另外,計算簽名的時候不需要對參數進行urlencode處理(“application/x-www-form-urlencoded”編碼),但是發送請求的時候需要進行urlencode處理,這是很多開發者最容易犯錯的地方。
簽名過程示例
假設某個應用需要獲取某個uid為67411167的用戶的基本資料,應用在之前的通過百度OAuth2.0服務獲取Access Token的過程中所拿到的session_key和session_secret參數值分別為:
- session_key: "9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A="
- session_secret: "27e1be4fdcaa83d7f61c489994ff6ed6"
調用Open API時的系統時間(PHP中可以通過date('Y-m-d H:i:s')來獲取當前系統時間)為"2011-06-21 17:18:09",希望REST服務器以JSON格式返回調用結果,即相當於參與參數簽名計算的請求參數集合為:
[ "session_key" => "9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A=", "timestamp" => "2011-06-21 17:18:09", "format" => "json", "uid" => 67411167 ]
則計算簽名的具體過程如下:
- 將請求參數格式化為“key=value”格式,格式化后的請求參數集合為:
[ "session_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A=", "timestamp=2011-06-21 17:18:09", "format=json", "uid=67411167" ]
- 將格式化好的參數鍵值對以字典序升序排列,得到如下參數集:
[ "format=json", "session_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A=", "timestamp=2011-06-21 17:18:09", "uid=67411167" ]
- 將前面排序好的參數集拼接在一起,得到如下字符串:
format=jsonsession_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A=timestamp=2011-06-21 17:18:09uid=67411167
- 在拼接好的字符串末尾追加上應用通過百度OAuth2.0協議獲取Access Token時所獲取到的session_secret參數值,得到如下字符串:
format=jsonsession_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A=timestamp=2011-06-21 17:18:09uid=6741116727e1be4fdcaa83d7f61c489994ff6ed6
- 對前面得到的字符串求MD5簽名,得到的d24dd357a95a2579c410b3a92495f009就是調用API時所需要的sign參數值。
接下來便可以通過HTTP POST方法或HTTP GET方法請求百度Open API的REST服務器,進行接口調用了,如:
GET /rest/2.0/passport/users/getInfo?session_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A%3D×tamp=2011-06-21+17%3A18%3A09&format=json&uid=67411167&sign=d24dd357a95a2579c410b3a92495f009 HTTP/1.1 Host: openapi.baidu.com User-Agent: Client of Baidu Open Platform Accept: */* Accept-Encoding: gzip,deflate Accept-Charset: utf-8 Connection: close 或 POST /rest/2.0/passport/users/getInfo HTTP/1.1 Host: openapi.baidu.com User-Agent: Client of Baidu Open Platform Accept: */* Accept-Encoding: gzip,deflate Accept-Charset: utf-8 Content-Length: 179 Connection: close session_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A%3D×tamp=2011-06-21+17%3A18%3A09&format=json&uid=67411167&sign=d24dd357a95a2579c410b3a92495f009
簽名算法實現代碼
PHP代碼實現
獲取簽名的PHP代碼實現方式如下所示:
/** * 簽名生成算法 * @param array $params API調用的請求參數集合的關聯數組,不包含sign參數 * @param string $secret 簽名的密鑰即獲取access token時返回的session secret * @return string 返回參數簽名值 */ function getSignature($params, $secret) { $str = ''; //待簽名字符串 //先將參數以其參數名的字典序升序進行排序 ksort($params); //遍歷排序后的參數數組中的每一個key/value對 foreach ($params as $k => $v) { //為key/value對生成一個key=value格式的字符串,並拼接到待簽名字符串后面 $str .= "$k=$v"; } //將簽名密鑰拼接到簽名字符串最后面 $str .= $secret; //通過md5算法為簽名字符串生成一個md5簽名,該簽名就是我們要追加的sign參數值 return md5($str); }
調用示例:
$uid = 67411167; $params = array( "session_key" => "9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A=", "timestamp" => "2011-06-21 17:18:09", "format" => "json", "uid" => $uid, ); $sign = getSignature($params, "27e1be4fdcaa83d7f61c489994ff6ed6");
Java代碼實現
獲取簽名的java代碼實現方式如下所示:
/** * 簽名生成算法 * @param HashMap<String,String> params 請求參數集,所有參數必須已轉換為字符串類型 * @param String secret 簽名密鑰 * @return 簽名 * @throws IOException */ public static String getSignature(HashMap<String,String> params, String secret) throws IOException { // 先將參數以其參數名的字典序升序進行排序 Map<String, String> sortedParams = new TreeMap<String, String>(params); Set<Entry<String, String>> entrys = sortedParams.entrySet(); // 遍歷排序后的字典,將所有參數按"key=value"格式拼接在一起 StringBuilder basestring = new StringBuilder(); for (Entry<String, String> param : entrys) { basestring.append(param.getKey()).append("=").append(param.getValue()); } basestring.append(secret); // 使用MD5對待簽名串求簽 byte[] bytes = null; try { MessageDigest md5 = MessageDigest.getInstance("MD5"); bytes = md5.digest(basestring.toString().getBytes("UTF-8")); } catch (GeneralSecurityException ex) { throw new IOException(ex); } // 將MD5輸出的二進制結果轉換為小寫的十六進制 StringBuilder sign = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(bytes[i] & 0xFF); if (hex.length() == 1) { sign.append("0"); } sign.append(hex); } return sign.toString(); }
注意:計算簽名時所有參數的key和value都必須先轉換為對應的字符串類型,因為在HTTP請求中傳遞的內容都是字符串類型的,很多開發者都因為沒注意到這點,直接將非字符串類型的參數的二進制值傳遞了進去,結果導致簽名與服務端計算的不一致而出錯。
C#代碼實現
獲取簽名的C#代碼實現方式如下所示:
/// <summary> /// 計算參數簽名 /// </summary> /// <param name="params">請求參數集,所有參數必須已轉換為字符串類型</param> /// <param name="secret">簽名密鑰</param> /// <returns>簽名</returns> public static string getSignature(IDictionary<string, string> parameters, string secret) { // 先將參數以其參數名的字典序升序進行排序 IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters); IEnumerator<KeyValuePair<string, string>> iterator= sortedParams.GetEnumerator(); // 遍歷排序后的字典,將所有參數按"key=value"格式拼接在一起 StringBuilder basestring= new StringBuilder(); while (iterator.MoveNext()) { string key = iterator.Current.Key; string value = iterator.Current.Value; if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value)){ basestring.Append(key).Append("=").Append(value); } } basestring.Append(secret); // 使用MD5對待簽名串求簽 MD5 md5 = MD5.Create(); byte[] bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(basestring.ToString())); // 將MD5輸出的二進制結果轉換為小寫的十六進制 StringBuilder result = new StringBuilder(); for (int i = 0; i < bytes.Length; i++) { string hex = bytes[i].ToString("x"); if (hex.Length == 1) { result.Append("0"); } result.Append(hex); } return result.ToString(); }
注意:計算簽名時所有參數的key和value都必須先轉換為對應的字符串類型,因為在HTTP請求中傳遞的內容都是字符串類型的,很多開發者都因為沒注意到這點,直接將非字符串類型的參數的二進制值傳遞了進去,結果導致簽名與服務端計算的不一致而出錯。