dsh-tool-jwt
DSH JWT 工具插件 —— HS256 JWT 的解析(不验签)、签发与完整验签。纯函数、零运行时依赖(仅 Node 内置 crypto)。
动机
后端开发日常:拿到一个 token 想看里面是什么、过期没有,但手算 base64url 解码 + JSON 解析 + 时间戳换算太慢;联调时需要造测试 token(网关/下游鉴权),手拼 header/payload/signature 容易错;验证 token 签名和有效期时,心算 HMAC 不可靠。
本插件提供确定性 JWT 工具,其中 decode 是差异化能力:不验签,只做结构解析 + 过期状态计算(当前时钟对比),用于快速排查 token 内容。verify 走完整校验链:算法检查(alg=HS256)、常数时间签名比较(crypto.timingSafeEqual,抗时序攻击)、过期判定(可选 leewaySeconds 时钟偏差容忍)。
安全模型
JWT 的签名伪造和算法混淆是真实威胁。防线:
- 算法强制:
verify仅接受alg=HS256;alg=none或任何非 HS256 直接拒绝,杜绝算法降级攻击 - 常数时间签名比较:
crypto.timingSafeEqual,签名字节逐位比较不泄露时序信息 - 输入长度上限:token ≤ 16KB、secret ≤ 4KB、payload 序列化后 ≤ 8KB——超限在入口直接拒绝,不进入处理
- decode 不验签:只做结构解析 + 过期状态计算,明确告知调用方"decode does not verify the signature"
⚠️ 本工具面向开发/联调场景(解码排查、造测试 token、验签)。不要把生产环境密钥粘贴到不可信会话。
其余边界:无效 base64url 直接报错(含输入截断);非 JSON header/payload 报错(含定位);非对象 JSON 报错;exp 非数字报错;secret 互斥检测(secret 与 secretBase64url 同时给即报错)。
工具声明
注册 jwt 工具(dsh-tool-jwt,row id tool-jwt),统一输出文本报告。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
action |
string | ✅ | decode / sign / verify |
token |
string | JWT 紧凑序列化(header.payload.signature)。decode/verify 必需 |
|
payload |
object | 声明对象(sign 必需),如 {"sub":"u1","role":"admin"}。exp/iat 由 expiresInSeconds 管理 |
|
secret |
string | HMAC 密钥(UTF-8 字节)。sign/verify 必需。与 secretBase64url 互斥 |
|
secretBase64url |
string | HMAC 密钥(base64url 编码的原始字节,用于二进制密钥如 RFC 7515 向量、JWK k 值)。与 secret 互斥 |
|
expiresInSeconds |
integer | sign only:有效期秒数,设置 exp = now + N(payload.exp 已给时跳过) |
|
leewaySeconds |
integer | verify only:允许的时钟偏差秒数(默认 0) |
Actions
| action | 功能 | 输出示例 |
|---|---|---|
decode |
解析 header/payload 为 JSON,按当前时钟计算过期状态(valid / expired / no-exp-claim,含 ISO 时间与剩余/已过期秒数)。不验签 |
header: {"alg":"HS256","typ":"JWT"}payload: {"sub":"u1","exp":1767231600}expiry.status: validexpiry.expiresInSeconds: 3600 |
sign |
用 HS256 签发 token。可选 expiresInSeconds 自动填 exp,iat 自动填充;payload.exp 已给时跳过 |
token: eyJ...claims: {"sub":"u1","iat":1767225600,"exp":1767231600} |
verify |
完整校验:alg=HS256、常数时间签名比较、过期判定(可选 leewaySeconds) |
valid: truepayload: {"sub":"u1","exp":1767231600}expiry.expiresAt: 2026-01-01T01:00:00.000Z |
示例
jwt { action: "decode", token: "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1MSJ9.sig" }
→ header: {"alg":"HS256","typ":"JWT"}
payload: {"sub":"u1"}
expiry.status: no-exp-claim
note: decode does not verify the signature
jwt { action: "sign", payload: {"sub":"u1","role":"admin"}, secret: "topsecret", expiresInSeconds: 3600 }
→ token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1MSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc2NzIyNTYwMCwiZXhwIjoxNzY3MjI5MjAwfQ.<sig>
claims: {"sub":"u1","role":"admin","iat":1767225600,"exp":1767229200}
jwt { action: "verify", token: "eyJ...", secret: "topsecret", leewaySeconds: 30 }
→ valid: true
payload: {"sub":"u1","role":"admin","iat":1767225600,"exp":1767229200}
expiry.expiresAt: 2026-01-01T01:00:00.000Z
边界行为
| 情况 | 处理 |
|---|---|
| 非三段的 token | jwt: token must have exactly three segments |
| 非 JSON header/payload | jwt: header/payload is not valid JSON |
| 非对象的 header/payload | jwt: header/payload JSON must be an object |
| 无效 base64url | jwt: invalid base64url input: "..." |
exp 非数字 |
jwt: exp claim must be a number |
exp 正好等于当前时间 |
expired(RFC 7519:exp 必须严格大于当前时间) |
无 exp 声明 |
no-exp-claim |
| token 超 16KB / secret 超 4KB / payload 超 8KB | 入口拒绝(不截断) |
secret 与 secretBase64url 同时给 |
入口拒绝 |
| 签名不匹配 | valid: false + reason: bad-signature |
alg 非 HS256 |
valid: false + reason: wrong-alg |
| 过期 token | valid: false + reason: expired(leewaySeconds 内可容错) |
| 空 secret | jwt: secret must be a non-empty string |
| RFC 7515 二进制密钥 | 通过 secretBase64url 传入原始字节(Buffer.from(key, 'base64url')) |
正确性
签名逻辑由 RFC 7515 Appendix A.1 官方 HS256 测试向量验证——tests/jwt-sign.spec.ts 中 verifyJwt: RFC vector 测试组确认本实现与 RFC 标准输出逐字节一致。
安装
Profile Bundle(推荐)
将本插件作为独立 bundle 安装到 profile:
# 交互式(web)profile —— 从 GitHub 仓库安装
dsh plugin --profile web add github:chenxuhl/dsh-tool-jwt
# 一次性任务(headless)profile —— dsh run 默认使用 headless
dsh plugin --profile headless add github:chenxuhl/dsh-tool-jwt
或从 npm pack 生成的 tarball 安装:
npm pack # 生成 dsh-tool-jwt-<version>.tgz
# 交互式(web)profile
dsh plugin --profile web add ./dsh-tool-jwt-<version>.tgz
# 一次性任务(headless)profile
dsh plugin --profile headless add ./dsh-tool-jwt-<version>.tgz
包内 dsh.bundle.patch 会在安装后自动把插件加入 profile 的 layer stack(row id:tool-jwt)。插件缺失的 peer 依赖(@deepseek-ai/cordis、@deepseek-ai/dsh-tools)由 profile 的 healed profiles/node_modules 回退安装提供。
⚠️ web 与 headless 是不同 profile:web 安装不会自动覆盖 headless;
dsh run默认使用 headless profile。Windows 路径使用正斜杠(C:/...)。
本地开发(link 安装)
git clone https://github.com/chenxuhl/dsh-tool-jwt.git
cd dsh-tool-jwt && npm install && npm run build
dsh plugin --profile web add link:<repo-path>
Windows + pnpm
link:协议注意:部分 pnpm 版本会把link:D:\...反斜杠路径错误拼接。使用正斜杠link:D:/...;若 junction 仍指向错误路径,手动New-Item -ItemType Junction修复node_modules内的链接即可。
验证安装
dsh --profile web --dump-config | grep tool-jwt
运行验证
dsh run "用 jwt 工具 decode 一下 eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1MSJ9.sig"
测试
npm test
base64url.spec.ts:编解码全分支 + 非法字符 + 非字符串输入 + 大小写/填充变体jwt-decode.spec.ts:三段拆分 + JSON 解析 + 过期状态(valid/expired/no-exp-claim/边界) + 大小守卫jwt-sign.spec.ts:RFC 7515 A.1 官方 HS256 向量 + 自签 round-trip + 错密钥 + 过期 + leeway 容错 + 篡改检测 + 非 HS256 算法拒绝register.spec.ts:注册契约(AUDIT-CROSS-02 风格)
许可
MIT
No comments yet. Be the first to write one.