目錄
1. Web 開發模式
1.1?服務端渲染的 Web 開發模式
1.2?服務端渲染的優缺點
1.3?前后端分離的 Web 開發模式
1.4?前后端分離的優缺點
1.5?如何選擇 Web 開發模式
2. 身份認證
2.1 什么是身份認證
2.2 為什么需要身份認證
2.3?不同開發模式下的身份認證
3. Session 認證機制
3.1?HTTP 協議的無狀態性
3.2?如何突破 HTTP 無狀態的限制
3.3 什么是 Cookie
3.4?Cookie 在身份認證中的作用
3.5?Cookie 不具有安全性
3.6?提高身份認證的安全性
3.7?Session 的工作原理
4. 在 Express 中使用 Session 認證
4.1?安裝 express-session 中間件
4.2?配置 express-session 中間件
4.3 向 session 中存數據
4.4 從 session 中取數據
4.5 清空 session
5. JWT 認證機制(重點)
5.1 了解 Session 認證的局限性
5.2?什么是 JWT
5.3 JWT 的工作原理
5.4 JWT 的組成部分
5.6 JWT 的三個部分各自代表的含義
5.7 JWT 的使用方式
5.7.1?安裝 JWT 相關的包
5.7.2?導入 JWT 相關的包
5.7.3?定義 secret 密鑰
5.7.4?在登錄成功后生成 JWT 字符串
5.7.5 將 JWT 字符串還原為 JSON 對象
5.7.6 使用 req.auth 獲取用戶信息
5.7.7 捕獲解析 JWT 失敗后產生的錯誤
1. Web 開發模式
- ① 基于服務端渲染的傳統 Web 開發模式
- ② 基于前后端分離的新型 Web 開發模式
1.1?服務端渲染的 Web 開發模式
- 服務器發送給客戶端的 HTML 頁面,是在服務器通過字符串的拼接,動態生成的。因此,客戶端不需要使用 Ajax 這樣的技術額外請求頁面的數據。
- 代碼示例如下:
1.2?服務端渲染的優缺點
- ① 前端耗時少。因為服務器端負責動態生成 HTML 內容,瀏覽器只需要直接渲染頁面即可。尤其是移動端,更省電。
- ② 有利于SEO。因為服務器端響應的是完整的 HTML 頁面內容,所以爬蟲更容易爬取獲得信息,更有利于 SEO。
- ① 占用服務器端資源。即服務器端完成 HTML 頁面內容的拼接,如果請求較多,會對服務器造成一定的訪問壓力。
- ② 不利于前后端分離,開發效率低。使用服務器端渲染,則無法進行分工合作,尤其對于前端復雜度高的項目,不利于項目高效開發。
1.3?前后端分離的 Web 開發模式
- 前后端分離的開發模式,依賴于 Ajax 技術的廣泛應用。簡而言之,前后端分離的 Web 開發模式,就是后端只負責提供 API 接口,前端使用 Ajax 調用接口的開發模式。
1.4?前后端分離的優缺點
- ① 開發體驗好。前端專注于 UI 頁面的開發,后端專注于api 的開發,且前端有更多的選擇性。
- ② 用戶體驗好。Ajax 技術的廣泛應用,極大的提高了用戶的體驗,可以輕松實現頁面的局部刷新。
- ③ 減輕了服務器端的渲染壓力。因為頁面最終是在每個用戶的瀏覽器中生成的。
- ① 不利于 SEO。因為完整的 HTML 頁面需要在客戶端動態拼接完成,所以爬蟲對無法爬取頁面的有效信息。(解決方案:利用 Vue、React 等前端框架的 SSR (server side render)技術能夠很好的解決 SEO 問題!)
1.5?如何選擇 Web 開發模式
- 比如企業級網站,主要功能是展示而沒有復雜的交互,并且需要良好的 SEO,則這時我們就需要使用服務器端渲染;
- 而類似后臺管理項目,交互性比較強,不需要考慮 SEO,那么就可以使用前后端分離的開發模式。
2. 身份認證
2.1 什么是身份認證
- 日常生活中的身份認證隨處可見,例如:高鐵的驗票乘車,手機的密碼或指紋解鎖,支付寶或微信的支付密碼等。
- 在 Web 開發中,也涉及到用戶身份的認證,例如:各大網站的手機驗證碼登錄、郵箱密碼登錄、二維碼登錄等。
2.2 為什么需要身份認證
2.3?不同開發模式下的身份認證
- ① 服務端渲染推薦使用 Session 認證機制
- ② 前后端分離推薦使用 JWT 認證機制
3. Session 認證機制
3.1?HTTP 協議的無狀態性

