express vue-axios 實現(xiàn)用戶登錄狀態(tài)保存

本文講解在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)的流程圖:


cookie.png

首先大家讀懂了上面的實現(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)的保存磨德。
如有錯誤,請大家進行指正吆视,謝謝典挑!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市啦吧,隨后出現(xiàn)的幾起案子您觉,更是在濱河造成了極大的恐慌,老刑警劉巖授滓,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琳水,死亡現(xiàn)場離奇詭異肆糕,居然都是意外死亡,警方通過查閱死者的電腦和手機在孝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門诚啃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人私沮,你說我怎么就攤上這事始赎。” “怎么了仔燕?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵造垛,是天一觀的道長。 經(jīng)常有香客問我晰搀,道長筋搏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任厕隧,我火速辦了婚禮奔脐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吁讨。我一直安慰自己髓迎,他們只是感情好,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布建丧。 她就那樣靜靜地躺著排龄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪翎朱。 梳的紋絲不亂的頭發(fā)上橄维,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機與錄音拴曲,去河邊找鬼争舞。 笑死,一個胖子當著我的面吹牛澈灼,可吹牛的內(nèi)容都是我干的竞川。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼叁熔,長吁一口氣:“原來是場噩夢啊……” “哼委乌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荣回,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤遭贸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后心软,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壕吹,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡著蛙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了算利。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片册踩。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖效拭,靈堂內(nèi)的尸體忽然破棺而出暂吉,到底是詐尸還是另有隱情,我是刑警寧澤缎患,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站挤渔,受9級特大地震影響嫉父,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜仪际,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一变秦、第九天 我趴在偏房一處隱蔽的房頂上張望伴栓。 院中可真熱鬧,春花似錦额港、人聲如沸肚医。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搁痛。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纽甘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工破婆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留祷舀,地道東北人抛丽。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像蒿柳,于是被迫代替她去往敵國和親垒探。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容