更新Node.js包后jsonwebtoken.sign()无法使用环境变量作为expiresIn参数的类型错误问题
我之前升级jsonwebtoken包后也碰到过一模一样的情况——之前运行正常的代码,升级后只要用环境变量传expiresIn就报类型错误,硬编码具体值就一切正常。这其实是因为新版本的jsonwebtoken对TypeScript类型检查的约束变得更严格了,尤其是对SignOptions配置项里的expiresIn参数和密钥参数的类型定义做了更精确的限制。
问题根源
升级后的jsonwebtoken包,把expiresIn的类型从宽泛的string | number | undefined细化成了更严格的类型(要求是符合JWT时长格式的字符串,比如"100d"、"24h"这类);而process.env里的变量默认被TypeScript推断为string | undefined,哪怕做了空值合并处理,TS也无法确认这个字符串是否符合expiresIn要求的格式,所以会抛出类型不匹配的错误。另外你的错误信息里还提到了密钥的问题,是因为新版本对secretOrPrivateKey的类型也做了收紧,直接传process.env.JWT_KEY as string已经不符合它的类型要求了。
解决方法
我试过几个可行的方案,你可以参考:
方案1:明确断言为对应类型(最简单直接)
先从jsonwebtoken导入对应的类型定义,然后把环境变量的值断言为SignOptions['expiresIn'],同时把密钥断言为Secret类型:
import jwt, { SignOptions, Secret } from 'jsonwebtoken'; // 生成JWT的代码 const user_jwt = jwt.sign( { user_id: user_id.uuid }, process.env.JWT_KEY as Secret, { expiresIn: (process.env.JWT_EXPIRES_IN ?? '100d') as SignOptions['expiresIn'], } );
方案2:用类型守卫缩小类型范围(更严谨)
如果担心环境变量的值格式不符合要求,可以加个类型守卫来校验,让TypeScript信任这个值的合法性:
import jwt, { SignOptions, Secret } from 'jsonwebtoken'; // 校验JWT过期时间格式是否合法的类型守卫函数 function isValidJwtExpiry(value: string): value is SignOptions['expiresIn'] { // 匹配类似"100d"、"24h"、"30m"的标准时长格式 return /^\d+(s|m|h|d|w|M|Y)$/i.test(value); } // 先处理环境变量 const rawExpiresIn = process.env.JWT_EXPIRES_IN ?? '100d'; const expiresIn = isValidJwtExpiry(rawExpiresIn) ? rawExpiresIn : '100d'; // 生成JWT const user_jwt = jwt.sign( { user_id: user_id.uuid }, process.env.JWT_KEY as Secret, { expiresIn } );
方案3:用环境变量解析工具(长期维护更友好)
如果你的项目里有很多环境变量需要处理,可以用zod、envalid这类工具来统一解析环境变量,它们能自动给变量加上符合要求的TypeScript类型:
比如用envalid的示例:
import { cleanEnv, str } from 'envalid'; import jwt, { SignOptions } from 'jsonwebtoken'; // 解析环境变量并生成强类型的配置 const env = cleanEnv(process.env, { JWT_KEY: str(), JWT_EXPIRES_IN: str({ default: '100d' }), }); // 生成JWT const user_jwt = jwt.sign( { user_id: user_id.uuid }, env.JWT_KEY, { expiresIn: env.JWT_EXPIRES_IN as SignOptions['expiresIn'], } );
为什么之前的空值合并没用?
因为新版本的jsonwebtoken类型定义里,expiresIn不再接受任意字符串,只接受符合特定时长格式的字符串。空值合并只是给了变量一个默认值,但TypeScript还是把它当成普通string类型,无法通过更严格的类型校验,所以需要我们明确告诉TS这个值是符合要求的。
内容来源于stack exchange

