欢迎光临
我们一直在努力

Jsonwebtoken 跨端三方库仓颉适配全流程

本文记录 jsonwebtoken 9.0.3 适配仓颉(Cangjie)的完整过程。适配目标是得到一个可以被仓颉项目直接依赖的跨端三方库,并在本地终端中通过真实的 JWT 签名、验证、解码、声明校验、过期处理和错误路径验证库的输出。

项目地址: AtomGit/nutpi_cj/node-jsonwebtoken

上游仓库: auth0/node-jsonwebtoken

开发工具: 华为云码道

一、背景

1.1 为什么适配 jsonwebtoken

jsonwebtoken 是 Node.js 生态中使用广泛的 JSON Web Token 实现,提供 sign、verify 和 decode 等入口,用于生成 compact JWT、校验签名以及读取 token 内容。JavaScript 版本接收动态 payload、secret/key、options 和 callback,运行时还会根据算法、密钥类型和声明内容选择不同的校验路径。

仓颉强调静态类型、显式异常和可检查的模块边界。直接把 JavaScript 源码逐行翻译成仓颉,无法自然表达动态 JSON、可选声明和多种密钥输入,因此本适配重新组织为仓颉值模型、显式选项类、明确的验证结果和可捕获错误。

本项目保留熟悉的 JWT 使用概念:sign 生成 token,verify 返回 payload/header/signature,decode 只解码不验签,并额外提供 signJson 和 verifyJson 方便以 JSON 文本作为边界。这样熟悉 jsonwebtoken 的开发者可以沿用原有思路,同时获得仓颉编译器的参数检查。

1.2 跨端三方库的适配边界

当前项目提供的是仓颉跨端库,不绑定某一个 UI 框架或操作系统。适配边界可以概括为:

边界仓颉实现
包管理 cjpm.toml、cjpm.lock、cjpm build 和静态库输出
JSON 模型 JwtValue 表达 null、布尔、数字、字符串、数组和对象
编码 RFC 4648 Base64URL 无填充编解码
对称认证 纯仓颉 HMAC-SHA-256、HMAC-SHA-384 和 HMAC-SHA-512
JWT HS256、HS384、HS512 Compact JWT 和 none 无签名 token
声明校验 iat、exp、nbf、aud、iss、sub、jti、nonce、maxAge
错误模型 JwtError,包含格式、算法、签名、声明和时间校验错误
交付形态 仓颉静态库 libjsonwebtoken_cangjie.a 和接口文件 jsonwebtoken_cangjie.cjo
验证方式 test/main.cj 独立编译测试和 example/example.cj 终端示例

这里没有 HAP 构建、ArkTS 桥接和真机系统能力验收。当前实现以零外部依赖、可重复运行和可观察输出为目标;RSA、PSS 和 ECDSA 需要目标平台密码库接入,本次仓颉包会明确返回不支持错误,不生成伪造签名。

1.3 适配后的能力

当前导出的仓颉 API 覆盖以下能力:

  • JSON 值构造:jsonNull、jsonBool、jsonNumber、jsonString、jsonObject 和 jsonArray;
  • JWT 生成:sign 和 signJson,支持算法、时间、声明、kid 和 none 选项;
  • JWT 验证:verify 和 verifyJson,支持算法白名单、声明匹配、过期/生效时间、时钟容差、ignoreExpiration、ignoreNotBefore 和 maxAge;
  • JWT 解码:decode 返回 header、payload 和 signature,不把解码误认为签名验证;
  • JSON 处理:parseJson 和 stringify,可处理嵌套数组、对象、布尔、数字和字符串;
  • 结果与错误:JwtDecoded、JwtVerification 和 JwtError,示例会直接打印真实 token、payload、signature 和失败信息。

二、实现路线图

