百度 AIP 開放平台使用 OAuth2.0 授權調用開放 API,調用 API 時必須在 URL 中帶上 access_token 參數。
請求 URL 數據格式
授權服務地址:https://aip.baidubce.com/oauth/2.0/token
請求參數如下:
- grant_type: 必須參數,固定為 client_credentials;
 - client_id: 必須參數,應用的 API Key;
 - client_secret: 必須參數,應用的 Secret Key;
 
獲取結果
服務器返回的JSON文本參數如下:
- access_token: 要獲取的 Access Token;
 - expires_in: Access Token 的有效期(秒為單位,一般為 1 個月);
 - 其他參數忽略,暫時不用;
 
以下代碼為示例:
{
  "refresh_token": "25.b55fe1d287227ca97aab219bb249b8ab.315360000.1798284651.282335-8574074",
  "expires_in": 2592000,
  "scope": "public wise_adapt",
  "session_key": "9mzdDZXu3dENdFZQurfg0Vz8slgSgvvOAUebNFzyzcpQ5EnbxbF+hfG9DQkpUVQdh4p6HbQcAiz5RmuBAja1JJGgIdJI",
  "access_token": "24.6c5e1ff107f0e8bcef8c46d3424a0e78.2592000.1485516651.282335-8574074",
  "session_secret": "dfac94a3489fe9fca7c3221cbf7525ff"
}
 
         
         
        若請求錯誤,服務器將返回的 JSON 文本包含以下參數:
- error: 錯誤碼;關於錯誤碼的詳細信息請參考下方鑒權認證錯誤碼。
 - error_description: 錯誤描述信息,幫助理解和解決發生的錯誤。
 
以下為請求錯誤返回結果:
{
    "error": "invalid_client",
    "error_description": "unknown client id"
}
 
        | error | error_description | 解釋 | 
|---|---|---|
| invalid_client | unknown client id | API Key不正確 | 
| invalid_client | Client authentication failed | Secret Key不正確 | 
C++ 代碼
#include <curl/curl.h>
#include <string>
#include <map>
#include <iostream>
#include <fstream>
#include <json/json.h>
// callback function for curl
size_t writeCallback(void *ptr, size_t size, size_t nmemb, void *userdata)
{
	std::string *str = dynamic_cast<std::string *>((std::string *)userdata);
	str->append((char *)ptr, size * nmemb);
	return size * nmemb;
}
// get access token from server by get method
std::string getTokenKey() {
	std::string url = "https://aip.baidubce.com/oauth/2.0/token";
	std::string apikey = "這里更改為對應應用的 API Key";
	std::string secritkey = "這里更改為對應應用的 Secrit Key";
	std::map<std::string, std::string> params;
	std::string response;
	params["grant_type"] = "client_credentials";
	params["client_id"] = apikey;
	params["client_secret"] = secritkey;
	// append url with parameters
	for (auto it = params.begin(); it != params.end(); ++it) {
		url += (it == params.begin() ? "?" : "&") + it->first + "=" + it->second;
	}
	CURL *curl = curl_easy_init();
	struct curl_slist * slist = NULL;
	curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);  // set callback function
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); // set var to receive return info from callback function
	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, true);
	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);
	curl_easy_setopt(curl, CURLOPT_VERBOSE, false);
	int status_code = curl_easy_perform(curl);
	curl_easy_cleanup(curl);
	curl_slist_free_all(slist);
	Json::Value obj;
	if (status_code != CURLcode::CURLE_OK) {
		obj["curl_error_code"] = status_code;
		return obj.toStyledString();
	}
	// parse json string
	JSONCPP_STRING error;
	Json::CharReaderBuilder builder;
	const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
	reader->parse(response.data(), response.data() + response.size(), &obj, &error);
	std::string access_token = obj["access_token"].asString();
	return access_token;
}
// write messages to file
int write_string_to_file_append(const std::string & file_string, const std::string str)
{
	std::ofstream OsWrite(file_string, std::ofstream::app);
	OsWrite << str;
	OsWrite << std::endl;
	OsWrite.close();
	return 0;
}
int main(int argc, char *argv[])
{
	std::string tokenKey;
	tokenKey = getTokenKey();
	std::cout << "Token Key: " << tokenKey << "\n";
	std::string filename = "tokenKey.db";
	write_string_to_file_append(filename, tokenKey);
	system("pause");
	exit(EXIT_SUCCESS);
}
 
         
         
        代碼分析
