JWT 函数手册
CHAPTER 20

JWT 函数手册

7 分钟阅读 · 1,575 字

依据源码:include/jwt.h、src/jwt.cpp。


0. 模块能力总览

JWT 提供三类能力:

  1. Token 生成/校验(HS256)
  2. Claim 解析与 TTL 处理
  3. 密码哈希/校验(PBKDF2 风格迭代 HMAC-SHA256)

关键成员:

  • secret_:签名密钥
  • algorithm_:当前固定 HS256
  • defaultTTL_:默认过期秒数
  • static JWT* instance_:单例指针

1. 构造与配置接口

1.1 JWT::JWT(const std::string& expiresIn, const std::string& jwtPrivateKeyPath)

  • 作用:单例风格构造,允许通过 TTL 字符串与密钥文件初始化。
  • 流程:
    1. 设 algorithm_=HS256,defaultTTL_=3600
    2. 若 instance_ != nullptr,仅打印 "JWT instance already exists" 并 return
    3. 若 expiresIn 非空,调用 parseTTL,结果 >0 则覆写 defaultTTL_
    4. 若 jwtPrivateKeyPath 非空,调用 loadSecretFromFile
    5. instance_ = this

注意:这是“软单例”构造,重复构造不会抛异常,但行为可能让调用者误判(只打印错误)。


1.2 JWT::JWT(std::string secret, long long ttlSeconds)

  • 作用:直接以密钥 + TTL 构造。
  • 行为:
    • secret_ = secret
    • defaultTTL_ = (ttlSeconds > 0 ? ttlSeconds : 3600)
    • 若 secret_ 为空,抛 invalid_argument

1.3 void setSecret(const std::string& secret)

  • 非空校验,不通过则抛 invalid_argument
  • 成功后覆写 secret_

1.4 void loadSecretFromFile(const std::string& path)

  • 以二进制打开文件
  • 失败抛 runtime_error("Failed to open secret file: ...")
  • 读完整文件到字符串流,再调用 setSecret

1.5 void setDefaultTTL(long long ttlSeconds)

  • 必须 > 0
  • 否则抛 invalid_argument("TTL must be positive")

1.6 std::string getSecret() const / Algorithm algorithm() const

  • 只读返回密钥副本与当前算法枚举

2. Token 生成与校验

2.1 std::string generateToken(const std::map<std::string,std::string>& customClaims, long long ttlSeconds) const

作用

生成 header.payload.signature 三段 JWT。

关键步骤

  1. 检查 secret_ 非空,否则抛 runtime_error
  2. header = buildHeader(),再 base64UrlEncode
  3. 取当前 issuedAt(秒级时间戳)
  4. 计算有效 TTL:
    • 参数 < 0 -> 用 defaultTTL_
    • 0 则设置 exp = iat + ttl

    • <=0 时可不带 exp
  5. payload = buildPayload(customClaims, issuedAt, expiresAt) 并编码
  6. signingInput = headerEncoded + "." + payloadEncoded
  7. signature = base64UrlEncode(hmacSha256(signingInput, secret_))
  8. 返回 signingInput + "." + signature

2.2 bool verifyToken(const std::string& token, std::string* payloadJson) const

作用

校验 token 格式、签名与过期时间。

关键步骤

  1. 若 secret_ 空,直接 false
  2. parts = splitToken(token),必须是 3 段
  3. 重算签名并比对(当前是 expectedSignature != parts[2])
  4. 解码 payload:base64UrlDecode(parts[1])
  5. 提取 exp:extractNumericClaim(payload, "exp")
  6. 若有 exp 且当前时间 > exp,返回 false
  7. 若 payloadJson 非空,写回 payload 文本
  8. 返回 true

安全注意

  • 当前签名比较使用普通字符串比较,不是常量时间比较。
  • 模块虽实现了 constant_time_compare,但此处未使用。

2.3 std::optional<std::map<std::string,std::string>> parseClaims(const std::string& token) const

  • 先 verifyToken(token, &payload),失败返回 nullopt
  • 再 parseJsonObject(payload),异常则 nullopt
  • 成功返回 claims map

3. 编码与签名基础函数

3.1 static std::string buildHeader()

  • 固定返回:{"alg":"HS256","typ":"JWT"}

3.2 static std::string base64UrlEncode(const std::string& data)

  • 标准 base64 编码后:
    • + -> -
    • / -> _
    • 去掉末尾 =

3.3 static std::string base64UrlDecode(const std::string& data)

  • 先还原字符:
    • - -> +
    • _ -> /
  • 补齐 = 到 4 的倍数
  • 解码为原始字节串

3.4 static std::string sha256(const std::string& data)

  • 自实现 SHA-256:
    • 消息填充
    • 512-bit 分块
    • 64 轮压缩
    • 输出 32 字节原始哈希

