2023-1-16 前端達(dá)人
目前主流的 Web 開發(fā)模式有兩種,分別是:
服務(wù)端渲染的概念:服務(wù)器發(fā)送給客戶端的 HTML 頁面,是在服務(wù)器通過字符串的拼接,動(dòng)態(tài)生成的。因此,客戶端不需要使用 Ajax 這樣的技術(shù)額外請求頁面的數(shù)據(jù)。代碼示例如下:
優(yōu)點(diǎn):
缺點(diǎn):
前后端分離的概念:前后端分離的開發(fā)模式,依賴于 Ajax 技術(shù)的廣泛應(yīng)用。簡而言之,前后端分離的 Web 開發(fā)模式,就是后端只負(fù)責(zé)提供 API 接口,前端使用 Ajax 調(diào)用接口的開發(fā)模式。
優(yōu)點(diǎn):
缺點(diǎn):
不利于 SEO。因?yàn)橥暾?nbsp;HTML 頁面需要在客戶端動(dòng)態(tài)拼接完成,所以爬蟲對無法爬取頁面的有效信息。(解決方案:利用 Vue、React 等前端框架的 SSR (server side render)技術(shù)能夠很好的解決 SEO 問題?。?/span>
不談業(yè)務(wù)場景而盲目選擇使用何種開發(fā)模式都是耍流氓。
另外,具體使用何種開發(fā)模式并不是絕對的,為了同時(shí)兼顧了首頁的渲染速度和前后端分離的開發(fā)效率,一些網(wǎng)站采用了首屏服務(wù)器端渲染 + 其他頁面前后端分離的開發(fā)模式。
身份認(rèn)證(Authentication)又稱“身份驗(yàn)證”、“鑒權(quán)”,是指通過一定的手段,完成對用戶身份的確認(rèn)。
身份認(rèn)證的目的,是為了確認(rèn)當(dāng)前所聲稱為某種身份的用戶,確實(shí)是所聲稱的用戶。例如,你去找快遞員取快遞,你要怎么證明這份快遞是你的。
在互聯(lián)網(wǎng)項(xiàng)目開發(fā)中,如何對用戶的身份進(jìn)行認(rèn)證,是一個(gè)值得深入探討的問題。例如,如何才能保證網(wǎng)站不會錯(cuò)誤的將“馬云的存款數(shù)額”顯示到“馬化騰的賬戶”上。
對于服務(wù)端渲染和前后端分離這兩種開發(fā)模式來說,分別有著不同的身份認(rèn)證方案:
對于超市來說,為了方便收銀員在進(jìn)行結(jié)算時(shí)給 VIP 用戶打折,超市可以為每個(gè) VIP 用戶發(fā)放會員卡。
注意:現(xiàn)實(shí)生活中的會員卡身份認(rèn)證方式,在 Web 開發(fā)中的專業(yè)術(shù)語叫做 Cookie
Cookie的幾大特性:
客戶端第一次請求服務(wù)器的時(shí)候,服務(wù)器通過響應(yīng)頭的形式,向客戶端發(fā)送一個(gè)身份認(rèn)證的 Cookie,客戶端會自動(dòng)將 Cookie 保存在瀏覽器中。
隨后,當(dāng)客戶端瀏覽器每次請求服務(wù)器的時(shí)候,瀏覽器會自動(dòng)將身份認(rèn)證相關(guān)的 Cookie,通過請求頭的形式發(fā)送給服務(wù)器,服務(wù)器即可驗(yàn)明客戶端的身份。
由于 Cookie 是存儲在瀏覽器中的,而且瀏覽器也提供了讀寫 Cookie 的 API,因此 Cookie 很容易被偽造,不具有安全性。因此不建議服務(wù)器將重要的隱私數(shù)據(jù),通過 Cookie 的形式發(fā)送給瀏覽器。
注意:千萬不要使用 Cookie 存儲重要且隱私的數(shù)據(jù)!比如用戶的身份信息、密碼等。
3.6、提高身份認(rèn)證的安全性
為了防止客戶偽造會員卡,收銀員在拿到客戶出示的會員卡之后,可以在收銀機(jī)上進(jìn)行刷卡認(rèn)證。只有收銀機(jī)確認(rèn)存在的會員卡,才能被正常使用。
這種“會員卡 + 刷卡認(rèn)證”的設(shè)計(jì)理念,就是 Session 認(rèn)證機(jī)制的精髓。
在 Express 項(xiàng)目中,只需要安裝 express-session 中間件,即可在項(xiàng)目中使用 Session 認(rèn)證:
npm install express-session
express-session 中間件安裝成功后,需要通過 app.use() 來注冊 session 中間件,示例代碼如下:
-
// 導(dǎo)入 session 中間件
-
const session = require('express-session')
-
-
// 配置 session 中間件
-
app.use(
-
session({
-
secret: 'itheima', // secret 屬性的值可以為任意字符串
-
resave: false, // 固定寫法
-
saveUninitialized: true, // 固定寫法
-
})
-
)
當(dāng) express-session 中間件配置成功后,即可通過 req.session 來訪問和使用 session 對象,從而存儲用戶的關(guān)鍵信息:
-
// 登錄的 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 這個(gè)中間件之后,才能夠通過 req 點(diǎn)出來 session 這個(gè)屬性
-
req.session.user = req.body // 用戶的信息
-
console.log(req.body)
-
req.session.islogin = true // 用戶的登錄狀態(tài)
-
-
res.send({ status: 0, msg: '登錄成功' })
-
})
可以直接從 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,
-
})
-
})
調(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: '退出登錄成功',
-
})
-
})
index.html
-
<!DOCTYPE html>
-
<html lang="en">
-
-
<head>
-
<meta charset="UTF-8">
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
<title>后臺主頁</title>
-
<script src="./jquery.js"></script>
-
</head>
-
-
<body>
-
<h1>首頁</h1>
-
-
<button id="btnLogout">退出登錄</button>
-
-
<script>
-
$(function () {
-
-
// 頁面加載完成后,自動(dòng)發(fā)起請求,獲取用戶姓名
-
$.get('/api/username', function (res) {
-
// status 為 0 表示獲取用戶名稱成功;否則表示獲取用戶名稱失敗!
-
if (res.status !== 0) {
-
alert('您尚未登錄,請登錄后再執(zhí)行此操作!')
-
location.href = './login.html'
-
} else {
-
alert('歡迎您:' + res.username)
-
}
-
})
-
-
// 點(diǎn)擊按鈕退出登錄
-
$('#btnLogout').on('click', function () {
-
// 發(fā)起 POST 請求,退出登錄
-
$.post('/api/logout', function (res) {
-
if (res.status === 0) {
-
// 如果 status 為 0,則表示退出成功,重新跳轉(zhuǎn)到登錄頁面
-
location.href = './login.html'
-
}
-
})
-
})
-
})
-
</script>
-
</body>
-
-
</html>
login.html
-
<!DOCTYPE html>
-
<html lang="en">
-
-
<head>
-
<meta charset="UTF-8">
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
<title>登錄頁面</title>
-
<script src="./jquery.js"></script>
-
</head>
-
-
<body>
-
<!-- 登錄表單 -->
-
<form id="form1">
-
<div>賬號:<input type="text" name="username" autocomplete="off" /></div>
-
<div>密碼:<input type="password" name="password" /></div>
-
<button>登錄</button>
-
</form>
-
-
<script>
-
$(function () {
-
// 監(jiān)聽表單的提交事件
-
$('#form1').on('submit', function (e) {
-
// 阻止默認(rèn)提交行為
-
e.preventDefault()
-
// 發(fā)起 POST 登錄請求
-
$.post('/api/login', $(this).serialize(), function (res) {
-
// status 為 0 表示登錄成功;否則表示登錄失??!
-
if (res.status === 0) {
-
location.href = './index.html'
-
} else {
-
alert('登錄失敗!')
-
}
-
})
-
})
-
})
-
</script>
-
</body>
-
-
</html>
app.js
-
// 導(dǎo)入 express 模塊
-
const express = require('express')
-
// 創(chuàng)建 express 的服務(wù)器實(shí)例
-
const app = express()
-
-
// TODO_01:請配置 Session 中間件
-
const session = require('express-session')
-
app.use(
-
session({
-
secret: 'itheima',
-
resave: false,
-
saveUninitialized: true,
-
})
-
)
-
-
// 托管靜態(tài)頁面
-
app.use(express.static('./pages'))
-
// 解析 POST 提交過來的表單數(shù)據(jù)
-
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 中
-
// 注意:只有成功配置了 express-session 這個(gè)中間件之后,才能夠通過 req 點(diǎn)出來 session 這個(gè)屬性
-
req.session.user = req.body // 用戶的信息
-
console.log(req.body)
-
req.session.islogin = true // 用戶的登錄狀態(tài)
-
-
res.send({ status: 0, msg: '登錄成功' })
-
})
-
-
// 獲取用戶姓名的接口
-
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,
-
})
-
})
-
-
// 退出登錄的接口
-
app.post('/api/logout', (req, res) => {
-
// TODO_04:清空 Session 信息
-
req.session.destroy()
-
res.send({
-
status: 0,
-
msg: '退出登錄成功',
-
})
-
})
-
-
// 調(diào)用 app.listen 方法,指定端口號并啟動(dòng)web服務(wù)器
-
app.listen(80, function () {
-
console.log('Express server running at http://127.0.0.1:80')
-
})
Session 認(rèn)證機(jī)制需要配合 Cookie 才能實(shí)現(xiàn)。由于 Cookie 默認(rèn)不支持跨域訪問,所以,當(dāng)涉及到前端跨域請求后端接口的時(shí)候,需要做很多額外的配置,才能實(shí)現(xiàn)跨域 Session 認(rèn)證。
注意:
JWT(英文全稱:JSON Web Token)是目前最流行的跨域認(rèn)證解決方案。
總結(jié):用戶的信息通過 Token 字符串的形式,保存在客戶端瀏覽器中。服務(wù)器通過還原 Token 字符串的形式來認(rèn)證用戶的身份。
JWT 通常由三部分組成,分別是 Header(頭部)、Payload(有效荷載)、Signature(簽名)。
三者之間使用英文的“.”分隔,格式如下:
下面是 JWT 字符串的示例:
JWT 的三個(gè)組成部分,從前到后分別是 Header、Payload、Signature。
其中:
客戶端收到服務(wù)器返回的 JWT 之后,通常會將它儲存在 localStorage 或 sessionStorage 中。
此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè) JWT 的字符串,從而進(jìn)行身份認(rèn)證。推薦的做法是把 JWT 放在 HTTP 請求頭的 Authorization 字段中,格式如下:
運(yùn)行如下命令,安裝如下兩個(gè) JWT 相關(guān)的包:
npm install jsonwebtoken express-jwt
其中:
使用 require() 函數(shù),分別導(dǎo)入 JWT 相關(guān)的兩個(gè)包:
-
// 安裝并導(dǎo)入 JWT 相關(guān)的兩個(gè)包,分別是 jsonwebtoken 和 express-jwt
-
const jwt = require('jsonwebtoken')
-
const expressJWT = require('express-jwt')
為了保證 JWT 字符串的安全性,防止 JWT 字符串在網(wǎng)絡(luò)傳輸過程中被別人破解,我們需要專門定義一個(gè)用于加密和解密的 secret 密鑰:
-
// 定義 secret 密鑰,建議將密鑰命名為 secretKey,本質(zhì)上就是一個(gè)字符串
-
const secretKey = 'itheima No1 ^_^'
調(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 字符串
-
})
-
})
-
// 使用 app.use() 來注冊中間件
-
// expressJWT({ secret: secretKey }) 就是用來解析 Token 的中間件
-
// .unless({ path: [/^\/api\//] }) 用來指定哪些接口不需要訪問權(quán)限
-
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
6.6、使用 req.user 獲取用戶信息
當(dāng) express-jwt 這個(gè)中間件配置成功之后,即可在那些有權(quán)限的接口中,使用 req.user 對象,來訪問從 JWT 字符串中解析出來的用戶信息了,示例代碼如下:
-
// 這是一個(gè)有權(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ā)送給客戶端的用戶信息
-
})
-
})
藍(lán)藍(lán)設(shè)計(jì)( www.bouu.cn )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)、UI設(shè)計(jì)公司、界面設(shè)計(jì)公司、UI設(shè)計(jì)服務(wù)公司、數(shù)據(jù)可視化設(shè)計(jì)公司、UI交互設(shè)計(jì)公司、高端網(wǎng)站設(shè)計(jì)公司、UI咨詢、用戶體驗(yàn)公司、軟件界面設(shè)計(jì)公司
藍(lán)藍(lán)設(shè)計(jì)的小編 http://www.bouu.cn