(基礎篇 03)C++ 獲取 access token

百度 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

更多 Curloptions 參考 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 explainedgetinmemory.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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章