3.5 static std::string hmacSha256(const std::string& data, const std::string& key)

  • 标准 HMAC 过程:
    • key 按 blockSize=64 归一化
    • iKeyPad / oKeyPad
    • sha256(oKeyPad + sha256(iKeyPad + data))

3.6 static inline uint32_t rotor(uint32_t value, uint32_t count)(匿名命名空间)

  • SHA-256 内部循环右移工具函数

4. Claim 与 JSON 处理函数

4.1 static std::string buildPayload(...)

  • 用 JsonValue 组装 payload:
    • 必填 iat
    • 可选 exp
    • 追加 customClaims
  • toJson() 返回文本

4.2 static std::vector<std::string> splitToken(const std::string& token)

  • 以 . 分割,返回段数组

4.3 static std::optional<long long> extractNumericClaim(const std::string& payload, const std::string& claimKey)

  • 在 payload 文本中查找 "claimKey":
  • 跳过空白,解析可选负号 + 数字
  • 找不到/无数字返回 nullopt

4.4 static long long parseTTL(const std::string& ttlSpec)

  • 支持单位:
    • s 秒
    • m 分钟
    • h 小时
    • d 天
  • 支持组合串(如 1h30m)
  • 解析失败返回 0

4.5 static std::map<std::string,std::string> parseJsonObject(const std::string& json)

  • 用 JsonValue::fromJson 解析
  • 要求根类型必须是 OBJECT,否则抛异常
  • 值类型支持:
    • STRING -> 原字符串
    • NUMBER/BOOLEAN -> toJson()
    • NULL -> "null"
    • OBJECT/ARRAY -> 抛异常(当前不支持嵌套对象值导出到 string map)

4.6 static std::string escapeJson(const std::string& input) / static std::string unescapeJson(const std::string& input)

  • escapeJson:手工转义控制字符与特殊字符
  • unescapeJson:复用 JsonValue::parseString 执行反转义

4.7 static std::string getStringFromJsonValue(const JsonValue& value, const std::string& key)

  • 若 value 非字符串,抛 runtime_error
  • 是字符串则返回 asString()

5. 密码哈希相关函数

5.1 static std::string encrypt_password(const std::string& password)

  • 目标格式:salt_hex:iterations:hash_hex
  • 固定迭代:100000
  • 流程:
    1. salt = generate_salt()
    2. 执行 PBKDF2 风格迭代(基于 hmacSha256)
    3. 输出 bytes_to_hex(salt) + ":" + iterations + ":" + bytes_to_hex(dk)

注意:generate_salt() 本身已经返回十六进制字符串,这里再次 bytes_to_hex(salt) 会把“hex文本”再按字节转 hex,格式与常见实现不同但与当前 verify_password 协同一致。


5.2 static bool verify_password(const std::string& password, const std::string& stored_hash)

  • 解析 salt_hex:iterations:hash_hex
  • 用 hex_to_bytes 还原盐值与期望哈希
  • 迭代次数必须为正整数
  • 复算哈希后使用 constant_time_compare 比较
  • 成功 true,否则 false

5.3 static std::string generate_salt()

  • 生成 16 字节随机数据
  • 使用 std::random_device + mt19937_64 + uniform_int_distribution<uint8_t>
  • 返回十六进制字符串

5.4 static std::string bytes_to_hex(const std::string& bytes) / static std::string hex_to_bytes(const std::string& hex)

  • bytes_to_hex:字节 -> 2位hex文本
  • hex_to_bytes:hex文本 -> 字节串(奇数长度返回空串)

5.5 static bool constant_time_compare(const std::string& a, const std::string& b)

  • 长度不等直接 false
  • 长度相等时逐字节异或累积 diff
  • 最终 diff == 0 判等

6. 单例接口

static JWT* getInstance()

  • 若 instance_ == nullptr,执行 instance_ = new JWT();
  • 返回 instance_

风险点

  • 默认构造未保证 secret_ 已设置。
  • 若调用方未再 setSecret 或 loadSecretFromFile,generateToken 会抛错,verifyToken 会直接 false。

7. 已声明但在本文件未见实现的函数

jwt.h 声明了:

  • sha256_bytes(const std::vector<uint8_t>& data)
  • hmac_sha256(const std::vector<uint8_t>& key, const std::vector<uint8_t>& data)

在当前 src/jwt.cpp 中未看到对应定义。
若链接未报错,说明工程里未实际引用这两个符号;否则需补实现或移除声明。


8. 面试可直接讲的风险与改进

  1. verifyToken 签名比较建议改为常量时间比较。
  2. 自研密码学组件建议引入成熟库并保留接口层。
  3. 单例初始化流程需要更严格(避免无 secret 的默认实例)。
  4. claims 解析目前只导出扁平字符串 map,嵌套对象值会报错,可扩展 JSON 值透传。

下一模块(顺序)

RDConnector → docs/modules/detail/RDConnector_FUNCTIONS.md

全部章节