什么是JWT?本篇文章带大家了解一下JWT,介绍一下JWT在node中的应用,以及JWT的优缺点,希望对大家有所帮助!
什么是JWT
JWT也就是JSON Web Token的缩写,也就是为了在网络应用环境中一种认证解决方案,在传统的认证机制中,无非是一下几个步骤:
1. 用户将账号密码发送到服务器;
2. 服务器通过验证账号密码后,会在当前session中保存一些用户相关的信息,用户角色或者过期时间等等;
3. 服务器给用户一个session_id, 写入用户的Cookie或者客户端自行保存在本地;
4. 用户每次请求服务,都需要带上这个session_id,或许会通过Cookie,或者其他的方式;
5. 服务器接收到后,回去数据库查询当前的session_id,校验该用户是否有权限;
这种模式有一种优势在于,服务器随时可以终止用户的权限,可以去数据库修改或者删除当前用户的session信息。但是也有一点不好的,就是如果是服务器集群的话,所有的机器就需要共享这些session信息,确保每台服务器都能够获取到相同的session存储信息。虽然可以解决这些问题,但是工程量巨大。
JWT方案的优势呢,就是不保存这些信息,token数据保存在客户端,每次接受请求时,只需要校验就好。
JWT的原理
简单说一下JWT的原理,其实就是客户端发送请求认证的时候,服务器在认证用户之后,会生成一个JSON对象,大概包括“你是谁,你是干嘛的等等,到期时间”这些信息,重要的是一定要有到期时间;大致格式为:
{username:"贼烦字符串er",role:"世代码农",endTime:"2022年5月20日"}
但是不会用这么肤浅的方式传给你,它会通过制定的签名算法和你提交的payload的一些信息进行可逆的签名算法进行签名后传输,大致的格式我用一张图片表示:
由图片可以看出,返回的信息大致分为三部分,左侧为签名之后的结果,也就是返回给客户端的结果,右侧也是就Decoded的源码了,三部分由“点”隔开,分别由红、紫、青三种颜色一一对应:
第一个红色部分是Header,Header中主要是指定了的方式,图中的签名算法(默认HS256)就是带有 SHA-256 的 HMAC 是一种对称算法, 双方之间仅共享一个密钥,typ字段标识为JWT类型;
第二个紫色部分payload,就是一个JSON对象,也就是实际要传输的数据,官方有七个字段可以使用:
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了这些字段,你还可以搞一些自定义的字段,由于JWT默认是不加密的,所以在使用的时候尽量注意不要使用一些敏感数据。
第三部分就是Signature签名,这一部分,是由你自己指定且只有服务器存在的秘钥,然后使用头部指定的算法通过下面的签名方法进行签名。
JWT的简单使用
下面我们来感受一下具体的使用:
第一步:我们需要搭建一个nodejs的项目;通过npm init -y初始化一个项目;之后我们需要安装依赖,分别按状express、jsonwebtoken和nodemon三个依赖:
npmiexpressjsonwebtokennodemon
之后在package.json中的scripts字段中添加nodemon app.js命令:
"scripts":{"start":"nodemonapp.js"},
第二步:初始化一下node应用,在根目录下创建app.js文件;
//app.jsconstexpress=require("express");constapp=express();app.use(express.json());app.listen(3000,()=>{console.log(3000+"listening…");//监听3000端口});
第三步:引入jsonwebtoken依赖,并且创建接口和服务器的私钥;
//app.js//…constjwt=require("jsonwebtoken");constjwtKey="~!@#$%^&*()+,";//…
这里面的jwtKey是我们自定义保存仅限保存在服务器中的私钥,之后我们开始写一个 /login 接口,用来登录,并且创建本地的模拟数据库用来校验,并通过jwt.sign方法进行校验签名:
//app.jsconstdatabase={username:"username",password:"password",};app.post("/login",(req,res)=>{const{username,password}=req.body;if(username===database.username&&password===database.password){jwt.sign({username,},jwtKey,{expiresIn:"30S",},(_,token)=>{res.json({username,message:"登陆成功",token,});});}});
上面代码中我们创建了database变量来模拟创建了本地的账号密码数据库,用来校验登陆;接下来建立了一个/login的post接口,在校验账号密码完全匹配之后,我们通过jsonwebtoken包导入的jwt对象下的人sign方法进行签名,这个方法有三种接口签名:
exportfunctionsign(payload:string|Buffer|object,secretOrPrivateKey:Secret,options?:SignOptions,):string;exportfunctionsign(payload:string|Buffer|object,secretOrPrivateKey:Secret,callback:SignCallback,):void;exportfunctionsign(payload:string|Buffer|object,secretOrPrivateKey:Secret,options:SignOptions,callback:SignCallback,):void;
这里用到了函数重载的方式实现接口,我们这里将实现最后一个接口签名,第一个参数可以是一个自定义的对象类型,也可以是一个Buffer类型,还可以直接是一个string类型,我们的源码使用了object类型,自定义了一些字段,因为jwt在进行签名是也会对这些数据一并进行签名,但是值得注意的是,这里尽量不要使用敏感数据,因为JWT默认是不加密的,它的核心就是签名,保证数据未被篡改,而检查签名的过程就叫做验证。
当然你也可以对原始Token进行加密后传输;
第二个参数:是我们保存在服务器用来签名的秘钥,通常在客户端-服务端模式中,JWS 使用 JWA 提供的 HS256 算法加上一个密钥即可,这种方式严格依赖密钥,但在分布式场景,可能多个服务都需要验证JWT,若要在每个服务里面都保存密钥,那么安全性将会大打折扣,要知道,密钥一旦泄露,任何人都可以随意伪造JWT。
第三个参数:是签名的选项SignOptions,接口的签名:
exportinterfaceSignOptions{algorithm?:Algorithm|undefined;keyid?:string|undefined;expiresIn?:string|number|undefined;/**expressedinsecondsorastringdescribingatimespan[zeit/ms](https://github.com/zeit/ms.js).Eg:60,"2days","10h","7d"*/notBefore?:string|number|undefined;audience?:string|string[]|undefined;subject?:string|undefined;issuer?:string|undefined;jwtid?:string|undefined;mutatePayload?:boolean|undefined;noTimestamp?:boolean|undefined;header?:JwtHeader|undefined;encoding?:string|undefined;}
这里我们用的是expiresIn字段,指定了时效时间,使用方法参考这个文档;
第四个参数是一个回调,回调的第二个参数就是我们通过签名生成的token,最后将这个token返回给前端,以便存储到前端本地每次请求是带上到服务端进行验证。
接下来,我们来验证一下这个接口: 我是在vscode安装的REST Client插件,之后在根目录创建一个request.http的文件,文件内写上请求的信息:
POSThttp://localhost:3000/logincontent-type:application/json{"username":"username","password":"password"}
之后在命令行执行npm run start命令启动服务,之后在requset.http文件上方点击Send Request按钮,发送请求:
请求成功后,会看到这样的响应报文:
token字段就是我们JWT生成的token;
下面来验证一下这个token是否有效,我们在写一个登录过后的接口:
app.get("/afterlogin",(req,res)=>{const{headers}=req;consttoken=headers["authorization"].split("")[1];//将token放在header的authorization字段中jwt.verify(token,jwtKey,(err,payload)=>{if(err)returnres.sendStatus(403);res.json({message:"认证成功",payload});});});
这段代码中,通过获取请求头中的authorization字段中的token进行获取之前通过JWT生成的token。 之后通过调用jwt.verify校验方法校验这个token是否有效,这个方法分别有三个参数:
//有四个接口签名,可以自行查文档exportfunctionverify(token:string,//需要检验的tokensecretOrPublicKey:Secret|GetPublicKeyOrSecret,//定义在服务器的签名秘钥callback?:VerifyCallback<JwtPayload|string>,//获取校验信息结果的回调):void;
接下来我们把刚才响应的token复制到请求头中:
###GEThttp://localhost:3000/afterloginauthorization:BearereyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwiaWF0IjoxNjUyNzg5NzA3LCJleHAiOjE2NTI3ODk3Mzd9.s9fk3YLhxTUcpUgCfIK4xQN58Hk_XEP5y9GM9A8jBbY
前面的Bearer认证, 是http协议中的标准认证方式
同样点击Send Request当看到下面图片的响应,就意味着响应成功:
其实以上就是JWT的一些简单的用法,接下来再说一下JWT本身存在的优缺点.
JWT的不足
JWT占用的存储空间其实并不小,如果我们需要签名做过多的信息,那么token很可能会超出cookie的长度限制,例如对比一下这两张图片:
很明显,随着payload的信息量增大,token的长度也会增加;
安全性,其实如果token的占用空间过大,Cookie最大存储空间只有4kb前端可以存储在localStorage之类的本地存储,但是会带来一个问题,如果不是放在cookie的话,安全性就会大打折扣,就会有通过js脚本获取到的风险,就意味着任何hacker都可以拿着它做任何事情;
不灵活的时效性,其实JWT的某方面意义在于用户token不需要持久化存储,而是采用服务器校验的方式对token进行有效校验,刚才看到了,签名也是把到期时间一并签名的,如果改变到期时间token就会被篡改,由于没有存储和手动更改时效的方法,所以很难立刻将这个token删掉,如果用户重复登陆两次,生成两个token,那么原则上两个token都是有效的;
总结
以上主要讲了几点:
JWT的原理,主要是通过服务器的私钥对JSON的签名产生的token进行会话;
也介绍了JWT内部数据的组成,是由Header用来指定签名算法和类型的,payload来传输JSON数据,Signature来对数据进行签名算法,放置篡改;
具体介绍了一下如何通过nodejs使用JWT,通过sign方法进行数据签名,verify方法进行签名验证;
还介绍了一些JWT的不足:
一个是存储空间随着签名数据量的增大而增加;
再有就是安全性,如果由于存储空间过大将无法存储在安全级别相对较高的Cookie中,导致脚本可以随意获取;
再有就是时效性,无法灵活的控制token的时效性;
这个是上面nodejs的demo源码,可供参考;
https://github.com/wangzi6224/jwt-usage-nodejs
产品猿社区致力收录更多优质的商业产品,给服务商以及软件采购客户提供更多优质的软件产品,帮助开发者变现来实现多方共赢;
日常运营的过程中我们难免会遇到各种版权纠纷等问题,如果您在社区内发现有您的产品未经您授权而被用户提供下载或使用,您可按照我们投诉流程处理,点我投诉;
本文来自用户发布投稿,不代表产品猿立场 ;若对此文有疑问或内容有严重错误,可联系平台客服反馈;
部分产品是用户投稿,可能本文没有提供官方下下载地址或教程,若您看到的内容没有下载入口,您可以在我们产品园商城搜索看开发者是否有发布商品;若您是开发者,也诚邀您入驻商城平台发布的产品,地址:点我进入;
如若转载,请注明出处:https://www.chanpinyuan.cn/28058.html;