第 1 阶段:盘点 API ── 对照 jsonwebtoken 的 sign/verify/decode 生命周期,确定仓颉类型和算法边界
第 2 阶段:建立 cjpm 工程 ── 固定包名、版本、源码目录和静态库输出
第 3 阶段:类型映射 ── 用 enum、class、Option 和显式异常替换动态 payload/options
第 4 阶段:实现 JWT ── 完成 JSON、Base64URL、HMAC、claims 注入和验证错误处理
第 5 阶段:测试与核对 ── 编译测试入口,执行真实签名、解码、声明和失败路径校验
第 6 阶段:终端演示 ── 输出所有已实现功能、算法结果、错误信息和运行效果图

每个阶段都有可检查的产物:工程阶段得到 cjpm.toml 和 cjpm.lock,实现阶段得到 .cjpm-target/jsonwebtoken_cangjie,验证阶段得到固定测试输出,最终示例则输出真实计算出的 token、header、payload、signature 和错误结果。

三、逐步实现过程

第 1 阶段:盘点原库 API 和仓颉工程边界

1.1 识别动态 API

jsonwebtoken 的 JavaScript API 接受对象、字符串、Buffer、密钥对象和多种 options 组合。仓颉没有与这些 JavaScript 动态联合类型完全等价的边界,因此本适配先筛选可以由静态类型稳定表达的核心流程,再为这些流程设计清晰签名。

payload 使用递归的 JwtValue 表达;secret 使用 String;sign 和 verify 的可选配置分别使用 JwtSignOptions 与 JwtVerifyOptions;解码和验证结果使用 JwtDecoded 与 JwtVerification。格式错误、算法错误和校验失败统一通过 JwtError 抛出,调用方可以在 catch 中读取真实错误文本。

1.2 固定仓颉项目结构

以下目录树按照当前 jsonwebtoken 项目的仓颉适配文件整理,只展示仓颉相关文件;原始 Node.js 源码、测试、lib/ 和其他目录保持原位置:

node-jsonwebtoken/
├── cjpm.toml
├── cjpm.lock
├── README.Cangjie.md
├── README.Cangjie_CN.md
├── src/
│ └── jsonwebtoken.cj
├── test/
│ └── main.cj
└── example/
├── cjpm.toml
├── cjpm.lock
└── example.cj

源码集中在 src/jsonwebtoken.cj,按 JSON、Base64URL、摘要、JWT 生成和验证顺序组织。测试入口位于 test/main.cj,示例工程位于 example/,两者都独立于库源码。根包的 target-dir 是 .cjpm-target/jsonwebtoken_cangjie,在 src/ 扫描范围之外;示例使用 src-dir = ".",target-dir 放在 example/ 外部,因此构建输出不会再次被当作源码扫描。

第 2 阶段:创建 cjpm 工程

根目录 cjpm.toml 使用静态库输出类型,并固定源码目录和构建目录:

[package]
cjc-version = "1.1.3"
name = "jsonwebtoken_cangjie"
organization = ""
description = "Cangjie implementation of JSON Web Token signing, verification and decoding"
version = "9.0.3"
src-dir = "src"
target-dir = ".cjpm-target/jsonwebtoken_cangjie"
output-type = "static"
package-configuration = {}

[dependencies]

示例工程使用本地路径依赖,并把构建目录放在 example 外部:

[package]
cjc-version = "1.1.3"
name = "jsonwebtoken_example"
organization = ""
description = "Observable JSON Web Token feature demonstration"
version = "1.0.0"
src-dir = "."
target-dir = "../.cjpm-target/jsonwebtoken_example"
output-type = "executable"
package-configuration = {}

[dependencies]
jsonwebtoken_cangjie = { path = ".." }

包版本沿用当前 jsonwebtoken 版本,包名使用 jsonwebtoken_cangjie。构建成功后,最重要的两个库产物是:

.cjpm-target/jsonwebtoken_cangjie/release/jsonwebtoken_cangjie/libjsonwebtoken_cangjie.a
.cjpm-target/jsonwebtoken_cangjie/release/jsonwebtoken_cangjie/jsonwebtoken_cangjie.cjo

