示例代码

如果您直接访问 zeuz 基础 API,您可以根据自身需求改编下方示例代码。

前期准备

如果您直接访问 zeuz 基础 API,您可以根据自身需求改编下方示例代码。

该示例代码涵盖了 API 认证概览 中的两项任务:3. 登录 zeuz 并生成会话密钥4. 生成用于附在 API 请求上的签名哈希 以及其他功能,如下所示:

  • 定义数据结构
    • 示例一:定义数据结构。
  • 登录并生成会话密钥
    • 示例二:生成 nonce 和时间戳,并向 auth_login 端点发送 cURL 请求。
    • 示例三:通过发送示例二中的 cURL 请求登录 zeuz,并生成会话密钥。
  • 生成签名哈希、检查登录会话是否有效并向 API 端点发送请求
    • 示例四:生成签名哈希,向 auth_check API 端点发送 JSON 数据包,并解析返回值查看登录会话是否有效。如果登录会话有效,发送新的 cURL 请求到 zeuz API 端点。

这些示例使用 OpenSSL 工具的 SHA-3 安全哈希算法和 Base64 编码进行哈希,并使用 libcurl 库发送 HTTP 请求。

注意:您也可以在 API 登录签名哈希生成 中的两个主要任务里查看单独的 C++ 代码示例。

示例

示例一:定义数据结构 (C++)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct LoginData {
   // SessionId:zeuz API 会话的唯一标识符。
   // 会话 ID 在调用 auth_login 端点生成后的 24 小时内有效。
   std::string SessionId;

   // 会话密钥:在 zeuz API 中用于验证身份的值。
   // 与 SessionId 相绑定。
   std::string SessionKey;

   // ValidThru:会话过期的日期 / 时间。
   long ValidThru;
};

示例二:生成 nonce 和时间戳,并向 auth_login 端点 (C++) 发送 cURL 请求。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <chrono>
#include <cstring>
#include <string>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
#include <unistd.h>
#include <curl/curl.h>

// 生成指定长度的 nonce。
std::string generate_nonce(const int len) {
   std::string tmp_s;
   static const char alphanum[] =
      "0123456789"
      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
      "abcdefghijklmnopqrstuvwxyz";

   srand( (unsigned) time(NULL) * getpid());
   tmp_s.reserve(len);
   for (int i = 0; i < len; ++i)
       tmp_s += alphanum[rand() % (sizeof(alphanum) - 1)];
   return tmp_s;
}
// 生成当前时间的 zeuz 时间戳。
// 更多信息,请查看 doc.zeuz.io/docs/time-handling
long zeuz_now() {
   auto now = std::chrono::system_clock::now();
   auto now_ms = std::chrono::time_point_cast<std::chrono::seconds>(now);
   auto epoch = now_ms.time_since_epoch();
   return (epoch.count() + 2208988800) * 1000 * 1000;
}

// 使用 Base64 编码输入的字符串。
std::string base64_encode(unsigned char * value, unsigned int length) {
   BIO *bmem, *b64;
   BUF_MEM *bptr;

   b64 = BIO_new(BIO_f_base64());
   bmem = BIO_new(BIO_s_mem());
   b64 = BIO_push(b64, bmem);
   BIO_write(b64, value, length);
   BIO_flush(b64);
   BIO_get_mem_ptr(b64, &bptr);

   char *buff = (char *)malloc(bptr->length);
   std::memcpy(buff, bptr->data, bptr->length-1);
   buff[bptr->length-1] = 0;

   BIO_free_all(b64);

   auto ret = std::string(buff);
   free(buff);
   return ret;
}

// 使用 SHA3-256 哈希输入的字符串。
void sha3_256_hash(const char * value, int length, unsigned char ** output, unsigned int * output_length) {
   EVP_MD_CTX *ctx = EVP_MD_CTX_new();
   EVP_MD_CTX_init(ctx);
   EVP_DigestInit(ctx, EVP_sha3_256());
   EVP_DigestUpdate(ctx, value, length);
   *output = (unsigned char *)OPENSSL_malloc(EVP_MD_size(EVP_sha3_256()));
   EVP_DigestFinal(ctx, *output, output_length);
   EVP_MD_CTX_free(ctx);
}

