Node.js?express中的身份認(rèn)證的實(shí)現(xiàn)
Web開(kāi)發(fā)模式
目前主流的Web開(kāi)發(fā)模式有兩種:
基于服務(wù)端渲染的傳統(tǒng)Web開(kāi)發(fā)模式
服務(wù)端渲染的概念:服務(wù)器發(fā)送給客戶端的HTML頁(yè)面,是在服務(wù)器通過(guò)字符串的拼接,動(dòng)態(tài)生成的。因此,客戶端不需要使用Ajax這樣的技術(shù)額外請(qǐng)求頁(yè)面的數(shù)據(jù)。代碼如下:
app.get('/index.html',(req,res)=>{ // 1.要渲染的數(shù)據(jù) const user = {name:'zs',age:20} // 2.服務(wù)器通過(guò)字符串的拼接,動(dòng)態(tài)生成 HTML 內(nèi)容 const html = `<h1>姓名:${user.name},年齡:${user.age}</h1>` // 3.把生成好的頁(yè)面內(nèi)容響應(yīng)給客戶端。因此客戶端拿到的是帶有真實(shí)數(shù)據(jù)的 HTML 頁(yè)面 res.send(html) })
服務(wù)端渲染的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
1)前端耗時(shí)少:因?yàn)榉?wù)器端負(fù)責(zé)動(dòng)態(tài)生成 HTML內(nèi)容,瀏覽器只需要直接渲染頁(yè)面即可。尤其是移動(dòng)端,更省電。
2)有利于SEO:因?yàn)榉?wù)器端響應(yīng)的是完整的 HTML頁(yè)面內(nèi)容,所以爬蟲(chóng)更容易爬取獲得信息,更有利于SEO。
缺點(diǎn):
1)占用服務(wù)器端資源:即服務(wù)器端完成HTML頁(yè)面內(nèi)容的拼接,如果請(qǐng)求較多,會(huì)對(duì)服務(wù)器造成一定的訪問(wèn)壓力。
2)不利于前后端分離,開(kāi)發(fā)效率低:使用服務(wù)器端渲染,則無(wú)法進(jìn)行分工合作,尤其對(duì)于前端復(fù)雜度高的項(xiàng)目,不利于項(xiàng)目高效開(kāi)發(fā)。
基于前后端分離的新型Web開(kāi)發(fā)模式
前后端分離的概念:前后端分離的開(kāi)發(fā)模式,依賴于Ajax技術(shù)的廣泛應(yīng)用。簡(jiǎn)而言之,前后端分離的Web開(kāi)發(fā)模式,就是后端只負(fù)責(zé)提供API接口,前端使用Ajax調(diào)用接口的開(kāi)發(fā)模式。
前后端分離的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
1)開(kāi)發(fā)體驗(yàn)好。前端專注于UI頁(yè)面的開(kāi)發(fā),后端專注于api的開(kāi)發(fā),且前端有更多的選擇性
2)用戶體驗(yàn)好。Ajax技術(shù)的廣泛應(yīng)用,極大的提高了用戶的體驗(yàn),可以輕松實(shí)現(xiàn)頁(yè)面的局部刷新
3)減輕了服務(wù)器端的渲染壓力。因?yàn)轫?yè)面最終是在每個(gè)用戶的瀏覽器中生成的。
缺點(diǎn):
1)不利于SE0。因?yàn)橥暾腍TML頁(yè)面需要在客戶端動(dòng)態(tài)拼接完成,所以爬蟲(chóng)對(duì)無(wú)法爬取頁(yè)面的有效信息。(解決方案:利用Vue、React等前端框架的SSR (server side render)技術(shù)能夠很好的解決SEO問(wèn)題!)
如何選擇Web開(kāi)發(fā)模式
比如企業(yè)級(jí)網(wǎng)站,主要功能是展示而沒(méi)有復(fù)雜的交互,并且需要良好的SEO,則這時(shí)我們就需要使用服務(wù)器端渲染;而類似后臺(tái)管理項(xiàng)目,交互性比較強(qiáng),不需要考慮SEO,那么就可以使用前后端分離的開(kāi)發(fā)模式。
因此,具體使用何種開(kāi)發(fā)模式并不是絕對(duì)的,為了同時(shí)兼顧了首頁(yè)的渲染速度和前后端分離的開(kāi)發(fā)效率,一些網(wǎng)站采用了首屏服務(wù)器端渲染+其他頁(yè)面前后端分離的開(kāi)發(fā)模式。
身份認(rèn)證
身份認(rèn)證(Authentication)又稱“身份驗(yàn)證”、“鑒權(quán)”,是指通過(guò)一定的手段,完成對(duì)用戶身份的確認(rèn)。在Web開(kāi)發(fā)中,也涉及到用戶身份的認(rèn)證,例如:各大網(wǎng)站的手機(jī)驗(yàn)證碼登錄、郵箱密碼登錄、二維碼登錄等。
身份認(rèn)證的目的:為了確認(rèn)當(dāng)前所聲稱為某種身份的用戶,確實(shí)是所聲稱的用戶。
不同開(kāi)發(fā)模式下的身份認(rèn)證
對(duì)于服務(wù)端渲染和前后端分離這兩種開(kāi)發(fā)模式來(lái)說(shuō),分別有著不同的身份認(rèn)證方案:
1)服務(wù)端渲染推薦使用 Session 認(rèn)證機(jī)制
2)前后端分離推薦使用 JWT 認(rèn)證機(jī)制
Session認(rèn)證機(jī)制
HTTP協(xié)議的無(wú)狀態(tài)性:指的是客戶端的每次HTTP請(qǐng)求都是獨(dú)立的,連續(xù)多個(gè)請(qǐng)求之間沒(méi)有直接的關(guān)系,服務(wù)器不會(huì)主動(dòng)保留每次HTTP請(qǐng)求的狀態(tài)。而要想突破HTTP的無(wú)狀態(tài)的限制,就需要借助Cookie。
Cookie是存儲(chǔ)在用戶瀏覽器中的一段不超過(guò)4KB的字符串。它由一個(gè)名稱(Name)、一個(gè)值(Value)和其它幾個(gè)用于控制Cookie有效期、安全性、使用范圍的可選屬性組成。不同域名下的Cookie 各自獨(dú)立,每當(dāng)客戶端發(fā)起請(qǐng)求時(shí),會(huì)自動(dòng)把當(dāng)前域名下所有未過(guò)期的Cookie一同發(fā)送到服務(wù)器。
Cookie的特性:自動(dòng)發(fā)送、域名獨(dú)立、過(guò)期時(shí)限、4KB限制。
Cookie在身份認(rèn)證中的作用
客戶端第一次請(qǐng)求服務(wù)器的時(shí)候,服務(wù)器通過(guò)響應(yīng)頭的形式,向客戶端發(fā)送一個(gè)身份認(rèn)證的Cookie,客戶端會(huì)自動(dòng)將Cookie保存在瀏覽器中。隨后,當(dāng)客戶端瀏覽器每次請(qǐng)求服務(wù)器的時(shí)候,瀏覽器會(huì)自動(dòng)將身份認(rèn)證相關(guān)的Cookie,通過(guò)請(qǐng)求頭的形式發(fā)送給服務(wù)器,服務(wù)器即可驗(yàn)明客戶端的身份。
Cookie的安全性問(wèn)題
由于Cookie是存儲(chǔ)在瀏覽器中的,而且瀏覽器提供了讀寫(xiě)Cookie的API,因此Cookie很容易被偽造,不具有安全性。因此不建議服務(wù)器將重要的隱私數(shù)據(jù),通過(guò)Cookie的形式發(fā)送給瀏覽器,所以千萬(wàn)不要使用Cookie存儲(chǔ)重要且隱私的數(shù)據(jù),比如用戶的身份信息、密碼等。
而Session的認(rèn)證機(jī)制就是為了提高cookie安全性的一種認(rèn)證機(jī)制。
Session的工作原理
在Express中使用Session認(rèn)證
在Express項(xiàng)目中,只需安裝 express-session 中間件,即可在項(xiàng)目中使用Session認(rèn)證:
npm install express-session
express-session中間件安裝完成后,需要通過(guò)app.use()來(lái)注冊(cè)session中間件,代碼如下:
// 配置 Session 中間件 const session = require('express-session') app.use(session({ secret:'Session_test', // secret 屬性值可以是任意字符串 resave:false, // 固定寫(xiě)法 saveUninitialized:true // 固定寫(xiě)法 }))
當(dāng)express-session中間件配置成功后,即可通過(guò) req.session 來(lái)訪問(wèn)和使用 session 對(duì)象,從而存儲(chǔ)用戶的關(guān)鍵信息:
// 登錄的API接口 app.post('/api/login',(req,res)=>{ // 判斷用戶提交的信息是否正確 if(req.body.username !=='admin' || req.body.password !=='123456'){ return res.send({status:1,msg:'登錄失敗'}) } // 登錄成功后,將成功的用戶信息保存到session中 // 注意:只有成功配置了 express-session 這個(gè)中間件后,才能通過(guò) req 點(diǎn)出來(lái) session 這個(gè)屬性 req.session.user = req.body // 用戶的信息 req.session.islogin = true // 用戶的登錄狀態(tài) res.send({status:0,msg:'登錄成功'}) })
當(dāng)然也可以直接從 req.session 對(duì)象上獲取之前存儲(chǔ)的數(shù)據(jù)。代碼如下:
app.post('/api/logout',(req,res)=>{ // 清空session信息 req.session.destroy() res.send({ status:0, msg:'退出登錄成功' }) })
Session認(rèn)證的局限性:Session認(rèn)證機(jī)制需要配合Cookie 才能實(shí)現(xiàn)。由于Cookie默認(rèn)不支持跨域訪問(wèn),所以,當(dāng)涉及到前端跨域請(qǐng)求后端接口的時(shí)候,需要做很多額外的配置,才能實(shí)現(xiàn)跨域Session認(rèn)證。
當(dāng)前端請(qǐng)求后端接口不存在跨域問(wèn)題的時(shí)候,推薦使用Session身份認(rèn)證機(jī)制。
JWT認(rèn)證機(jī)制
JWT(英文全稱:JSON Web Token)是目前最流行的跨域認(rèn)證解決方案。當(dāng)前端需要跨域請(qǐng)求后端接口的時(shí)候,不推薦使用Session身份認(rèn)證機(jī)制,推薦使用JWT認(rèn)證機(jī)制。
JWT工作原理:用戶的信息通過(guò)Token字符串的形式,保存在客戶端瀏覽器中。服務(wù)器通過(guò)還原Token字符串的形式來(lái)認(rèn)證用戶的身份。
JWT的組成部分:
Header(頭部)、Payload(有效荷載)、Signature(簽名)。
Payload部分才是真正的用戶信息,它是用戶信息經(jīng)過(guò)加密之后生成的字符串。
Header和Signature是安全性相關(guān)的部分,只是為了保證Token的安全性。
三者之間使用英文的“.”分隔,格式如下:
Header.Payload.Signature
JWT的使用方式: 客戶端收到服務(wù)器返回的WT之后,通常會(huì)將它儲(chǔ)存在localStorage或sessionStorage中。此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè)WT的字符串,從而進(jìn)行身份認(rèn)證。推薦的做法是把JWT放在HTTP請(qǐng)求頭的Authorization字段中,格式如下:
Authorization: Bearer <token>
在Express中使用JWT
運(yùn)行如下命令安裝兩個(gè)JWT相關(guān)的包:
# jsonwebtoken用于生成JWT字符串 # express-jwt用于將JWT字符串解析還原成JSON對(duì)象 npm install jsonwebtoken express-jwt@5.3.3
定義secret密鑰:為了保證JWT字符串的安全性,防止JWT字符串在網(wǎng)絡(luò)傳輸過(guò)程中被別人破解,我們需要專門(mén)定義一個(gè)用于加密和解密的secret密鑰。
當(dāng)生成JWT字符串的時(shí)候,需要使用 secret 密鑰對(duì)用戶的信息進(jìn)行加密,最終得到加密好的JWT字符串;當(dāng)把JWT字符串解析還原成JSON對(duì)象的時(shí)候,需要使用secret密鑰進(jìn)行解密。
// 定義 secret 密鑰,建議將密鑰命名為 secretKey const secretKey = 'Hello Node.js'
生成JWT字符串:調(diào)用jsonwebtoken包提供的sign()方法,將用戶的信息加密成JWT字符串,響應(yīng)給客戶端。
// 登錄接口 app.post('/api/login',(req,res)=>{ // 將 req.body 請(qǐng)求體中的數(shù)據(jù),轉(zhuǎn)存為 userinfo 常量 const userinfo = req.body // 登錄失敗 if(userinfo.username !=='admin' || userinfo.password !=='123456'){ return res.send({ status:400, message:'登錄失敗!' }) } // 三個(gè)參數(shù)分別是:用戶信息對(duì)象,加密密鑰,配置對(duì)象 const tokenStr = jwt.sign({username: userinfo.username},secretKey,{expiresIn:'20s'}) // 用戶登錄成功后,生成 JWT 字符串,通過(guò) token 屬性響應(yīng)給客戶端 res.send({ status:200, message:'登錄成功', token: tokenStr, // 要發(fā)送給客戶端的 token 字符串 }) })
JWT字符串還原為JSON對(duì)象:客戶端每次在訪問(wèn)那些有權(quán)限接口的時(shí)候,都需要主動(dòng)通過(guò)請(qǐng)求頭中的Authorization字段將Token字符串發(fā)送到服務(wù)器進(jìn)行身份認(rèn)證。此時(shí),服務(wù)器可以通過(guò)express-jwt這個(gè)中間件,自動(dòng)將客戶端發(fā)送過(guò)來(lái)的Token解析還原成JSON對(duì)象。
// 注冊(cè)將 JWT 字符串解析還原成 JSON 對(duì)象的中間件 // expiresJWT({secret:secretKey}) 用來(lái)解析 Token 的中間件 // .unless({path:[/^\/api\//]}) 用來(lái)指定哪些接口不需要訪問(wèn)權(quán)限 app.use(expressJWT({secret:secretKey}).unless({path:[/^\/api\//]}))
使用req.user獲取用戶信息:當(dāng)express-jwt這個(gè)中間件配置成功之后,即可在那些有權(quán)限的接口中,使用req.user對(duì)象,來(lái)訪問(wèn)從WT字符串中解析出來(lái)的用戶信息了,示例代碼如下:
app.get('/admin/getinfo',function(req,res){ // 使用 req.user 獲取用戶信息,并使用data屬性將用戶信息發(fā)送給客戶端 console.log(req.user); res.send({ status:200, message:'獲取用戶信息成功', data:req.user, // 要發(fā)送給客戶端的用戶信息 }) })
捕獲解析JWT失敗后產(chǎn)生的錯(cuò)誤:當(dāng)使用express-jwt解析Token字符串時(shí),如果客戶端發(fā)送過(guò)來(lái)的Token字符串過(guò)期或不合法,會(huì)產(chǎn)生一個(gè)解析失敗的錯(cuò)誤,影響項(xiàng)目的正常運(yùn)行。我們可以通過(guò) Express的錯(cuò)誤中間件,捕獲這個(gè)錯(cuò)誤并進(jìn)行相關(guān)的處理,示例代碼如下:
// 使用全局錯(cuò)誤處理中間件,捕獲解析 JWT 失敗后產(chǎn)生的錯(cuò)誤 app.use((err, req, res, next) => { // 這次錯(cuò)誤是由 token 解析失敗導(dǎo)致的 if (err.name === 'UnauthorizedError') { return res.send({ status: 401, message: '無(wú)效的token', }) } res.send({ status: 500, message: '未知的錯(cuò)誤', }) })
具體詳細(xì)代碼如下:
// 導(dǎo)入 express 模塊 const express = require('express') // 創(chuàng)建服務(wù)器 const app = express() // 導(dǎo)入JWT相關(guān)的兩個(gè)包 const jwt = require('jsonwebtoken') const expressJWT = require('express-jwt') // 允許跨域資源共享 const cors = require('cors') app.use(cors()) // 解析 post 表單數(shù)據(jù)的中間件 const bodyParser = require('body-parser') app.use(bodyParser.urlencoded({ extended: false })) // 定義 secret 密鑰,建議將密鑰命名為 secretKey const secretKey = 'Hello Node.js' // 注冊(cè)將 JWT 字符串解析還原成 JSON 對(duì)象的中間件 // expiresJWT({secret:secretKey}) 用來(lái)解析 Token 的中間件 // .unless({path:[/^\/api\//]}) 用來(lái)指定哪些接口不需要訪問(wèn)權(quán)限 app.use(expressJWT({secret:secretKey}).unless({path:[/^\/api\//]})) // 登錄接口 app.post('/api/login',(req,res)=>{ // 將 req.body 請(qǐng)求體中的數(shù)據(jù),轉(zhuǎn)存為 userinfo 常量 const userinfo = req.body // 登錄失敗 if(userinfo.username !=='admin' || userinfo.password !=='123456'){ return res.send({ status:400, message:'登錄失??!' }) } // 三個(gè)參數(shù)分別是:用戶信息對(duì)象,加密密鑰,配置對(duì)象 const tokenStr = jwt.sign({username: userinfo.username},secretKey,{expiresIn:'50s'}) // 用戶登錄成功后,生成 JWT 字符串,通過(guò) token 屬性響應(yīng)給客戶端 res.send({ status:200, message:'登錄成功', token: tokenStr, // 要發(fā)送給客戶端的 token 字符串 }) }) // 這是一個(gè)有權(quán)限的API接口 app.get('/admin/getinfo',function(req,res){ // 使用 req.user 獲取用戶信息,并使用data屬性將用戶信息發(fā)送給客戶端 console.log(req.user); res.send({ status:200, message:'獲取用戶信息成功', data:req.user, // 要發(fā)送給客戶端的用戶信息 }) }) // 使用全局錯(cuò)誤處理中間件,捕獲解析 JWT 失敗后產(chǎn)生的錯(cuò)誤 app.use((err, req, res, next) => { // 這次錯(cuò)誤是由 token 解析失敗導(dǎo)致的 if (err.name === 'UnauthorizedError') { return res.send({ status: 401, message: '無(wú)效的token', }) } res.send({ status: 500, message: '未知的錯(cuò)誤', }) }) // 調(diào)用app.listen 方法,指定端口號(hào)并啟動(dòng)web服務(wù)器 app.listen(80,function(){ console.log('Express server running at http://127.0.0.1:80'); })
到此這篇關(guān)于Node.js express中的身份認(rèn)證的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Node.js express 身份認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- node.js中koa和express的差異對(duì)比
- Node.js中Express框架使用axios同步請(qǐng)求(async+await)實(shí)現(xiàn)方法
- node.js使用express-jwt報(bào)錯(cuò):expressJWT?is?not?a?function解決
- Node.js使用express寫(xiě)接口的具體代碼
- 使用Express+Node.js對(duì)mysql進(jìn)行增改查操作?
- node.js三個(gè)步驟實(shí)現(xiàn)一個(gè)服務(wù)器及Express包使用
- Node.js中Express框架的使用教程詳解
- node.js+express留言板功能實(shí)現(xiàn)示例
- node.js使用express-fileupload中間件實(shí)現(xiàn)文件上傳
- Node.js+express+socket實(shí)現(xiàn)在線實(shí)時(shí)多人聊天室
- Express框架實(shí)現(xiàn)簡(jiǎn)單攔截器功能示例
相關(guān)文章
Node.js之如何創(chuàng)建TCP服務(wù)器端
這篇文章主要介紹了Node.js之如何創(chuàng)建TCP服務(wù)器端問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-09-09npm安裝的全局包/工具不能使用,不是內(nèi)部/外部命令的解決方法
這篇文章主要給大家介紹了關(guān)于npm安裝的全局包/工具不能使用,不是內(nèi)部/外部命令的解決方法,文中通過(guò)圖文將解決的方法介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03手把手教你把nodejs部署到linux上跑出hello world
本篇文章主要介紹了手把手教你把nodejs部署到linux上跑出hello world,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-06-06nodejs連接ftp上傳下載實(shí)現(xiàn)方法詳解【附:踩坑記錄】
這篇文章主要介紹了nodejs連接ftp上傳下載實(shí)現(xiàn)方法,結(jié)合實(shí)例形式詳細(xì)分析了node.js使用ftp模塊實(shí)現(xiàn)針對(duì)ftp上傳、下載相關(guān)操作的方法,并附帶記錄了傳輸速度慢的解決方法,需要的朋友可以參考下2023-04-04Nodejs Express4.x開(kāi)發(fā)框架隨手筆記
Express: ?web application framework for?Node.js?Express 是一個(gè)簡(jiǎn)潔、靈活的 node.js Web 應(yīng)用開(kāi)發(fā)框架, 它提供一系列強(qiáng)大的特性,幫助你創(chuàng)建各種 Web 和移動(dòng)設(shè)備應(yīng)用,本篇文章給大家介紹nodejs express4.x開(kāi)發(fā)框架隨手筆記,感興趣的朋友一起學(xué)習(xí)吧2015-11-11node.js使用express-fileupload中間件實(shí)現(xiàn)文件上傳
本文使用express作為服務(wù)端,使用express-fileupload庫(kù)提供的中間件函數(shù)來(lái)接受從客戶端傳來(lái)的圖片,并將圖片作為文件存儲(chǔ)在服務(wù)端,感興趣的可以了解一下2021-07-07node.js 核心http模塊,起一個(gè)服務(wù)器,返回一個(gè)頁(yè)面的實(shí)例
下面小編就為大家?guī)?lái)一篇node.js 核心http模塊,起一個(gè)服務(wù)器,返回一個(gè)頁(yè)面的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09