其中 .a 用于链接,.cjo 提供编译器需要的接口信息。示例可执行文件写入 .cjpm-target/jsonwebtoken_example,验证完成后清理这些目录,不提交构建产物。

第 3 阶段:把 JavaScript 动态输入映射为仓颉类型

3.1 用 JwtValue 表达 JSON payload

仓颉边界不强制调用方引入新的 JSON 依赖,payload 可以通过类型构造器表达:

let payload = jsonObject([
("sub", jsonString("alice")),
("role", jsonString("admin")),
("permissions", jsonArray([jsonString("read"), jsonString("write")])),
("active", jsonBool(true))
])
let token = sign(payload, "demo-secret",
options: JwtSignOptions(issuedAt: Some(1700000000), expiresIn: Some(3600)))
println(token)

JwtValue 同时支持嵌套数组和对象;stringify 将它序列化为紧凑 JSON,parseJson 可以从 JSON 文本恢复同一值模型。signJson 和 verifyJson 则为已有 JSON 字符串的调用方提供直接入口。

3.2 用选项类表达 claims 和验证约束

JavaScript options 中的动态字段在仓颉中变成显式的 Option<T> 成员:

let options = JwtSignOptions(
issuedAt: Some(1700000000), expiresIn: Some(3600), notBefore: Some(-5),
audience: Some("users"), issuer: Some("example-service"),
subject: Some("alice"), jwtid: Some("example-1"), nonce: Some("nonce-1"),
keyid: Some("demo-key"))
let token = sign(payload, "demo-secret", options: options)
let verified = verify(token, "demo-secret", options: JwtVerifyOptions(
clockTimestamp: Some(1700000100), audience: Some("users"),
issuer: Some("example-service"), subject: Some("alice"),
jwtid: Some("example-1"), nonce: Some("nonce-1")))
println(stringify(verified.payload))

签名时会把 iat、exp、nbf 和其他声明写入 payload;验证时会根据指定时钟检查生效时间、过期时间、最大年龄和声明匹配。示例使用固定 issuedAt 和 clockTimestamp,使每次运行得到可复核的结果。

3.3 用 JwtError 表示失败

JavaScript jsonwebtoken 会把错误密钥、错误算法、过期 token 和声明不匹配分别返回错误。仓颉版本把这些失败收敛到可捕获的 JwtError:

try {
verify(token, "wrong-secret")
} catch (error: JwtError) {
println(error.message)
}

这种错误模型可以覆盖 malformed token、invalid signature、jwt expired、jwt audience invalid 和不支持算法。示例会主动触发错误密钥、过期时间和 audience 不匹配,并把错误文本打印到终端,而不是只输出一个断言通过标记。

第 4 阶段:实现源码

4.1 文件 src/jsonwebtoken.cj

源码按 JWT 生命周期组织:先由 JsonParser 解析 JSON,再用 stringify 生成 header/payload 文本;随后通过 Base64URL 编码组成 signing input;最后使用纯仓颉 HMAC 计算签名。

sign 默认使用 HS256,也支持 HS384、HS512 和 none;header 会包含 alg、typ,配置 keyid 时追加 kid。对象 payload 会注入时间和标准声明,字符串等非对象 payload 可以用于无声明 token。decode 只负责拆分和解码三段内容,返回 header、payload 和 signature,不把解码结果误认为已验签。

4.2 Base64URL 和 HMAC

JWT 的 header、payload 和 signature 使用无填充 Base64URL。仓颉实现直接处理 Array<UInt8>,不依赖 Node Buffer。HMAC-SHA-256、HMAC-SHA-384 和 HMAC-SHA-512 的摘要、补位、块处理和最终编码都在 src/jsonwebtoken.cj 中完成;示例打印的三个算法结果可直接用 Node crypto.createHmac 交叉验证。

验证时会重新计算 signing input 的签名,并与 token 的第三段比较。none 只允许空 secret 和空 signature,避免把未签名 token 当作有密钥的 token 接受。

4.3 非对称算法和平台边界