// 使用 SHA3-256 哈希输入的字符串,然后使用 Base64 进行编码。
// 返回经过哈希和编码后的值。
std::string zeuz_hash(std::string value) {
   unsigned char * hash;
   unsigned int hash_length;
   sha3_256_hash(value.c_str(), value.length(), &hash, &hash_length);
   std::string base64_hash = base64_encode(hash, hash_length);
   OPENSSL_free(hash);
   return base64_hash;
}

// cURL 调用函数处理响应。
// 参阅:curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
size_t read_curl_response(const char *in, size_t size, size_t num, std::string *out) {
   const std::size_t totalBytes(size * num);
   out->append(in, totalBytes);
   return totalBytes;
}

// 按照 auth_login 端点格式组织 POST cURL 请求。
// 以 std::string 的形式返回响应。
std::string post_request(std::string url, std::string post_body) {
   auto curl = curl_easy_init();
   // 设置 cURL 请求的选项。
   // 在 CURLOPT_URL 中设置 auth_login 端点。
   // 您可以使用 curl_easy_setopt,
   // 向任意 zeuz API端点 发送 POST 请求。
   // 请参阅:curl.se/libcurl/c/curl_easy_setopt.html
   // 请参阅:doc.zeuz.io/reference
   curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
   curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_body.c_str());

   std::string response_data;
   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, read_curl_response);
   curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_data);

   curl_easy_perform(curl);

   long http_code(0);
   curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

   // 请求完成后进行清理。
   curl_easy_cleanup(curl);

   // zeuz API 在 JSON 响应的错误字段中返回自定义错误代码。
   // 在函数中直接处理响应数据。
   return response_data;
}

示例三:通过发送示例二中的 cURL 请求登录 zeuz,并生成会话密钥 (C++)。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
#include "login.h"
#include <libscrypt.h>
#include <jsoncpp/json/writer.h>
#include <jsoncpp/json/reader.h>

// 生成签名哈希。
// 登录名:开发人员用户名或 API 密钥。
// 密码:开发人员密码或 API 密钥密码。
std::string LoginFunction::generate_pwhash(std::string login, std::string password) {
   std::string salt = "zeuz" + login;
   uint8_t hash[32] = { 0 };

   // 该示例使用 libscrypt-dev 库。
   // 使用 sudo apt-get install libscrypt-dev 命令安装该库。
   libscrypt_scrypt(
       reinterpret_cast<const uint8_t*>(&(password.c_str())[0]),
       password.length(),
       reinterpret_cast<const uint8_t*>(&(salt.c_str())[0]),
       salt.length(),
       1024,
       8,
       1,
       hash,
       32);
   return "a" + base64_encode((unsigned char *) hash, 32);
}

// 将登录数据格式化为 JSON 格式。
// 详情请参阅:doc.zeuz.io/reference#auth_login
std::string format_login_data_to_json_payload(long time, std::string hash, std::string login, std::string nonce) {
   Json::Value data;
   Json::FastWriter writer;
   data["Time"] = Json::Int64(time);
   data["Data"]["Hash"] = hash;
   data["Data"]["IsApi"] = false;
   data["Data"]["IsUser"] = false;
   data["Data"]["Login"] = login;
   data["Data"]["Nonce"] = nonce;
   data["Data"]["Time"] = Json::Int64(time);
   auto json = writer.write(data);
}

