JWT鉴权,JsonWebToken如何生成令牌与鉴定?Node环境
发布于 作者:苏南大叔 来源:程序如此灵动~
现代各种网络接口的鉴权,都离不开各种令牌token。其中,使用最普遍的就是JWT令牌,全称JsonWebToken。本文就是基于node环境,测试jwt令牌的生成与验证。当然,其它语言中也有jwt令牌的实现,并不局限于node环境。

苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。测试环境:nodejs@20.18.0,JsonWebToken@9.0.2。本文的代码很简单,就是JWT令牌的生成与解密。
写在前面
现在的各种网络接口鉴权,都不走原来的session/cookie那一套理论了,都是走token令牌鉴权了。苏南大叔对这种token鉴权不太喜欢,因为:
- 理论上来说,泄漏了
token也就相当于密码丢失了。并且这是一种无状态服务,无法封杀被泄漏的令牌,在有效期结束之前,【无论是否修改密码】,这个令牌都是生效的。(除非在使用令牌的时候,再做其它逻辑) - 很多网络服务这种
token的过期时间又很长很长,很久之后才会失效。即使修改密码的话,原token依然有效。也就是说,一旦拿到token,理论上就可以几乎“永久”劫持。 - 令牌是可以解密的,不要放置私密信息到
payload里面!没有服务器端的加密密码的话,仅仅是无法通过数据验证而已。但是,内容是可以随意解密的!
官方文档
jsonwebtoken的官方网站地址:

这里有很多语言版本的实现,本文是基于node环境的。苏南大叔选择的这个node环境下的jsonwebtoken实现。参考:
需要先安装jsonwebtoken。参考命令:
npm i jsonwebtoken --save生成令牌(加密)
实际上这个令牌的生成过程,可以理解为对【目标信息】,使用一个密码进行加密的行为。它支持很多种加密算法,默认的算法是HS256。加密的时候,可以设定有效期,超过有效期的数据,会报错jwt expired。并不是说不能解密,而是说解密得到的信息是个“过期状态”。
要用来加密的数据,在这里称之为payload。
const jwt = require("jsonwebtoken");
const secret = "数据验证密码sunan"; // 用于verify的密码
const payload = {
user: "sunan大叔", // 真正的数据,然而它是可以无需密码就可以解密出来的
// iat: Math.floor(Date.now() / 1000), // 控制签发时间,当前Unix时间戳
};
const options = {
expiresIn: "5000", // 秒s分钟m小时h天d周w年y,大小写无关,月的写法未知,默认单位毫秒ms
algorithm: "HS256", // 默认算法是 HS256,会出现在令牌的header里面
issuer: "令牌颁发者苏南", // 颁发者,会出现在令牌的payload里面
audience: "令牌接收者sunan", // 接收者,会出现在令牌的payload里面
// ...
};
const token = jwt.sign(payload, secret, options);
console.log(token); // 多次加密结果保持一致
令牌生成时间 iat
在这里,令牌的生成时间叫做iat。
通过主动设置payload的iat,可以控制生成时间为过去或者未来。但是,好像没有太大的意义。它的合法值是当前的unix时间戳,单位是秒!!!不是毫秒!!!所以要除以1000!!!
它结合expireIn时间段,可以生成一个令牌的exp属性,以控制令牌的有效期。参考:
const payload = {
user: "sunan大叔", // 真正的数据,然而它是可以无需密码就可以解密出来的
iat: Math.floor(Date.now() / 1000), // 控制签发时间,当前Unix时间戳
};过期时间 expireIn
过期时间expireIn的默认单位是毫秒。expireIn将叠加iat之后,将转化为payload中的exp属性。
不写任何单位的话,就会把数字单位理解为毫秒。它【不区分大小写】。由于这个特点,目前月份M的表述失效,变成了分钟m。所以,除了月份外,其它的时间点都可以正常表述。比如:秒s、分钟m、小时h、天d、周w、年y。
目前的解决方案是:月份为单位的话,就需要转化为d,或者m/s之类的进行表述。
传递令牌
传递令牌,并不是JsonWebToken的分内事情,所以这里一笔带过。
正常情况下来说,token被生成后。传递给客户端,一般被保存在localStorage里面。需要通过访问网络接口的时候,通过修改请求头里面的Authorization:Bearer <token>来进行传递。这是一个承上启下的过渡过程。
令牌解密
目前的实验配置中,多次生成令牌token,会得到一样的结果,多次“加密”结果一致。不要认为这个令牌token是多么的可靠。它是可以无密码解密的,密码仅仅影响是的数据验证部分。
令牌验证
令牌token的解密结果,并不是原版的payload数据,在其基础上增加了令牌生成时间iat,令牌过期时间exp,iss令牌颁发者等信息。
jwt的解密算法是:
try {
let payload = jwt.verify(token, secretKey);
console.log(payload);
} catch (e) {
console.log(e.message);
}
因为存在解密失败的可能性。所以,正常来说,需要加个try{}catch(){}。令牌已过期“jwt expired”,也算一种需要被截获的错误。
如果,解析成功的话,则可以发现payload里面,除了原始数据外,还存在着:
iat,可定义在data里面。不定义的话,就是当前UTC时间戳。exp,实际的过期时间戳,UTC【秒】级别时间戳。注意:单位是秒而不是毫秒。iss,定义来自于options的issuer属性。意思是令牌的发布者。aud,定义来自于options的audience属性。意思是令牌的接收者。
时间可读
实际上针对的就是payload里面的iat和exp。它们两个如何解读为可以正常理解的时间的话。可以参考代码:
const iat = new Date(payload.iat * 1000);
console.log(iat.toLocaleString("zh-CN"));
const exp = new Date(payload.exp * 1000);
console.log(exp.toLocaleString("zh-CN"));令牌格式
令牌格式上分为三部分,格式是:header.payload.verify。使用.进行分割。
- 第一部分是
header,内容包括算法等信息。 - 第二部分是
payload,最重点的部分!它仅仅是个加密的结果,可以直接无密码解密! - 第三部分是
verify部分,这个地方才用的到服务器端报错的那个secret。
所以,秘密信息payload都已经被解出来了。伪造的verify部分确实无法通过服务器端验证。但是,如果这个token是被泄漏的那种呢?verify部分也是可以通过验证的。
官方解密
jwt官方提供了令牌解密的功能:

这个官方解密界面,并不知道测试数据是使用什么secret加密的。所以,它解密出了header和payload部分(获得了数据格式)。第三方部分verify部分是失败的。随便填入一个secret的话,它可以自动修改主界面上用来测试数据的第三部分verify部分,使它变成一个合法的token。

也就是说:
得到一个合法的token的话,就可以分析出加密算法以及payload的格式。还可以在到期之前,获得接口承认的合法身份。
一旦服务器端的secret泄漏的话,就可以批量伪造任何合法的token了。
相关文章
- https://newsn.net/say/javascript-obfuscator.html
- https://newsn.net/say/enphp2-decode.html
- https://newsn.net/say/php-crack-crane.html
以前也写过其它的类似鉴权方式,参考:
- https://newsn.net/say/php-ntlm.html
- https://newsn.net/say/php-digest-realm.html
- https://newsn.net/say/php-401.html