原 Node.js jsonwebtoken 还支持 RSA、RSA-PSS 和 ECDSA。当前仓颉包没有引入平台密码库或 OpenSSL 外部链接,因此没有伪造这些算法的兼容实现;调用 RS256、PS256 或 ES256 等算法会明确抛出 Unsupported algorithm。如果目标平台需要非对称 JWT,应在平台提供真实密码学 API 后扩展同一套 JwtSignOptions 和 JwtVerifyOptions 边界。

第 5 阶段:建立自动化测试

test/main.cj 是一个直接链接仓颉静态库的可执行测试入口,覆盖以下行为:

测试组示例
JSON 与编码 对象、数组、布尔、数字、字符串、Base64URL 和 JSON 往返
签名算法 HS256、HS384、HS512 的 token 生成和验证
JWT 声明 iat、exp、nbf、aud、iss、sub、jti、nonce 和 kid
解码结果 header、payload 和 signature 的实际字段
错误 错误 secret、过期时间和 none 算法校验

断言失败会抛出异常并以非零状态退出;全部通过时输出固定标记:

JWT CANGJIE TESTS: sign, verify, decode, claims, expiry, errors and none all passed

示例工程则负责展示完整中间结果,测试程序负责断言库调用,两者共同覆盖编译期 API 和运行时签名、解码、声明校验及错误行为。

第 6 阶段:核对 jsonwebtoken 适配 API

当前仓颉公开入口按功能核对如下:

JSON values: JwtValue, jsonNull, jsonBool, jsonNumber, jsonString, jsonObject, jsonArray
JWT operations: sign, signJson, verify, verifyJson, decode, parseJson, stringify
Options/results: JwtSignOptions, JwtVerifyOptions, JwtDecoded, JwtVerification, JwtError
Algorithms: HS256, HS384, HS512, none

这个检查说明当前仓颉适配公开入口与 README 和示例一致,不代表它已经覆盖 JavaScript jsonwebtoken 的回调式 API、Buffer/KeyObject 输入和 RSA/PSS/ECDSA 平台实现。

四、完整验证步骤

以下命令均以仓库根目录 node-jsonwebtoken/ 为当前目录。先确认本机已经安装仓颉 SDK:

echo "$CANGJIE_HOME"
cjc –version
cjpm –version

然后构建静态库并检查依赖:

cjpm check
cjpm build

编译并运行测试入口:

cjc test/main.cj \\
–import-path .cjpm-target/jsonwebtoken_cangjie/release/jsonwebtoken_cangjie \\
-L .cjpm-target/jsonwebtoken_cangjie/release/jsonwebtoken_cangjie \\
-l jsonwebtoken_cangjie \\
-o /tmp/jsonwebtoken-cangjie-test
DYLD_LIBRARY_PATH="$CANGJIE_HOME/runtime/lib/darwin_aarch64_cjnative" \\
/tmp/jsonwebtoken-cangjie-test

再构建并运行独立的终端示例工程:

cd example
cjpm check
cjpm run

示例会打印真实计算结果,而不是只打印断言通过:

1) sign(payload, secret, options)
token = eyJhbGciOiJIUzI1NiIs…

HMAC algorithm outputs
HS256: signature=bV57e87Mb1yXVSgIxpNbMTQkJzPioO8R-wwdd75bClI, payload={…}
HS384: signature=gyU28bzsnG5l0LIgGYCzHrYY_Zfg-ze4HSdu0PbdHcelI1gGkHJt2HXEuVupzu8Q, payload={…}
HS512: signature=K1uinoNOztRtdgOYBLzpsLw08H-Z5RvgSDl-421Zc_7AZEuKIxGBU5cLZVUrwDXm5-801OakwAV2fFm55K8Z-Q, payload={…}

2) decode(token) without verification
decoded.header = {"alg":"HS256","typ":"JWT","kid":"demo-key"}
decoded.payload = {"sub":"alice",…,"nonce":"nonce-1"}
decoded.signature = e5-AuZ6julNG4wMk19bDJD2xhxa_wBab7smNiZzu8lo

