1. 前后端的身份認(rèn)證
1.1 Web 開發(fā)模式
目前主流的 Web 開發(fā)模式有兩種,分別是:
(1)基于服務(wù)端渲染
的傳統(tǒng) Web 開發(fā)模式
(2)基于前后端分離
的新型 Web 開發(fā)模式
-
服務(wù)端渲染
的傳統(tǒng) Web 開發(fā)模式
服務(wù)端渲染的概念:服務(wù)器發(fā)送給客戶端的HTML頁面,實在服務(wù)器通過字符串的拼接
,動態(tài)生成
的。因此,客戶端不需要 Ajax 這樣的技術(shù)額外請求頁面的數(shù)據(jù)。代碼示例如下: -
服務(wù)器端渲染的優(yōu)缺點
優(yōu)點:
(1)前端耗時少
。因為服務(wù)器端負(fù)責(zé)動態(tài)生成 HTML 內(nèi)容,瀏覽器只需要直接渲染頁面即可。尤其是移動端,更省電。
(2)有利于SEO
。因為服務(wù)器端響應(yīng)的是完整的 HTML 頁面內(nèi)容,所以爬蟲更容易爬取獲得信息,更有利于SEO。
缺點:
(1)占用服務(wù)器端資源
。即服務(wù)器端完成 HTML 頁面內(nèi)容的拼接,如果請求較多,會對服務(wù)器造成一定的訪問壓力。
(2)不利于前后端分離,開發(fā)效率低
。使用服務(wù)器端渲染,則無法進(jìn)行分工合作,尤其對于前端復(fù)雜度高的項目,不利于項目高效開發(fā)。 -
前后端分離的 Web 開發(fā)模式
前后端分離的概念:前后端分離的開發(fā)模式,依賴于 Ajax 技術(shù)的廣泛應(yīng)用
。簡而言之,前后端分離的 Web 開發(fā)模式,就是后端只負(fù)責(zé)提供 API 接口,前端使用 Ajax 調(diào)用接口的開發(fā)模式
。 -
前后端分離的優(yōu)缺點
優(yōu)點:
(1)開發(fā)體驗好
。前端專注于 UI 頁面的開發(fā),后端專注于api 的開發(fā),且前端有更多的選擇性。
(2)用戶體驗好
。Ajax 技術(shù)的廣泛應(yīng)用,極大的提高了用戶的體驗,可以輕松實現(xiàn)頁面的局部刷新。
(3)減輕了服務(wù)器端的渲染壓力
。因為頁面最終是在每個用戶的瀏覽器中生成的。
缺點:
(2)不利于 SEO
。因為完整的 HTML 頁面需要在客戶端動態(tài)拼接完成,所以爬蟲對無法爬取頁面的有效信息。(解決方案:利用 Vue、React 等前端框架的 SSR
(server side render)技術(shù)能夠很好的解決 SEO 問題?。?/p>
1.2 身份認(rèn)證
- 什么是身份認(rèn)證
身份認(rèn)證
(Authentication)又稱“身份驗證”、“鑒權(quán)”,是指通過一定的手段,完成對用戶身份的確認(rèn)
。
- 日常生活中的身份認(rèn)證隨處可見,例如:高鐵的驗票乘車,手機的密碼或指紋解鎖,支付寶或微信的支付密碼等。
- 在 Web 開發(fā)中,也涉及到用戶身份的認(rèn)證,例如:各大網(wǎng)站的
手機驗證碼登錄、郵箱密碼登錄、二維碼登錄
等。
-
為什么需要身份認(rèn)證
身份認(rèn)證的目的
,是為了確認(rèn)當(dāng)前所聲稱為某種身份的用戶
,確實是所聲稱的用戶。例如,你去找快遞員取快遞,你要怎么證明這份快遞是你的。
在互聯(lián)網(wǎng)項目開發(fā)中,如何對用戶的身份進(jìn)行認(rèn)證,是一個值得深入探討的問題。例如,如何才能保證網(wǎng)站不會錯誤的將“馬云的存款數(shù)額”顯示到“馬化騰的賬戶”上。 -
不同開發(fā)模式下的身份認(rèn)證
對于服務(wù)端渲染
和前后端分離
這兩種開發(fā)模式來說,分別有著不同的身份認(rèn)證方案:
(1)服務(wù)端渲染
推薦使用 Session 認(rèn)證機制
(2)前后端分離
推薦使用 JWT 認(rèn)證機制
1.3 Session 認(rèn)證機制
-
HTTP 協(xié)議的無狀態(tài)性
了解 HTTP 協(xié)議的無狀態(tài)性是進(jìn)一步學(xué)習(xí) Session 認(rèn)證機制的必要前提。
HTTP 協(xié)議的無狀態(tài)性,指的是客戶端的每次 HTTP 請求都是獨立的
,連續(xù)多個請求之間沒有直接的關(guān)系,服務(wù)器不會主動保留每次 HTTP 請求的狀態(tài)
。 -
如何突破 HTTP 無狀態(tài)的限制
對于超市來說,為了方便收銀員在進(jìn)行結(jié)算時給 VIP 用戶打折,超市可以為每個 VIP 用戶發(fā)放會員卡。
注意:現(xiàn)實生活中的會員卡身份認(rèn)證方式
,在 Web 開發(fā)中的專業(yè)術(shù)語叫做Cookie
。 -
什么是
Cookie
Cookie 是存儲在用戶瀏覽器中的一段不超過 4 KB 的字符串
。它由一個名稱
(Name)、一個值
(Value)和其它幾個用于控制 Cookie有效期
、安全性
、使用范圍
的可選屬性
組成。
不同域名下的 Cookie 各自獨立,每當(dāng)客戶端發(fā)起請求時,會自動
把當(dāng)前域名下
所有未過期的 Cookie
一同發(fā)送到服務(wù)器。Cookie的幾大特性
:
(1)自動發(fā)送
(2)域名獨立
(3)過期時限
(4)4KB 限制 -
Cookie 在身份認(rèn)證中的作用
客戶端讀一次請求服務(wù)器的時候,服務(wù)器通過響應(yīng)頭的形式
,向客戶端發(fā)送一個身份認(rèn)證的 Cookie ,客戶端會自動將 Cookie 保存在瀏覽器中。
隨后,當(dāng)客戶端瀏覽器每次請求服務(wù)的時候,瀏覽器會自動
將身份認(rèn)證相關(guān)的 Cookie ,通過請求頭的形式
發(fā)送給服務(wù)器,服務(wù)器即可驗明客戶端的身份 -
Cookie
不具有
安全性
由于 Cookie 是存儲在瀏覽器中的,而且瀏覽器也提供了讀寫 Cookie 的 API
,因此Cookie 很容易被偽造
,不具有安全性。因此不建議服務(wù)器將重要的隱私數(shù)據(jù),通過 Cookie 的形式發(fā)送給瀏覽器。
注意:千萬不要使用 Cookie 存儲重要且隱私的數(shù)據(jù)
!比如用戶的身份信息、密碼等。 -
提高
身份認(rèn)證的安全性
為了防止客戶偽造會員卡,收銀員在拿到客戶出示的會員卡之后,可以在收銀機上進(jìn)行刷卡認(rèn)證
。只有收銀機確認(rèn)存在的會員卡,才能被正常使用。
這種“會員卡 + 刷卡認(rèn)證”的設(shè)計理念,就是Session 認(rèn)證機制
的精髓。 -
Session 的工作原理
1.4 在 Express 中使用 Session 認(rèn)證
-
安裝
express-session 中間件
在 Express 項目中,只需要安裝 express-session 中間件,即可在項目中使用 Session 認(rèn)證:
npm install express-session
-
配置
express-session 中間件
express-session 中間件安裝成功后,需要通過 app.use() 來注冊 session 中間件,示例代碼如下:
//1.導(dǎo)入 session 中間件
const session = require('express-session')
//2. 配置 session 中間件
app.use(
session({
secret: 'itheima',//secret 屬性的值可以為任意字符串
resave: false, //固定寫法
saveUninitialized: true,//固定寫法
})
)
- 向 session 中
存數(shù)據(jù)
當(dāng)express-session中間件配置成功后,即可通過req.session
來訪問和使用 session 對象,從而存儲用戶的關(guān)鍵信息:
注意: 在配置express-session中間件之前,我們req身上是沒有.session屬性的,只有配置成功之后,才能使用req.session
// 登錄的 API 接口
app.post('/api/login', (req, res) => {
// 判斷用戶提交的登錄信息是否正確
if (req.body.username !== 'admin' || req.body.password !== '000000') {
return res.send({ status: 1, msg: '登錄失敗' })
}
// TODO_02:請將登錄成功后的用戶信息,保存到 Session 中
// 注意:只有成功配置了 express-session 這個中間件之后,才能夠通過 req 點出來 session 這個屬性
req.session.user = req.body // 用戶的信息
req.session.islogin = true // 用戶的登錄狀態(tài)
res.send({ status: 0, msg: '登錄成功' })
})
- 從 session 中
取數(shù)據(jù)
可以直接從req.session
對象上獲取之前存儲的數(shù)據(jù),示例代碼如下:
app.get('/api/username', (req, res) => {
// TODO_03:請從 Session 中獲取用戶的名稱,響應(yīng)給客戶端
if (!req.session.islogin) {
return res.send({ status: 1, msg: 'fail' })
}
res.send({
status: 0,
msg: 'success',
username: req.session.user.username,
})
})
- 清空 session
調(diào)用req.session.destroy
函數(shù),即可清空服務(wù)器保存的session信息。
app.post('/api/logout', (req, res) => {
// TODO_04:清空 Session 信息
req.session.destroy()
res.send({
status: 0,
msg: '退出登錄成功',
})
})
1.5 JWT 認(rèn)證機制
- 了解 Session 認(rèn)證的
局限性
Session 認(rèn)證機制需要配合 Cookie 才能實現(xiàn)
。由于 Cookie 默認(rèn)不支持跨域訪問,所以,當(dāng)涉及到前端跨域請求后端接口
的時候,需要做很多額外的配置
,才能實現(xiàn)跨域 Session 認(rèn)證。
注意:
(1)當(dāng)前端請求后端接口不存在跨域問題
的時候,推薦使用 Session
身份認(rèn)證機制。
(2)當(dāng)前端需要跨域請求后端接口的時候,不推薦使用 Session 身份認(rèn)證機制,推薦使用 JWT 認(rèn)證機制。
-
什么是 JWT
JWT(英文全稱:JSON Web Token)是目前最流行的跨域認(rèn)證解決方案
。 -
JWT 的
工作原理
總結(jié):用戶的信息通過 Token 字符串的形式,保存在客戶端瀏覽器中,服務(wù)器通過還原Token字符串的形式來認(rèn)證用戶的身份 -
JWT 的組成部分
JWT 通常由三部分組成,分別是Header
(頭部)、Payload
(有效荷載)、Signature
(簽名)。
三者之間使用英文的 “.” 分隔,格式如下:
Header.Payload.Signature
下面是 JWT 字符串的示例:
- JWT 的
三個部分各自代表的含義
JWT的三個組成部分,從前到后分別是 Header 、Payload、Signature。
其中:
-
Payload
部分才是真正的用戶信息
,它是用戶信息經(jīng)過加密之后生成的字符串。 - Header 和 Signature 是
安全性相關(guān)
的部分,只是為了保證 Token 的安全性。
- JWT 的
使用方式
客戶端收到服務(wù)器返回的 JWT 之后,通常會將它儲存在localStorage
或sessionStorage
中。
此后,客戶端每次與服務(wù)器通信,都要帶上這個 JWT 的字符串,從而進(jìn)行身份認(rèn)證。推薦的做法是把 JWT 放在 HTTP 請求頭的 Authorization 字段中
,格式如下:
Authorization: Bearer<token>
1.6 在 Express 中使用 JWT
-
安裝
JWT 相關(guān)的包
運行命令如下,安裝如下兩個 JWT 相關(guān)的包:
npm install jsonwebtoken express-jwt
其中:文章來源:http://www.zghlxwxcb.cn/news/detail-405912.html
-
jsonwebtoken
用于生成 JWT 字符串 -
express-jwt
用于將 JWT 字符串解析還原成 JSON 對象
-
導(dǎo)入
JWT 相關(guān)的包
使用require()函數(shù),分別導(dǎo)入JWT相關(guān)的兩個包:
// TODO_01:安裝并導(dǎo)入 JWT 相關(guān)的兩個包,分別是 jsonwebtoken 和 express-jwt
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
- 定義 secret 密鑰
為了保證 JWT 字符串的安全性
,防止 JWT 字符串在網(wǎng)絡(luò)傳輸過程中被別人破解,我們需要定義一個用于加密
和解密
的 secret 密鑰:
(1)當(dāng)生成 JWT 字符串的時候,需要使用 secret 密鑰對用戶的信息進(jìn)行加密
,最終得到加密好的 JWT 字符串
(2)當(dāng)把 JWT 字符串解析還原成 JSON 對象的時候,需要使用 secret 密鑰進(jìn)行解密
// TODO_02:定義 secret 密鑰,建議將密鑰命名為 secretKey
const secretKey = 'itheima No1 ^_^'
- 在登錄成功后生成 JWT 字符串
調(diào)用jsonwebtoken
包提供的 sign() 方法,將用戶的信息加密成 JWT 字符串,響應(yīng)給客戶端:
app.post('/api/login', function (req, res) {
// 將 req.body 請求體中的數(shù)據(jù),轉(zhuǎn)存為 userinfo 常量
const userinfo = req.body
// 登錄失敗
if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
return res.send({
status: 400,
message: '登錄失??!',
})
}
// 登錄成功
// TODO_03:在登錄成功之后,調(diào)用 jwt.sign() 方法生成 JWT 字符串。并通過 token 屬性發(fā)送給客戶端
// 參數(shù)1:用戶的信息對象
// 參數(shù)2:加密的秘鑰
// 參數(shù)3:配置對象,可以配置當(dāng)前 token 的有效期
// 記?。呵f不要把密碼加密到 token 字符中
const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
res.send({
status: 200,
message: '登錄成功!',
token: tokenStr, // 要發(fā)送給客戶端的 token 字符串
})
})
- 將 JWT 字符串
還原成
JSON 對象
客戶端每次在訪問那些有權(quán)限接口的時候,都需要主動通過請求頭中的 Authorization 字段
,將 Token 字符串發(fā)送到服務(wù)器進(jìn)行身份認(rèn)證。
此時,服務(wù)器可以通過express-jwt
這個中間件,自動將客戶端發(fā)送過來的 Token 解析還原成 JSON 對象:
// 注意:只要配置成功了 express-jwt 這個中間件,就可以把解析出來的用戶信息,掛載到 req.user 屬性上
//expressJWT({ secret: secretKey }) 就是用來解析 Token 的中間件
//.unless({ path: [/^\/api\//] }) 用來指定哪些接口不需要訪問權(quán)限
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
- 使用 req.user 獲取用戶信息
當(dāng) express-jwt 這個中間件配置成功之后,即可在那些有權(quán)限的接口中,使用req.user
對象,來訪問從 JWT 字符串中解析出來的用戶信息了,示例代碼如下:
// 這是一個有權(quán)限的 API 接口
app.get('/admin/getinfo', function (req, res) {
// TODO_05:使用 req.user 獲取用戶信息,并使用 data 屬性將用戶信息發(fā)送給客戶端
console.log(req.user)
res.send({
status: 200,
message: '獲取用戶信息成功!',
data: req.user, // 要發(fā)送給客戶端的用戶信息
})
})
文章來源地址http://www.zghlxwxcb.cn/news/detail-405912.html
- 捕獲解析 JWT 失敗后產(chǎn)生的錯誤
當(dāng)使用 express-jwt 解析 Token 字符串時,如果客戶端發(fā)送過來的 Token 字符串過期
或不合法
,會產(chǎn)生一個解析失敗的錯誤,影響項目的正常運行。我們可以通過Express 的錯誤中間件
,捕獲這個錯誤并進(jìn)行相關(guān)的處理,示例代碼如下:
// TODO_06:使用全局錯誤處理中間件,捕獲解析 JWT 失敗后產(chǎn)生的錯誤
app.use((err, req, res, next) => {
// 這次錯誤是由 token 解析失敗導(dǎo)致的
if (err.name === 'UnauthorizedError') {
return res.send({
status: 401,
message: '無效的token',
})
}
res.send({
status: 500,
message: '未知的錯誤',
})
})
到了這里,關(guān)于前后端的身份認(rèn)證【Node.js】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!