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
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。