std::string url = "https://aip.baidubce.com/oauth/2.0/token";
std::string apikey = "這里更改為對應應用的 API Key";
std::string secritkey = "這里更改為對應應用的 Secrit Key";
std::map<std::string, std::string> params;
std::string response;
params["grant_type"] = "client_credentials";
params["client_id"] = apikey;
params["client_secret"] = secritkey;
// append url with parameters
for (auto it = params.begin(); it != params.end(); ++it) {
    url += (it == params.begin() ? "?" : "&") + it->first + "=" + it->second;
}
 
        上面這段代碼主要用於獲取最終的請求 URL。因為這里使用的是 get 方法來獲取 access token,所以需要將所有參數添加到 URL 中。params 用於存儲請求參數,response 表示請求結果。for 循環則是將各個參數和 URL 使用 ? 和 & 連接起來。最終 URL 的一個示例如下:
https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=Va5yQRHlA4Fq5eR3LT0vuXV4&client_secret=0rDSjzQ20XUj5itV6WRtznPQSzr5pVw2&
 
        CURL *curl = curl_easy_init();
struct curl_slist * slist = NULL;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);  // set callback function
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); // set var to receive return info from callback function
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, true);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);
curl_easy_setopt(curl, CURLOPT_VERBOSE, false);
int status_code = curl_easy_perform(curl);
curl_easy_cleanup(curl);
curl_slist_free_all(slist);
 
        上面這段代碼主要是對 URL 請求進行設置,並請求,結果通過回調函數返回給 response。關於這里面的部分函數的使用方法可以參考:The Easy interface
curl_easy_init 函數必須是第一個要調用的函數,並且它返回一個 CURL 類型的簡易句柄,必須將該 CURL 簡易句柄用作 easy 接口中其他函數的輸入。
curl_easy_setopt 用來告訴libcurl如何表現。通過設置適當的選項,應用程序可以更改libcurl的行為。這里設置的幾個參數解釋如下:
| options | 說明 | 
|---|---|
| CURLOPT_URL | URL to work on | 
| CURLOPT_HTTPHEADER | Custom HTTP headers | 
| CURLOPT_WRITEFUNCTION | Callback for writing data | 
| CURLOPT_WRITEDATA | Data pointer to pass to the write callback | 
| CURLOPT_NOSIGNAL | Do not install signal handlers | 
| CURLOPT_SSL_VERIFYPEER | Verify the SSL certificate | 
| CURLOPT_SSL_VERIFYHOST | Verify the host name in the SSL certificate | 
| CURLOPT_VERBOSE | Display verbose information | 
更多 Curl 的 options 參考 curl_easy_setopt。
// callback function for curl
size_t writeCallback(void *ptr, size_t size, size_t nmemb, void *userdata)
{
	std::string *str = dynamic_cast<std::string *>((std::string *)userdata);
	str->append((char *)ptr, size * nmemb);
	return size * nmemb;
}
 
        上面這段代碼是回調函數,一旦收到需要保存的數據,libcurl 就會立即調用此回調函數。對於大多數傳輸,此回調將被調用多次,每次調用都會傳遞另一塊數據。ptr 指向傳遞的數據,該數據的大小為 nmemb;大小始終為 1。關於該函數的使用說明可以參考 CURLOPT_WRITEFUNCTION explained 和 getinmemory.c。
Json::Value obj;
if (status_code != CURLcode::CURLE_OK) {
    obj["curl_error_code"] = status_code;
    return obj.toStyledString();
}
// parse json string
JSONCPP_STRING error;
Json::CharReaderBuilder builder;
const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
reader->parse(response.data(), response.data() + response.size(), &obj, &error);
std::string access_token = obj["access_token"].asString();
 
        上面這段代碼主要將字符串轉換為 Json 格式,然后獲取 access_token 值。
另外我們在調用接口獲取到 access token 后,可以將其存儲到文件中,因為每個 access token 的有效期為 30 天,所以可以重復使用直到過期。以下代碼為將字符串寫入文件和從文件讀入。
int writeFile(const std::string & fileString, const std::string &str) {
	std::ofstream out(fileString, std::ios::binary);
	if (out.is_open()) {
		out << str;
		out.close();
	}
	return 0;
}
int readFile(const std::string & fileString, std::string &str) {
	std::ifstream in(fileString);
	if (!in.is_open()) {
		str = "";
		return -1;
	}
	char buffer[256];
	while (!in.eof()) {
		in.getline(buffer, sizeof(buffer));	
	}
	str = buffer;
	return 0;
}
 
       