3.2?如何突破 HTTP 無狀態的限制

- 現實生活中的會員卡身份認證方式,在 Web 開發中的專業術語叫做 Cookie。
3.3 什么是 Cookie
- ① 自動發送
- ② 域名獨立
- ③ 過期時限
- ④ 4KB 限制
3.4?Cookie 在身份認證中的作用
- 客戶端第一次請求服務器的時候,服務器通過響應頭的形式,向客戶端發送一個身份認證的 Cookie,客戶端會自動將 Cookie 保存在瀏覽器中。
- 隨后,當客戶端瀏覽器每次請求服務器的時候,瀏覽器會自動將身份認證相關的 Cookie,通過請求頭的形式發送給服務器,服務器即可驗明客戶端的身份。

3.5?Cookie 不具有安全性

- 千萬不要使用 Cookie 存儲重要且隱私的數據!比如用戶的身份信息、密碼等。
3.6?提高身份認證的安全性

3.7?Session 的工作原理
4. 在 Express 中使用 Session 認證
4.1?安裝 express-session 中間件
npm install express-session
4.2?配置 express-session 中間件
// 導入 session 中間件
const session = require('express-session')// TODO_01:請配置 Session 中間件
app.use(session({secret: 'itheima', // secret 屬性值可以為任意字符串resave: false, // 固定寫法saveUninitialized: true // 固定寫法
}))
4.3 向 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 中req.session.user = req.body // 用戶的信息req.session.islogin = true // 用戶的登錄狀態res.send({ status: 0, msg: '登錄成功' })
})
4.4 從 session 中取數據
// 獲取用戶姓名的接口
app.get('/api/username', (req, res) => {// TODO_03:請從 Session 中獲取用戶的名稱,響應給客戶端if (!req.session.islogin) {return res.send({status: 1,msg: 'fail'})}res.send({status: 0,msg: 'success',username: req.session.user.username})
})
4.5 清空 session
// 退出登錄的接口
app.post('/api/logout', (req, res) => {// TODO_04:清空 Session 信息req.session.destroy()res.send({status: 0,msg: '退出登錄成功'})
})
完整代碼:
// 導入 express 模塊
const express = require('express')
const session = require('express-session')
// 創建 express 的服務器實例
const app = express()// TODO_01:請配置 Session 中間件
app.use(session({secret: 'itheima', // secret 屬性值可以為任意字符串resave: false, // 固定寫法saveUninitialized: true // 固定寫法
}))// 托管靜態頁面
app.use(express.static('./pages'))
// 解析 POST 提交過來的表單數據
app.use(express.urlencoded({ extended: false }))// 登錄的 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 中req.session.user = req.body // 用戶的信息req.session.islogin = true // 用戶的登錄狀態res.send({ status: 0, msg: '登錄成功' })
})// 獲取用戶姓名的接口
app.get('/api/username', (req, res) => {// TODO_03:請從 Session 中獲取用戶的名稱,響應給客戶端if (!req.session.islogin) {return res.send({status: 1,msg: 'fail'})}res.send({status: 0,msg: 'success',username: req.session.user.username})
})// 退出登錄的接口
app.post('/api/logout', (req, res) => {// TODO_04:清空 Session 信息req.session.destroy()res.send({status: 0,msg: '退出登錄成功'})
})// 調用 app.listen 方法,指定端口號并啟動web服務器
app.listen(80, function () {console.log('Express server running at http://127.0.0.1:80')
})
5. JWT 認證機制(重點)
5.1 了解 Session 認證的局限性
- 當前端請求后端接口不存在跨域問題的時候,推薦使用 Session 身份認證機制。
- 當前端需要跨域請求后端接口的時候,不推薦使用 Session 身份認證機制,推薦使用 JWT 認證機制。
5.2?什么是 JWT
5.3 JWT 的工作原理
- 用戶的信息通過 Token 字符串的形式,保存在客戶端瀏覽器中。服務器通過還原 Token 字符串的形式來認證用戶的身份。
5.4 JWT 的組成部分