3) verify(token, secret, claim checks)
verified.payload = {…}
verified.header = {"alg":"HS256","typ":"JWT","kid":"demo-key"}
verified.signature = e5-AuZ6julNG4wMk19bDJD2xhxa_wBab7smNiZzu8lo

4) verification failures are returned as real errors
wrong secret: invalid signature
expired clock: jwt expired
audience mismatch: jwt audience invalid

5) signJson / verifyJson convenience
json payload = {"message":"hello","count":2}

6) unsigned token (algorithm none)
unsigned payload = "public"

All JWT feature results were displayed

示例使用 target-dir = "../.cjpm-target/jsonwebtoken_example",位于 src-dir = "." 的扫描范围之外,因此运行 cjpm run 时不会出现把构建输出目录当作源码扫描目录的警告。验证结束后清理 .cjpm-target,确认它没有加入 Git。

五、运行效果

下面的效果图来自 example/example.cj 的实际终端运行,包含构建后的 jsonwebtoken Cangjie 签名、解码、验证、错误处理和 none 算法结果。

在这里插入图片描述

这张图可以作为文章或项目说明中的验收证据:终端同时展示了解码后的 header、payload、signature,验证后的内容,错误 secret、过期时间、audience 不匹配,以及 JSON convenience 和无签名 token 的结果。文本都来自仓颉示例程序实时调用 jsonwebtoken_cangjie 后的返回值,不是预先写死的展示文本,也不是只显示断言通过。

六、关键决策说明

6.1 为什么使用 JwtValue 和显式 options

仓颉适配的价值在于让调用方在编译期知道 payload 和 options 的数据形状。若所有内容都退化成无类型字符串,虽然表面上接近 JavaScript,却会失去数组、对象、布尔和数字的结构检查。因此项目使用递归 enum 表达 JSON,使用 Option<T> 表达可选声明,让常见调用保持可读、可检查和可预测。

6.2 为什么保留 sign、verify、decode 名称

保留 jsonwebtoken 的三个核心名称可以降低迁移成本。熟悉 Node.js jsonwebtoken 的开发者只需要适应仓颉的命名参数、JwtValue 构造和异常捕获,不必重新记忆一套完全不同的 token API。verify 直接返回完整验证结果,使终端示例可以同时展示 payload、header 和 signature。

6.3 为什么把测试和示例分开

测试程序负责断言签名算法、claims 和错误路径,适合自动化执行;示例工程负责展示 token、解码结果和真实失败信息,适合在终端中观察和截图。两者都使用同一个 jsonwebtoken_cangjie 静态库,因此效果图来自库的实际运行输出,而不是模拟文本。

七、兼容边界和遗留问题