// 登录 zeuz API。
LoginData LoginFunction::login(std::string login, std::string password) {

    // 生成指定长度的 nonce。
   auto nonce = generate_nonce(10);

    // 生成当前时间的 zeuz 时间戳。
  // 更多信息,请参阅:doc.zeuz.io/docs/time-handling。
   auto now = zeuz_now(); 

    // 生成密码哈希。
   auto pwhash = generate_pwhash(login, password);

    // 将 nonce、时间戳和密码哈希组合为请求哈希。
   auto request_hash = zeuz_hash(nonce + std::to_string(now) + pwhash);

   // 将数据格式化为 JSON 负载。
   // 该示例使用 JsonCpp。
   auto data = format_login_data_to_json_payload(now, request_hash, login, nonce);

   // 使用 HTTP POST 请求体,
   // 向 auth_login 端点发送数据。
   auto response = post_request("https://zcp.zeuz.io/api/v1/auth_login", data);

   // Parse the JSON response.
   Json::Reader reader;
   Json::Value response_json;
   if (!reader.parse(response, response_json)) {
       // 如解析失败则报错。
       // 注意:在生产代码中,使用 C++ 异常作为替代。
       throw std::string("could not parse response data ( " + response + ")");
   }

   // 检查是否设置错误字段。
   auto error = response_json["Error"].asString();
   if (!error.empty()) {

      // 如错误信息开头为 request_expired,
      // 则生成的时间戳已过时。
      if (error.rfind("request_expired", 0) == 0) {
          // 注意:在生产环境中,您可以重试登录,
          // 或确保系统时间是正确的。
          throw std::string("verify system clock or retry request");
      } else {
          // 如意外错误发生,说明认证信息有误。
          // 注意:在生产代码中,使用 C++ 异常作为替代。
          throw std::string("could not login (" + response + ")");
      }
   }

     // 提取会话 ID、nonce 和会话过期的日期 / 时间 (ValidThru),
   // 组合成会话密钥。
   auto sessionKey = zeuz_hash(response_json["Data"]["SessionNonce"].asString() + pwhash);

   // 返回会话 ID、会话密钥和 ValidThru 作为数据结构。
   return LoginData{
       .SessionId = response_json["Data"]["SessionID"].asString(),
       .SessionKey = sessionKey,
       .ValidThru = response_json["Data"]["ValidThru"].asInt64(),
   };
}

示例四:生成签名哈希,向 auth_check API 端点发送 JSON 数据包,并解析返回值查看登录会话是否有效。如果登录会话有效,发送新的 cURL 请求到 zeuz API 端点 (C++)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <jsoncpp/json/writer.h>
#include <jsoncpp/json/reader.h>

// 检查会话时间戳是否有效。
bool ZeuzApi::check_session(long now, LoginData login_data) {
    // 检查过期时间是否在 60 秒后。
    if (now > (login_data.ValidThru - (60 * 1000000))) {
        return false;
    }
    return true;
}

// 参考 doc.zeuz.io/reference#auth_check 调整格式。
// 按需修改其他 zeuz API 端点调用。
std::string format_to_json_payload(std::string request_id, std::string session, std::string sign_hash, long time, std::string session_id) {
   Json::Value data;
   Json::FastWriter writer;
   data["ReqID"] = request_id;
   data["Session"] = session;
   data["SignHash"] = sign_hash;
   data["Time"] = Json::Int64(time);
   data["Data"] = session_id;
   return writer.write(data);
}

// 调用 auth_check API 断点,
// 验证登录会话是否有效。
bool ZeuzApi::auth_check(LoginData login_data) {
   auto now = zeuz_now();
   if (!check_session(now, login_data)) {
       // 如会话过期,则永久失效。
       return false;
   }
   auto request_id = generate_nonce(10);

   // 生成签名哈希。
   auto sign_hash = zeuz_hash(std::to_string(now) + request_id + login_data.SessionKey);

     // 将签名哈希和其他请求数据
   // 添加到 zeuz API 端点调用请求。
   auto data = format_to_json_payload(request_id, login_data.SessionId, sign_hash, now, login_data.SessionId);

   // 查看示例 2 中的 post_request 函数。
   // 根据您要调用的 API 端点要求,
   // 修改 CURLOPT_URL 和其他属性。
   response = post_request("https://zcp.zeuz.io/api/v1/auth_check", data);

   // 解析 JSON 响应并提取返回值。
   Json::Reader reader;
   Json::Value response_json;
   if (!reader.parse(response, response_json)) {
      // 注意:在生产代码中,使用 C++ 异常作为替代。
      throw std::string("could not parse response data ( " + response + ")");
   }

   // 检查是否设置错误字段。
   auto error = response_json["Error"].asString();
   if (!error.empty()) {
      if (error.rfind("invalid_session", 0) == 0 ||
          error.rfind("session_expired", 0) == 0) {
          // 如会话过期,需重新登录。
          return false;
      } else {
          throw std::string("could not check session (" + response + ")");
      }
   }

   // 如没有收到错误信息,则会话有效。
   return true;
}


2021年5月12日 该文档已创建并通过审校


最近更新时间: October 20, 2021 (66902047)