前言
在大多數(shù)的應(yīng)用里,身份驗(yàn)證是必須的。最近學(xué)習(xí)了基于token的身份驗(yàn)證,很多大型網(wǎng)站也在用谈息,例如Facebook,twitter,Google,github。大家通常將token翻譯為“令牌”凛剥。顧名思義侠仇,只有拿到這個(gè)令牌,你才能通關(guān)犁珠,才能去請(qǐng)求相應(yīng)的數(shù)據(jù)或資源逻炊。
本文講解了傳統(tǒng)的和基于token的身份認(rèn)證方式,并介紹了JWT相關(guān)知識(shí)犁享,最后附上Node.js中使用jsonwebtoken余素、passport、passport-jwt模塊獲取和驗(yàn)證token的示例炊昆。
傳統(tǒng)身份驗(yàn)證
HTTP 是一種沒(méi)有狀態(tài)的協(xié)議溺森,也就是它并不知道是誰(shuí)是訪(fǎng)問(wèn)應(yīng)用。這里我們把用戶(hù)看成是客戶(hù)端窑眯,客戶(hù)端使用用戶(hù)名和密碼通過(guò)了身份驗(yàn)證屏积,不過(guò)下回這個(gè)客戶(hù)端再發(fā)送請(qǐng)求時(shí)候,還得再驗(yàn)證一下磅甩。
解決的方法就是炊林,當(dāng)用戶(hù)請(qǐng)求登錄的時(shí)候,如果沒(méi)有問(wèn)題卷要,我們?cè)诜?wù)端生成一條記錄渣聚,這個(gè)記錄里可以說(shuō)明一下登錄的用戶(hù)是誰(shuí),然后把這條記錄的 ID 號(hào)發(fā)送給客戶(hù)端僧叉,客戶(hù)端收到以后把這個(gè) ID 號(hào)存儲(chǔ)在 Cookie 里奕枝,下次這個(gè)用戶(hù)再向服務(wù)端發(fā)送請(qǐng)求的時(shí)候,帶著這個(gè) Cookie 瓶堕。服務(wù)端收到請(qǐng)求后隘道,會(huì)驗(yàn)證一個(gè)這個(gè) Cookie 里的信息,看看能不能在服務(wù)端這里找到對(duì)應(yīng)的記錄郎笆,如果可以谭梗,說(shuō)明用戶(hù)已經(jīng)通過(guò)了身份驗(yàn)證,就把用戶(hù)請(qǐng)求的數(shù)據(jù)返回給客戶(hù)端宛蚓。
上面說(shuō)的就是 Session激捏,我們需要在服務(wù)端存儲(chǔ)為登錄的用戶(hù)生成的 Session ,這些 Session 可能會(huì)存儲(chǔ)在內(nèi)存凄吏,磁盤(pán)远舅,或者數(shù)據(jù)庫(kù)里闰蛔。我們可能需要在服務(wù)端定期的去清理過(guò)期的 Session 。
token身份驗(yàn)證
使用基于 Token 的身份驗(yàn)證方法图柏,服務(wù)端不需要存儲(chǔ)用戶(hù)的登錄記錄序六。大概的流程如下:
- 客戶(hù)端使用用戶(hù)名跟密碼請(qǐng)求登錄
- 服務(wù)端收到請(qǐng)求,去驗(yàn)證用戶(hù)名與密碼
- 驗(yàn)證成功后爆办,服務(wù)端會(huì)簽發(fā)一個(gè) Token难咕,再把這個(gè) Token 發(fā)送給客戶(hù)端
- 客戶(hù)端收到 Token 以后可以把它存儲(chǔ)起來(lái)课梳,比如放在 Cookie 里或者 Local Storage 里
- 客戶(hù)端每次向服務(wù)端請(qǐng)求資源的時(shí)候需要帶著服務(wù)端簽發(fā)的 Token
- 服務(wù)端收到請(qǐng)求距辆,然后去驗(yàn)證客戶(hù)端請(qǐng)求里面帶著的 Token,如果驗(yàn)證成功暮刃,就向客戶(hù)端返回請(qǐng)求的數(shù)據(jù)
JWT
從上文可知跨算,token驗(yàn)證即:通過(guò)客戶(hù)端保存數(shù)據(jù),而服務(wù)器根本不保存會(huì)話(huà)數(shù)據(jù)椭懊,每個(gè)請(qǐng)求都被發(fā)送回服務(wù)器诸蚕。JWT是實(shí)施 Token 驗(yàn)證的代表,讀作:jot 氧猬,表示:JSON Web Tokens 背犯。也可以稱(chēng)作JSON Web令牌
1. JWT的原則
JWT的原則是在服務(wù)器身份驗(yàn)證之后,將生成一個(gè)JSON對(duì)象并將其發(fā)送回用戶(hù)盅抚,如下所示漠魏。
{
"UserName": "Ciger",
"Role": "Admin",
"Expire": "2019-02-18 20:15:56"
}
之后,當(dāng)用戶(hù)與服務(wù)器通信時(shí)妄均,客戶(hù)在請(qǐng)求中發(fā)回JSON對(duì)象柱锹。服務(wù)器僅依賴(lài)于這個(gè)JSON對(duì)象來(lái)標(biāo)識(shí)用戶(hù)。為了防止用戶(hù)篡改數(shù)據(jù)丰包,服務(wù)器將在生成對(duì)象時(shí)添加簽名
2. JWT的數(shù)據(jù)結(jié)構(gòu)
JWT標(biāo)準(zhǔn)的Token有如下三個(gè)部分
- header (頭部)
- payload (數(shù)據(jù))
-
signature (簽名)
jwt.jpg
這三部分用點(diǎn)分隔開(kāi)禁熏,并且使用Base64url編碼。
Token看起來(lái)如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjNjc3OTExMjdjYWEzMjFmNDAzOTc1MyIsIm5hbWUiOiJjY2MiLCJpYXQiOjE1NTAzMjAyNzcsImV4cCI6MTU1MDMyMzg3N30.a-CWZMdmtXnSJrYGUS6fbmqd4VzY6lPftDQ2A3UsK0w
Header
每個(gè) JWT token 里面都有一個(gè) header邑彪,也就是頭部數(shù)據(jù)瞧毙。里面包含了使用的算法,這個(gè) JWT 是不是帶簽名的或者加密的寄症。主要就是說(shuō)明一下怎么處理這個(gè) JWT token 升筏。
頭部里包含的東西可能會(huì)根據(jù) JWT 的類(lèi)型有所變化,比如一個(gè)加密的 JWT 里面要包含使用的加密的算法瘸爽。唯一在頭部里面要包含的是 alg 這個(gè)屬性您访,如果是加密的 JWT,這個(gè)屬性的值就是使用的簽名或者解密用的算法剪决。如果是未加密的 JWT灵汪,這個(gè)屬性的值要設(shè)置成 none檀训。
示例:
{
“alg”:"HS256"
}
意思是這個(gè)JWT用的算法為HS256。上面的json內(nèi)容經(jīng)過(guò)Base64url編碼后如下:
eyJhbGciOiJIUzI1NiJ9
Payload
Payload 里面是 Token 的具體內(nèi)容享言,這些內(nèi)容里面有一些是標(biāo)準(zhǔn)字段峻凫,你也可以添加其它需要的內(nèi)容。下面是標(biāo)準(zhǔn)字段:
- iss:Issuer览露,發(fā)行者
- sub:Subject荧琼,主題
- aud:Audience,觀(guān)眾
- exp:Expiration time差牛,過(guò)期時(shí)間
- nbf:Not before
- iat:Issued at命锄,發(fā)行時(shí)間
- jti:JWT ID
示例:
{
"exp": "1438955445",
"name": "Ciger",
"admin": true
}
使用base64url編碼后如下:
eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ
Signature
JWT 的最后一部分是 Signature ,這部分內(nèi)容有三個(gè)部分偏化,先是用 Base64Url 編碼的 header.payload 脐恩,之后選擇一個(gè)加密算法加密,將Secret作為參數(shù)放進(jìn)去(這個(gè)Secret相當(dāng)于一個(gè)密碼侦讨,存儲(chǔ)在服務(wù)端)
- header
- payload
- secret
const encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
HMACSHA256(encodedString, 'secret');
加密后如下:
SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
最后將header驶冒、payload、signature三部分組合起來(lái)便是一個(gè)要發(fā)送給客戶(hù)端的token
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
客戶(hù)端收到這個(gè)token之后將它存儲(chǔ)起來(lái)韵卤,下次向服務(wù)端發(fā)送請(qǐng)求的時(shí)候就帶著這個(gè) Token 骗污。服務(wù)端收到這個(gè) Token ,然后進(jìn)行驗(yàn)證沈条,通過(guò)以后就會(huì)返回給客戶(hù)端想要的資源需忿。
Node.js中使用Express結(jié)合Passport實(shí)現(xiàn)登陸認(rèn)證
1. 首先安裝
cnpm install jsonwebtoken
cnpm install passport-jwt passport
jsonwebtoken用于獲取token,passport和passport-jwt用于驗(yàn)證token。passport是express框架中一個(gè)針對(duì)密碼的中間件拍鲤,而passport-jwt是一個(gè)針對(duì)jsonwebtoken的插件贴谎。
2. 得到token
在項(xiàng)目中添加一個(gè).js文件,如users.js
const jwt = require('jsonwebtoken')
// Token 數(shù)據(jù)
const payload = {
name: 'Ciger',
admin: true
}
// 密鑰
const secretOrKey = 'secret'
// 簽發(fā) Token
const token = jwt.sign(payload, secretOrKey , { expiresIn: '3600' })
// 輸出簽發(fā)的 Token
console.log(token)
jwt.sign()方法有三個(gè)參數(shù):
- payload: token里面要包含的一些數(shù)據(jù)
- secret:簽發(fā)token用的密鑰季稳,驗(yàn)證token時(shí)也要用到這個(gè)密鑰
- options:一些其他選項(xiàng)擅这,如過(guò)期時(shí)間expiresIn
在命令行下執(zhí)行node users.js,就會(huì)輸出應(yīng)用簽發(fā)的token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlLCJpYXQiOjE1MjkwMzM5MDYsImV4cCI6MTUyOTEyMDMwNn0.DctA2QlUCrM6wLWkIO78wBVN0NLpjoIq4T5B_2WJ-PU
2. 驗(yàn)證JWT
這里我們采用passport+passport-jwt來(lái)驗(yàn)證景鼠,你也可以采用其他方法驗(yàn)證仲翎,如jwt.verify
采用passport+passport-jwt方法驗(yàn)證,首先需要在入口文件中引入passport并初始化
入口文件-server.js
const passport = require('passport');
app.use(passport.initialize())
3. 對(duì)passport進(jìn)行一些配置
在config文件夾下新建一個(gè)passport.js文件铛漓,然后在入口文件(server.js)中引入
require('./config/passport')(passport)
在passport.js中溯香,引入以下模塊:
- passport-jwt
- mongoose
- keys.js
- models/Users,js
具體代碼如下:
const JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
const mongoose = require('mongoose')
const User = mongoose.model('users')
const keys = require('./key')
const opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = keys.secretOrKey;
module.exports = (passport) => {
passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
User.findById(jwt_payload.id)
.then(user=>{
if(user){
//此處會(huì)將user信息寫(xiě)入req.user
return done(null,user)
}
return done(null,false)
}).catch(err=>{console.log(err)})
}))
}
(passport.js配置信息復(fù)制粘貼修改下即可)
4. 驗(yàn)證token
router.get("/current",passport.authenticate("jwt",{session:false}),(req,res) => {
res.json({
req.user
})
總結(jié)
token就像一個(gè)令牌,我們只有拿到這個(gè)令牌才能向服務(wù)器去請(qǐng)求用戶(hù)的信息浓恶。Node.js中獲取token可以用jsonwebtoken模塊玫坛,驗(yàn)證token用passport和passport-jwt模塊。對(duì)于密碼可用bcrypt模塊加密存儲(chǔ)進(jìn)數(shù)據(jù)庫(kù)包晰。