JavaScript jsonwebtoken 与仓颉以及目标平台的密钥模型不同,以下限制需要在接入时提前了解:

  • 当前实现支持 HS256、HS384、HS512 和 none;RSA、RSA-PSS、ECDSA 和其他非对称算法尚未接入平台密码库。
  • secret 当前使用 UTF-8 String,没有映射 Node.js Buffer、KeyObject、PEM 私钥或异步 secret callback。
  • payload 使用仓颉 JwtValue 或 JSON 文本,支持常见 JSON 类型;JavaScript 特有的 Buffer payload、Date 对象和动态原型语义不在范围内。
  • audience、issuer、subject、jwtid 和 nonce 以字符串选项表达;正则 audience、数组 audience 和复杂 callback 选项尚未提供。
  • test/main.cj 是直接链接静态库的可执行测试入口,仓颉 cjpm test 不会把这个普通 main.cj 自动当作 unittest macro 用例;文章提供了可复现的 cjc 编译命令。
  • 当前没有 HAP 构建、ArkTS 桥接、真机系统能力验收。构建产物位于 .cjpm-target,验证结束后不提交该目录。
  • 这些不是构建错误。只要 cjpm check、cjpm build 成功,测试入口输出固定通过标记,示例输出真实 token 和错误信息,就说明当前版本已经完成本地编译和行为验收。对于暂时无法一比一映射的非对称密码学能力,项目通过显式错误保持行为可解释,避免把不安全的伪实现交给业务代码。

    八、接入方式

    在另一个仓颉项目中,可以将本仓库作为本地依赖或 AtomGit 源码依赖使用。最直接的本地验证方式是:

    [dependencies]
    jsonwebtoken_cangjie = { path = "../node-jsonwebtoken" }

    调用代码只需要导入仓颉包:

    import jsonwebtoken_cangjie.*

    main(): Int64 {
    let payload = jsonObject([
    ("sub", jsonString("cangjie-user")),
    ("role", jsonString("reader"))
    ])
    let token = sign(payload, "demo-secret")
    let result = verify(token, "demo-secret")
    println(stringify(result.payload))
    return 0
    }

    如果使用远程仓库管理依赖,则先从 AtomGit/nutpi_cj/node-jsonwebtoken 获取固定版本,再由业务工程统一构建。跨端项目可以复用相同的 payload、claims、签名和错误处理思路;具体的 RSA/ECDSA 密钥存储、异步 secret 获取和系统密码库接入由宿主平台负责。

    九、总结

    本次适配完成了 jsonwebtoken 核心 token 模型到仓颉静态类型模型的迁移:payload 使用 JwtValue,签名和验证选项使用显式类,Base64URL 和 HMAC 使用纯仓颉实现,时间声明和标准 claims 通过 Option<T> 表达,失败路径统一使用 JwtError。

    验证链路包括 cjpm check、cjpm build、测试入口编译、HS256/384/512 真实签名、header/payload/signature 解码、claims 校验、过期处理、错误密钥、audience 不匹配和 none token。最终效果图来自真实的仓颉可执行程序,能够直观看到三方库在签名、验证、解码、JSON convenience 和错误处理场景中的输出。

    对于暂时无法一比一映射 JavaScript jsonwebtoken 的 RSA、PSS、ECDSA、Buffer/KeyObject 和异步 secret 语义,项目通过静态签名和明确错误保持行为可解释,方便后续接入目标平台真实密码学能力。

    适配过程 FAQ

    以下问题来自仓颉工程创建、cjpm 依赖、与 HarmonyOS 设备联调的实际流程。

    Q1:cjpm 依赖解析失败或拉取超时?

    • 检查 cjpm.toml 中的依赖坐标与版本号;
    • 配置可用的仓颉包仓库镜像或代理;
    • 删除 target、cjpm.lock 后重新执行 cjpm update / cjpm build。

    Q2:编译报找不到仓颉 SDK 或 cjc 版本不匹配?

    • 设置 CANGJIE_HOME 指向仓颉 SDK 根目录;
    • 确认 cjc –version、cjpm –version 与工程要求的版本一致;
    • 清理构建缓存后重新生成。

    Q3:与 HarmonyOS 设备联调时安装失败?

    • 用 hdc list targets 确认设备在线;
    • 确认 HAP 已签名,bundleName 与签名 profile 一致;
    • 设备需加入调试 profile 的 deviceIds。

    Q4:动态库或 FFI 符号找不到?

    • 检查导出符号名称、ABI 与打包路径;
    • 确认 .so 已随 HAP 一起打包到 libs/<abi>/;
    • 用 llvm-readelf -d 检查依赖库是否齐全。

    Q5:测试用例在设备上失败,但本机通过?

    • 对比设备与主机的架构、SDK 版本和环境变量;
    • 用 hdc hilog 抓取运行日志;
    • 优先复现最小用例,确认是否为平台差异或时序问题。

    十、参考资料

    • AtomGit 项目仓库:jsonwebtoken 仓颉适配
    • jsonwebtoken 官方仓库
    • RFC 7519:JSON Web Token
    • 仓颉语言官方文档
    • 仓颉项目管理工具 cjpm
    赞(0)
    未经允许不得转载:171主机测评 » Jsonwebtoken 跨端三方库仓颉适配全流程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址