一.前沿
jwt是json web token縮寫。它將用戶信息加密到token里,服務(wù)器不保存任何用戶信息,服務(wù)器通過保存的密鑰驗(yàn)證token的正確性,只要正確即通過驗(yàn)證。
優(yōu)點(diǎn)是在分布式系統(tǒng)中,很好地解決了單點(diǎn)登錄問題,很容易解決了session共享的問題。比如一個(gè)公司有很多相關(guān)聯(lián)的網(wǎng)站,比如A,B,C。通常希望用戶在登陸A網(wǎng)站以后,再訪問B,C網(wǎng)站能夠自動(dòng)登錄(也就是所謂的單點(diǎn)登陸)。
缺點(diǎn)是無法作廢已頒布的令牌/不易應(yīng)對數(shù)據(jù)過期。比如說,我登陸了A網(wǎng)站,再登錄B網(wǎng)站,在token過期之前,token始終是有效的,無法廢除token的有效性。
二.jwt原理
JWT的原理是服務(wù)器認(rèn)證以后,生成一個(gè)下面所示的對象,發(fā)送給用戶。
{
name:hello,
roles: 管理員,
expires:2018年11月
}
以后,用戶與服務(wù)端通信的時(shí)候,都要發(fā)回這個(gè) JSON 對象。服務(wù)器完全只靠這個(gè)對象認(rèn)定用戶身份。當(dāng)然,出于安全的考慮,服務(wù)器不能發(fā)送明文的數(shù)據(jù)給用戶,通常需要對這個(gè)對象進(jìn)行加密。
三.jwt加密生成token
這里使用了jsonwebtoken來生成token
jwt.sign(payload, secretOrPrivateKey, [options, callback])
1.Payload 部分是一個(gè) JSON 對象,用來存放實(shí)際需要傳遞的數(shù)據(jù).比如:
{
id:user.id,
name:user.name
}
2.secretOrPrivateKey:是加密的名字,可以自己定義
3.第三個(gè)參數(shù)是可選的,主要包括加密方式,過期時(shí)間等,一般只需要設(shè)置過期時(shí)間expiresIn,加密方式默認(rèn)使用HS256
const rule = {id:user.id,name:user.name};
jwt.sign(rule,keys.secretOrKey,{expiresIn:3600},(err,token) => {
if(err) throw err;
res.json({
success:true,
token:"Bearer " + token
});
})
token組成
最終生成的token如下圖所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJuYW1lIjoiaGVsbG8iLCJpYXQiOjE1NDMzOTUyMDgsImV4cCI6MTU0MzM5ODgwOH0.
zFiGRNTST8RSY3e1JqWx0SA0S4BCOgNCVsUC4u9JHbY
可以觀察到生成的token被 .劃分開來,由三部分組成。這三部分分別是Header,
Payload,Signature。其中:
Header是一個(gè)對象,描述 JWT 的元數(shù)據(jù),通常是下面的樣子。
{
"alg": "HS256",
"typ": "JWT"
}
上面代碼中,alg屬性表示簽名的算法(algorithm),默認(rèn)是 HMAC SHA256(寫成 HS256);typ屬性表示這個(gè)令牌(token)的類型(type),JWT 令牌統(tǒng)一寫為JWT。最后將上面的 JSON 對象使用 Base64URL 算法轉(zhuǎn)成字符串,也就是token的第一部分。
Payload也是一個(gè)對象,就是我們上面提到的需要傳遞的數(shù)據(jù)組成的對象
Signature 部分是對前兩部分的簽名,防止數(shù)據(jù)篡改。
首先,需要指定一個(gè)密鑰(secret)。這個(gè)密鑰只有服務(wù)器才知道,不能泄露給用戶。然后,使用 Header 里面指定的簽名算法(默認(rèn)是 HMAC SHA256),按照下面的公式產(chǎn)生簽名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
四.使用passport驗(yàn)證token(passport-jwt驗(yàn)證jwt類型的token)
passport通常是用來實(shí)現(xiàn)登陸驗(yàn)證。這里我們使用passport-jwt來進(jìn)行驗(yàn)證jwt類型的token。
1.使用passport
app.js
const passport = require('passport');
app.use(passport.initialize());
const JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
const opts = {}
//得到token
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
//設(shè)置token時(shí)使用的加密名字
opts.secretOrKey = 'secret';
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
console.log(jwt_payload);
}));
1. JwtStrategy用來定義策列,是在路由執(zhí)行之前執(zhí)行。
2. ExtractJwt里面包含很多函數(shù),這里需要注意的是fromAuthHeaderAsBearerToken**是獲取到請求時(shí)以Bearer +token得到的token。
注意:他只能獲取到以Bearer+一個(gè)空格開頭的token。因此我們這里的token必須以Bearer+ 一個(gè)空格開頭。如下所示:
res.json(
{
success:true,
token:"Bearer "+token
})
3.opts對象里面定義的各個(gè)參數(shù)是我們之前創(chuàng)建token設(shè)置的參數(shù)
opts.jwtFromRequest:用于從request中獲取token
opts.secretOrKey:是之前設(shè)置的加密名字
4.passport.use(new JwtStrategy(opts, function(jwt_payload, done) {}))使用passport.use()定義策略。
回調(diào)函數(shù)中的jwt_payload時(shí)一個(gè)包含id,name等的對象。
{ name: 'hello', id: 'hello', iat: 1543401227, exp: 1543404827 }
根據(jù)payload中的id,我們可以查詢是否有這個(gè)對象。
回調(diào)函數(shù)的第二個(gè)參數(shù)done是一個(gè)函數(shù)(相當(dāng)于next),這個(gè)函數(shù)第一個(gè)參數(shù)是err,第二個(gè)參數(shù)是user。我們可以通過done將user進(jìn)行返回,這樣的話在請求中就把user對象添加進(jìn)去了。如下所示:
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
// console.log(jwt_payload);
User.findById(jwt_payload.id)
.then((user) => {
if(user){
return done(null,user)
}else{
return done(null,false);
}
})
}));
user.js
這樣的話,在其他任何進(jìn)行路由的地方都可以使用passport.authenticate進(jìn)行驗(yàn)證.
router.get('/current',passport.authenticate('jwt',{ session: false }),(req,res) => {
console.log(req.user); //{ name: 'hello', id: 'hello', iat: 1543401227, exp: 1543404827 }
});
注意:非常重要的一點(diǎn)就是我們?nèi)绻褂昧藀assport進(jìn)行了驗(yàn)證。那么就可以從請求中獲取到user,當(dāng)然前提是調(diào)用了done函數(shù),返回了user。