基于token的身份認(rèn)證:JSON Web Token(附:Node.js項(xiàng)目示例)

前言

在大多數(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 。


session.png

token身份驗(yàn)證

使用基于 Token 的身份驗(yàn)證方法图柏,服務(wù)端不需要存儲(chǔ)用戶(hù)的登錄記錄序六。大概的流程如下:

  1. 客戶(hù)端使用用戶(hù)名跟密碼請(qǐng)求登錄
  2. 服務(wù)端收到請(qǐng)求,去驗(yàn)證用戶(hù)名與密碼
  3. 驗(yàn)證成功后爆办,服務(wù)端會(huì)簽發(fā)一個(gè) Token难咕,再把這個(gè) Token 發(fā)送給客戶(hù)端
  4. 客戶(hù)端收到 Token 以后可以把它存儲(chǔ)起來(lái)课梳,比如放在 Cookie 里或者 Local Storage 里
  5. 客戶(hù)端每次向服務(wù)端請(qǐng)求資源的時(shí)候需要帶著服務(wù)端簽發(fā)的 Token
  6. 服務(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ù):

  1. payload: token里面要包含的一些數(shù)據(jù)
  2. secret:簽發(fā)token用的密鑰季稳,驗(yàn)證token時(shí)也要用到這個(gè)密鑰
  3. 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ù)包晰。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末湿镀,一起剝皮案震驚了整個(gè)濱河市炕吸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勉痴,老刑警劉巖赫模,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蒸矛,居然都是意外死亡瀑罗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)雏掠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)斩祭,“玉大人,你說(shuō)我怎么就攤上這事磁玉⊥7蓿” “怎么了驾讲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵蚊伞,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我吮铭,道長(zhǎng)时迫,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任谓晌,我火速辦了婚禮掠拳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纸肉。我一直安慰自己溺欧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布柏肪。 她就那樣靜靜地躺著姐刁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烦味。 梳的紋絲不亂的頭發(fā)上聂使,一...
    開(kāi)封第一講書(shū)人閱讀 52,696評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音谬俄,去河邊找鬼柏靶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛溃论,可吹牛的內(nèi)容都是我干的屎蜓。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼钥勋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼炬转!你這毒婦竟也來(lái)了控汉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤返吻,失蹤者是張志新(化名)和其女友劉穎姑子,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體测僵,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡街佑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捍靠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沐旨。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖榨婆,靈堂內(nèi)的尸體忽然破棺而出磁携,到底是詐尸還是另有隱情,我是刑警寧澤良风,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布谊迄,位于F島的核電站,受9級(jí)特大地震影響烟央,放射性物質(zhì)發(fā)生泄漏统诺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一疑俭、第九天 我趴在偏房一處隱蔽的房頂上張望粮呢。 院中可真熱鬧,春花似錦钞艇、人聲如沸啄寡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)挺物。三九已至,卻和暖如春葡秒,著一層夾襖步出監(jiān)牢的瞬間姻乓,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工眯牧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹋岩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓学少,卻偏偏與公主長(zhǎng)得像剪个,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子版确,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容