5.6 JWT 的三個部分各自代表的含義
- Payload 部分才是真正的用戶信息,它是用戶信息經過加密之后生成的字符串。
- Header 和 Signature 是安全性相關的部分,只是為了保證 Token 的安全性。
5.7 JWT 的使用方式

5.7.1?安裝 JWT 相關的包
npm install jsonwebtoken express-jwt
- jsonwebtoken 用于生成 JWT 字符串
- express-jwt 用于將 JWT 字符串解析還原成 JSON 對象
5.7.2?導入 JWT 相關的包
// TODO_01:安裝并導入 JWT 相關的兩個包,分別是 jsonwebtoken 和 express-jwt
// jsonwebtoken 用于生成 JWT 字符串
const jwt = require('jsonwebtoken')
// express-jwt 用于將 JWT 字符串解析還原成 JSON 對象
const { expressjwt } = require('express-jwt')
5.7.3?定義 secret 密鑰
- ① 當生成 JWT 字符串的時候,需要使用 secret 密鑰對用戶的信息進行加密,最終得到加密好的 JWT 字符串
- ② 當把 JWT 字符串解析還原成 JSON 對象的時候,需要使用 secret 密鑰進行解密
- secret 密鑰的本質:就是一個字符串
// TODO_02:定義 secret 密鑰,建議將密鑰命名為 secretKey
const secretKey = 'itheima No1 ^_^'
5.7.4?在登錄成功后生成 JWT 字符串
// 登錄接口
app.post('/api/login', function (req, res) {// 將 req.body 請求體中的數據,轉存為 userinfo 常量const userinfo = req.body// 登錄失敗if (userinfo.username !== 'admin' || userinfo.password !== '000000') {return res.send({status: 400,message: '登錄失敗!'})}// 登錄成功// TODO_03:在登錄成功之后,調用 jwt.sign() 方法生成 JWT 字符串。并通過 token 屬性發送給客戶端// 參數 1:用戶的信息對象// 參數 2:加密的秘鑰// 參數 3:配置對象,可以配置當前 token 的有效期// 記住:千萬不要把用戶的登錄密碼加密到 token 字符串中const token = jwt.sign({username: userinfo.username,},secretKey,{expiresIn: '60s'})res.send({status: 200,message: '登錄成功!',token: token // 要發送給客戶端的 token 字符串})
})
5.7.5 將 JWT 字符串還原為 JSON 對象
// TODO_04:注冊將 JWT 字符串解析還原成 JSON 對象的中間件
// expressjwt({ secret: secretKey, algorithms: ['HS256']}) 就是用來解析 token 的中間件
// .unless({ path: [ /^\/api\// ] })) 用來指定那些接口不需要訪問權限
// / ^\/api\/ / 以 /api/ 開頭 ^ 的接口都不需要訪問權限
// \ 表示轉義
// 注意:只要配置成功了 express-jwt 這個中間件,就可以把解析出來的用戶信息,掛載到 req.auth 屬性上
app.use(expressjwt({ secret: secretKey, algorithms: ['HS256'] }).unless({ path: [ /^\/api\// ] }))
5.7.6 使用 req.auth 獲取用戶信息
// 這是一個有權限的 API 接口
app.get('/admin/getinfo', function (req, res) {// TODO_05:使用 req.auth 獲取用戶信息,并使用 data 屬性將用戶信息發送給客戶端console.log(req.auth)res.send({status: 200,message: '獲取用戶信息成功!',data: req.auth // 要發送給客戶端的用戶信息})
})
5.7.7 捕獲解析 JWT 失敗后產生的錯誤
// TODO_06:使用全局錯誤處理中間件,捕獲解析 JWT 失敗后產生的錯誤
app.use((err, req, res, next) => {// 這次錯誤是由 token 解析失敗導致的if (err.name === 'UnauthorizedError') {console.log('token 過期了。')return res.send({status: 401,message: '無效的token'})}res.send({status: 500,message: '未知的錯誤'})
})