URI參數簽名算法


簡介

應用基於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&timestamp=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&timestamp=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請求中傳遞的內容都是字符串類型的,很多開發者都因為沒注意到這點,直接將非字符串類型的參數的二進制值傳遞了進去,結果導致簽名與服務端計算的不一致而出錯。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM