最近由于項(xiàng)目需要,學(xué)習(xí)了一下JWT這種身份驗(yàn)證的方法。在這里做一下總結(jié)。
本文主要是做一些總結(jié),參考文章鏈接:JSON Web Token - 在Web應(yīng)用間安全地傳遞信息
什么是JWT
JWT是一種用于雙方之間傳遞安全信息的簡潔的、URL安全的表述性聲明規(guī)范。
- 簡潔(Compact): 可以通過URL,POST參數(shù)或者在HTTP header發(fā)送,因?yàn)閿?shù)據(jù)量小,傳輸速度也很快;
- 自包含(Self-contained):負(fù)載中包含了所有用戶所需要的信息,避免了多次查詢數(shù)據(jù)庫;
JWT結(jié)構(gòu)簡介
JWT結(jié)構(gòu):
-
Headers頭部 -
Payload負(fù)載 -
Signature簽名
整體結(jié)構(gòu)為:
xxxxx.yyyyy.zzzzz
Headers
在headers中通常包含了兩部分: token類型和采用的加密算法
{
"alg": "HS256",
"typ": "JWT"
}
- alg: 算法類型
- typ: token類型
Payload
Token的第二部分時負(fù)載,它包含了claim,Claim是一些實(shí)體(通常指的用戶)的狀態(tài)和額外的元數(shù)據(jù),有三種類型的claim: reserved,public和private.
-
Reserved claims:這些claim是JWT預(yù)先定義的,在JWT中并不會強(qiáng)制使用它們,而是推薦使用,常用的有: - iss(簽發(fā)者)
- exp(過期時間戳)
- sub(面向的用戶)
- aud(接收方)
- iat(簽發(fā)時間)
-
Public claims: 根據(jù)需要定義自己的字段,注意應(yīng)該避免沖突 -
Private claims: 這些是自定義的字段,可以用來在雙方之間交換信息
舉個栗子:
{
"sub": "4008517517",
"name": "John Doe",
"admin": true
}
Signature
創(chuàng)建簽名需要使用編碼后的headers和payload以及一個密鑰(secret),使用headers中制定簽名進(jìn)行簽名算法進(jìn)行簽名.例如如果希望使用HMAC SHA256算法,那么簽名應(yīng)該使用下列方式創(chuàng)建:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
簽名用于驗(yàn)證消息的發(fā)送者以及消息是沒有經(jīng)過篡改的.
完整的JWT
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0Nzk0NTYxMzI5NjJ9.yWGqnRYMhdLOEHlLI3_WIpHPtX3QHEdpJQHx_B0meaA
實(shí)現(xiàn)步驟
- 客戶端發(fā)送用戶名與密碼到服務(wù)器
- 服務(wù)器進(jìn)行信息核對
- 服務(wù)器生成token(
payload中包含的為user_id) - 服務(wù)器應(yīng)用返回token,以后每次用戶的請求都帶上token
- 服務(wù)器應(yīng)用驗(yàn)證token有效性.例如:簽名是否正確?token是否過期?檢查token的接收方是否為自己(可選)?
- 服務(wù)器應(yīng)用返回響應(yīng)的信息
node模塊jwt-simple
使用express框架,jwt-simple模塊來構(gòu)建中間件(另外,我在github上邊也找到express-jwt模塊,好像也還不錯的樣子,但是沒有實(shí)踐過)。
產(chǎn)生token
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as jwt from 'jwt-simple';
let app = express();
app.set('jwtTokenSecret', 'phpsucks!');
app.use(bodyParser.json());
app.post('/', (req, res) => {
let user = req.body;
// 產(chǎn)生token過期時間,這里設(shè)置
let expires = Date.now() + 7*24*60*60*1000;
let token = jwt.encode({
iss: user.id, // issuer 表明請求的實(shí)體
exp: expires, // expires token的生命周期
aud: 'jser'
}, app.get('jwtTokenSecret'));
res.json({
token: token,
expires: expires
});
});
獲取token并解析
app.get('/', (req, res) => {
// 獲取token,這里默認(rèn)是放在headers的authorization
let token = req.headers.authorization;
if (token) {
let decoded = jwt.decode(token, app.get('jwtTokenSecret'));
// 判斷是否token已過期以及接收方是否為自己
if(decoded.exp <= Date.now() || decoded.aud !== 'jser') {
res.sendStatus(401)
} else {
res.sendStatus(200)
}
} else {
res.sendStatus(401)
}
});
相對于常用的session而言,JWT通過將狀態(tài)的記錄放置于客戶端,從而降低服務(wù)端因反復(fù)查詢數(shù)據(jù)庫而產(chǎn)生的壓力。這應(yīng)該就是JWT的最大優(yōu)點(diǎn)了吧!