什麼是JWT?這篇文章帶大家了解JWT,介紹一下JWT在node中的應用,以及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的原理,其實就是客戶端發送請求認證的時候,伺服器在認證使用者之後,會產生一個JSON對象,大概包括「你是誰,你是幹嘛的等等,到期時間」這些訊息,重要的是一定要有到期時間;大致格式為:
{ username: "贼烦字符串er", role: "世代码农", endTime: "2022年5月20日" }
但是不會用這麼膚淺的方式傳給你,它會透過制定的簽章演算法和你提交的payload的一些資訊進行可逆的簽章演算法進行簽章後傳輸,大致的格式我用一張圖片表示:
由圖片可以看出,回傳的訊息大致分為三部分,左側為簽名之後的結果,也就是傳回給客戶端的結果,右邊也是就Decoded的原始碼了,三部分由「點」隔開,分別由紅、紫、青三種顏色一一對應:
第一個紅色部分是Header,Header中主要是指定了的方式,圖中的簽章演算法(預設HS256)就是有SHA-256 的HMAC 是一種對稱演算法, 雙方之間僅共用一個金鑰,typ欄位標示為JWT型別;
第二個紫色部分payload,就是一個JSON對象,也就是實際要傳輸的數據,官方有七個欄位可以使用:
除了這些字段,你還可以搞一些自訂的字段,由於JWT預設是不加密的,所以在使用的時候盡量注意不要使用一些敏感資料。
第三部分就是Signature
簽名,這一部分,是由你自己指定且只有伺服器存在的秘鑰,然後使用頭部指定的演算法通過下面的簽名方法進行簽名。
下面我們來感受一下具體的使用:
第一步:我們需要搭建一個nodejs的專案;透過npm init -y
初始化一個專案;之後我們需要安裝依賴,分別按狀express
、jsonwebtoken
和nodemon
三個依賴:
$ npm i express jsonwebtoken nodemon
之後在package.json
中的scripts
欄位中加入nodemon app.js
指令:
"scripts": { "start": "nodemon app.js" },
第二步:初始化一下node應用,在根目錄下建立app.js檔案;
// app.js const express = require("express"); const app = express(); app.use(express.json()); app.listen(3000, () => { console.log(3000 + " listening..."); // 监听3000端口 });
第三步:引入jsonwebtoken
依賴,並且建立介面和伺服器的私鑰;
// app.js //... const jwt = require("jsonwebtoken"); const jwtKey = "~!@#$%^&*()+,"; // ...
這裡面的jwtKey
是我們自訂保存僅限保存在伺服器中的私鑰,之後我們開始寫一個/login 接口,用來登錄,並且建立本地的模擬資料庫用來校驗,並透過jwt.sign
方法進行校驗簽章:
// app.js const database = { 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
#方法進行簽名,這個方法有三種介面簽名:
export function sign( payload: string | Buffer | object, secretOrPrivateKey: Secret, options?: SignOptions, ): string; export function sign( payload: string | Buffer | object, secretOrPrivateKey: Secret, callback: SignCallback, ): void; export function sign( payload: string | Buffer | object, secretOrPrivateKey: Secret, options: SignOptions, callback: SignCallback, ): void;
这里用到了函数重载的方式实现接口,我们这里将实现最后一个接口签名,第一个参数可以是一个自定义的对象类型,也可以是一个Buffer
类型,还可以直接是一个string
类型,我们的源码使用了object
类型,自定义了一些字段,因为jwt在进行签名是也会对这些数据一并进行签名,但是值得注意的是,这里尽量不要使用敏感数据,因为JWT默认是不加密的,它的核心就是签名,保证数据未被篡改,而检查签名的过程就叫做验证。
当然你也可以对原始Token进行加密后传输;
第二个参数:是我们保存在服务器用来签名的秘钥,通常在客户端-服务端模式中,JWS 使用 JWA 提供的 HS256 算法加上一个密钥即可,这种方式严格依赖密钥,但在分布式场景,可能多个服务都需要验证JWT,若要在每个服务里面都保存密钥,那么安全性将会大打折扣,要知道,密钥一旦泄露,任何人都可以随意伪造JWT。
第三个参数:是签名的选项SignOptions
,接口的签名:
export interface SignOptions { algorithm?: Algorithm | undefined; keyid?: string | undefined; expiresIn?: string | number | undefined; /** expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js). Eg: 60, "2 days", "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
的文件,文件内写上请求的信息:
POST http://localhost:3000/login content-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; const token = headers["authorization"].split(" ")[1]; // 将token放在header的authorization字段中 jwt.verify(token, jwtKey, (err, payload) => { if (err) return res.sendStatus(403); res.json({ message: "认证成功", payload }); }); });
这段代码中,通过获取请求头中的authorization
字段中的token
进行获取之前通过JWT生成的token
。
之后通过调用jwt.verify
校验方法校验这个token
是否有效,这个方法分别有三个参数:
// 有四个接口签名,可以自行查文档 export function verify( token: string, // 需要检验的token secretOrPublicKey: Secret | GetPublicKeyOrSecret, // 定义在服务器的签名秘钥 callback?: VerifyCallback<JwtPayload | string>, // 获取校验信息结果的回调 ): void;
接下来我们把刚才响应的token
复制到请求头中:
### GET http://localhost:3000/afterlogin authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwiaWF0IjoxNjUyNzg5NzA3LCJleHAiOjE2NTI3ODk3Mzd9.s9fk3YLhxTUcpUgCfIK4xQN58Hk_XEP5y9GM9A8jBbY
前面的Bearer认证, 是http协议中的标准认证方式
同样点击Send Request
当看到下面图片的响应,就意味着响应成功:
其实以上就是JWT的一些简单的用法,接下来再说一下JWT本身存在的优缺点.
JWT占用的存储空间其实并不小,如果我们需要签名做过多的信息,那么token很可能会超出cookie的长度限制,例如对比一下这两张图片:
很明显,随着payload的信息量增大,token的长度也会增加;
安全性,其实如果token
的占用空间过大,Cookie
最大存储空间只有4kb前端可以存储在localStorage
之类的本地存储,但是会带来一个问题,如果不是放在cookie的话,安全性就会大打折扣,就会有通过js脚本获取到的风险,就意味着任何hacker都可以拿着它做任何事情;
不灵活的时效性,其实JWT的某方面意义在于用户token
不需要持久化存储,而是采用服务器校验的方式对token
进行有效校验,刚才看到了,签名也是把到期时间一并签名的,如果改变到期时间token
就会被篡改,由于没有存储和手动更改时效的方法,所以很难立刻将这个token
删掉,如果用户重复登陆两次,生成两个token
,那么原则上两个token
都是有效的;
以上主要讲了几点:
JWT的原理,主要是透過伺服器的私鑰對JSON的簽章產生的token
進行會話;
方法進行資料簽名,verify
方法進行簽名驗證;
,可供參考;!https://github.com/wangzi6224/jwt-usage-nodejs
更多node相關知識,請造訪:
nodejs 教學
以上是什麼是JWT? JWT怎麼在nodejs使用?的詳細內容。更多資訊請關注PHP中文網其他相關文章!