本文講解在node平臺下强霎,使用express web服務(wù)框架霹琼,通過cookie和session以及封裝express中間件走越,實現(xiàn)用戶登錄信息的保存(下次打開網(wǎng)站自動登錄功能)。
我們先介紹功能的實現(xiàn)思路,后面再具體講解代碼實現(xiàn)的細節(jié)問題津函。
思路:首先是用戶登錄. 用戶登錄成功以后,express中我們將用戶登錄標識保存到session中孤页,比如用戶的token尔苦。每次前端請求express服務(wù)端的時候,我們都要判斷session中是否有用戶登錄的標識行施,如果有允坚,代表當前用戶是登錄狀態(tài)。如果沒有蛾号,則代表用戶當前沒有登錄稠项,將用戶登錄界面響應(yīng)給前端。由于session的生命周期鲜结,當我們關(guān)閉瀏覽器以后展运,session就會銷毀,所以無法做到保存用戶登錄狀態(tài)信息轻腺。我們就需要通過cookie來保存用戶賬號信息乐疆,并且設(shè)置cookie的有效期划乖,cookie過了有效期以后贬养,就會銷毀。
經(jīng)過上面總結(jié)琴庵,實現(xiàn)步驟為:
1.用戶登錄成功误算,將登錄成功的標識(比如token)保存到session中,并且將用戶名和密碼經(jīng)過加密(防止cookie被盜取迷殿,用戶名和密碼泄露)保存到cookie中儿礼。
2.每次前端請求express服務(wù)端接口的時候
a:服務(wù)端要判斷session中有沒有登錄成功的標識(比如token),如果有庆寺,則說明用戶已經(jīng)登錄蚊夫,將結(jié)果返回給用戶。
b:如果session中沒有登錄成功的標識懦尝,那么再去cookie中檢查是否有當初用戶登錄成功以后保存的經(jīng)過加密的用戶名和密碼知纷,
c:如果有,我們將加密的用戶名和密碼進行解密陵霉,獲取真實的用戶名和密碼琅轧,去進行登錄。
d:如果登錄成功踊挠,則把成功的標識再次保存到session中乍桂,說明用戶登錄成功了。
e:如果解密后的用戶名和密碼,進行登錄睹酌,沒有登錄成功权谁。那么說明cookie中保存的信息不對,可能是被人惡意篡改憋沿,此時闯传,服務(wù)端認為此時用戶沒有登錄,返回給前端用戶登錄頁面卤妒。
f:如果cookie中沒有用戶信息甥绿。那么認為用戶沒有登錄。
以上步驟對應(yīng)的流程圖:
首先大家讀懂了上面的實現(xiàn)流程则披,接下來我們再看express中代碼的具體實現(xiàn)共缕。
技術(shù)點包括:
1.express中cookie,session的使用
2.node中crypto庫用來對用戶名和密碼實現(xiàn)加密解密
3.通過express中間件實現(xiàn)所有接口均實現(xiàn)用戶是否登錄檢查
4.前端部分,vue通過axios中請求攔截實現(xiàn)未登錄路由跳轉(zhuǎn)
服務(wù)端處理用戶登錄的邏輯:
exports.login = async (req, res, next) => {
// 在請求體里面獲取客服端傳遞過來的參數(shù)士复,
//我們需要配置express.json中間件图谷,才能獲取到請求體的參數(shù)。
//具體使用方法見下段代碼
const {username, password} = req.body
try {
const loginRes = await request({
url: 'xxx/login',
method: 'POST',
data: {
username,
password
}
})
//如果 獲取到用戶的token表明登錄成功
if (loginRes.data.auth_token.length > 0) {
// login success 記錄session保存用戶登錄狀態(tài)
req.session.auth_token = loginRes.data.auth_token
// 把 用戶名 和 密碼 經(jīng)過加密(對稱加密)以后阱洪, 保存到cookie中便贵, 實現(xiàn)保持用戶登錄狀態(tài)
// 對用戶名和密碼 進行加密, 防止cookie被盜取 破解
// encrypt 方法的封裝 見下段代碼
const encryptUser = encrypt(JSON.stringify({
username,
password
}))
// cookie存儲的形式為鍵值對冗荸,所以 第一個參數(shù) 為鍵承璃, 第二個值為value。
//需要配置cookie中間件蚌本,具體寫法見下面
res.cookie('user', encryptUser, {
maxAge: 1000 * 60 * 60 * 24 * 7 //設(shè)置cookie的保存時間盔粹,7天為例
})
res.status(200).json({
'code': 0,
'msg': 'ok'
})
} else {
res.status(200).json({
'code': 1,
'msg': 'auth_token is null'
})
}
} catch (e) {
res.status(200).json({
'code': 2,
'msg': '賬號或密碼錯誤'
})
}
}
配置express.json和express.urlencode中間件,用來解析請求體的參數(shù)以及url中的參數(shù)
const express = require('express')
const cookieParser = require('cookie-parser') // npm i cookie-parser -S
const app = express()
// session
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
// 配置解析 Cookie 的中間件
app.use(cookieParser())
// 解析請求體
app.use(express.json())
app.use(express.urlencoded({
extended: true
}))
node平臺下crypto加密庫的使用
const crypto = require('crypto')
exports.encrypt = data => {
// createCipher函數(shù)接收兩個參數(shù)
// 第一個參數(shù)為 加密方式
//const ciphers = crypto.getCiphers();
//console.log(ciphers); // ['aes-128-cbc', 'aes-128-ccm', ...]
// 通過上述代碼 可以獲取到crypto支持的所有加密方式
//第二個參數(shù)為加密向量程癌,也稱為 加鹽(絕對隱私舷嗡,解密需要用到)
const cipher = crypto.createCipher('aes-256-cfb', 'aaron')
let encrypted = cipher.update(data, 'utf8', 'hex')
encrypted += cipher.final('hex')
return encrypted
}
exports.decrypt = (data) => {
// 解密數(shù)據(jù) 解密方式要和加密時使用的一致
const decipher = crypto.createDecipher('aes-256-cfb', 'aaron')
let decrypted = decipher.update(data, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
}
經(jīng)過上述操作,當用戶登錄成功以后嵌莉,我們分別把token記錄到session中进萄,用戶名和密碼保存到cookie中。以后锐峭,當客戶端請求其他接口服務(wù)的時候中鼠,代碼的具體寫法為:
// 獲取登錄時記錄到session中的token
const { auth_token } = req.session
// 如果session 中沒有登錄信息記錄 那么 再去cookie中獲取 用戶名和 密碼
if (!auth_token) {
const { user: cookieUser } = req.cookies
if (!cookieUser) {
// cookie中 沒有用戶登錄信息 說明 用戶沒有登錄過
// 響應(yīng)給客戶端401錯誤
return res.status(401).json({
msg: 'not login'
})
}
//如果 cookie中有 加密的用戶登錄信息 解密 獲得 用戶名 和 密碼 進行 登錄
const decryptUser = JSON.parse(decrypt(cookieUser))
try {
const loginRes = await request({
url: 'xxx/login/',
method: 'POST',
data: {
username: decryptUser.username,
password: decryptUser.password
}
})
// 獲取到token代表
if (loginRes.data.auth_token.length > 0) {
// cookie 信息 登錄 成功 保存到session中
req.session.auth_token = loginRes.data.auth_token
//處理客戶端請求(比如是獲取用戶詳情,此時將用戶詳情信息返回給客戶端)
} else {
// 登錄失敗 重新登錄
return res.status(401).json({
msg: 'not login'
})
}
} catch (e) {
// 登錄失敗 重新登錄
return res.status(401).json({
msg: 'not login'
})
}
else {
// 此時代表session中有token只祠,說明客戶端已經(jīng)登錄
//處理客戶端請求(比如是獲取用戶詳情兜蠕,此時將用戶詳情信息返回給客戶端)
}
具體檢查用戶是否登錄邏輯就完成了,同學(xué)們可以根據(jù)自己業(yè)務(wù)的具體情況抛寝,對代碼進行修改熊杨∈镄瘢總之,實現(xiàn)思路是通用的晶府。
難道桂躏,我們在服務(wù)端的每個服務(wù)中都要寫這么多的邏輯代碼進行檢測用戶登錄嗎?首先每個服務(wù)確實都要檢測用戶是否登錄川陆,但是我們可以封裝一個中間件剂习,對客戶端的請求進行統(tǒng)一的檢測。接下來较沪,請看封裝后中間件的代碼:
module.exports = async (req, res, next) => {
// 如果客戶端訪問的是Login服務(wù)鳞绕,那么我們不需要檢查用戶是否登錄
// 直接 return next() 傳遞到下一步, 也就是我們的登錄服務(wù)的邏輯處理
// 比如此時用戶請求的是獲取用戶詳情接口(/api/userinfo)尸曼,next()此時代表將事件傳遞給
//我們所寫的(/api/userinfo)事件中
if (req.path === '/api/login') {
return next()
}
const { auth_token } = req.session
// 如果session 中沒有登錄信息記錄 那么 再去cookie中獲取 用戶名和 密碼
if (!auth_token) {
const { user: cookieUser } = req.cookies
if (!cookieUser) {
// cookie中 沒有用戶登錄信息 說明 用戶沒有登錄過
// 直接返回給客戶端401
return res.status(401).json({
msg: 'not login'
})
}
//如果 cookie中有 加密的用戶登錄信息 解密 獲得 用戶名 和 密碼 進行 登錄
const decryptUser = JSON.parse(decrypt(cookieUser))
try {
const loginRes = await request({
url: 'xxx/login/',
method: 'POST',
data: {
username: decryptUser.username,
password: decryptUser.password
}
})
if (loginRes.data.auth_token.length > 0) {
// cookie 信息 登錄 成功 保存到session中
req.session.auth_token = loginRes.data.auth_token
// next()傳遞到下一個處理邏輯们何。
return next()
} else {
// 登錄失敗 重新登錄
return res.status(401).json({
msg: 'not login'
})
}
} catch (e) {
// 登錄失敗 重新登錄
return res.status(401).json({
msg: 'not login'
})
}
}
// session中有用戶登錄標識,直接next()
return next()
}
中間件封裝完畢以后控轿,我們要向express中注入中間件冤竹,這樣所有的客戶端的請求,會先經(jīng)過我們封裝的這個中間件茬射,如果中間件中next().那么才會走向下一個中間件鹦蠕。這樣,就實現(xiàn)了對客戶端所有的請求統(tǒng)一進行用戶登錄狀態(tài)的驗證在抛。
const express = require('express')
const router = require('./router')
const checkSession = require('./middleware/checkSession')
const app = express()
//***重點注意钟病, 我們封裝的中間件 一定要在路由中間件之前use。
app.use(checkSession)
app.use(router)
app.listen(3000, () => {
console.log('api listening port 3000...')
})
上面的所有代碼為express服務(wù)端需要處理的邏輯霜定。然后我以前端vue使用axios為例档悠,通過axios的請求攔截器,在前端對客戶端返回的401進行路由跳轉(zhuǎn)處理望浩。
import axios from 'axios'
import router from '../router/index'
const instance = axios.create()
// interceptors 用于對所有http請求進行攔截
instance.interceptors.response.use(res => {
return res
}, error => {
if (error.response) {
switch (error.response.status) {
case 401:
// 如果是401狀態(tài), 路由直接跳轉(zhuǎn)到login界面
router.replace({
path: '/login'
})
}
}
// 將錯誤信息傳遞下去
return Promise.reject(error.response.data)
})
export default instance
這樣惰说,我們就實現(xiàn)了express服務(wù)端的用戶登錄狀態(tài)的保存磨德。
如有錯誤,請大家進行指正吆视,謝謝典挑!