node js如何實現密碼雜湊加密以及jwt登陸驗證

JsRicardo發表於2019-12-27

github原始碼地址

為什麼使用token

session

當使用者第一次通過瀏覽器使用使用者名稱和密碼訪問伺服器時,伺服器會驗證使用者資料,驗證成功後在伺服器端寫入session資料,向客戶端瀏覽器返回sessionid,瀏覽器將sessionid儲存在cookie中。

當使用者再次訪問伺服器時,會攜帶sessionid,伺服器會進行使用者資訊查詢,查詢到,會將查詢到的使用者資訊返回,從而實現狀態保持。

token

token與session的不同主要在認證成功後,會對當前使用者資料進行加密,生成一個加密字串token,返還給客戶端,由客戶端儲存

再次訪問時伺服器端對token值的處理:伺服器對瀏覽器傳來的token值進行解密,解密完成後進行使用者資料的查詢,如果查詢成功,則通過認證,實現狀態保持。

相比較而言,token很大程度上減少了服務端的壓力,伺服器不用儲存那麼多sessionid,只需要對token解密就可以了,也不用儲存使用者在哪個伺服器登入的,只需要jwt加密規則相同就行。

node js實現token

使用到的包:

  • express@next:開啟伺服器
  • cors: 開啟跨域請求
  • dotenv: 應用env檔案
  • mongoose: 連線MongoDB
  • bcrypt: bcrypt雜湊加密,或者使用bcryptjs,相容性好,不過效能差一點
  • jsonwebtoken: 無屬性token生成

思路:

  1. 為了保證使用者密碼的安全性,使用者的密碼存入到資料庫之前應該被加密
  2. 使用者登入時,輸入的是密碼,需要解析一下和資料庫裡面存的雜湊資料對比
  3. 使用者登入成功後,需要向客戶端傳送token,客戶端儲存下來
  4. 客戶端發起的每一個需要登入驗證的介面,都需要在請求裡面帶上token,後端解析驗證通過再做後面的業務邏輯
  • 先建立一個express的服務
require('dotenv').config(); // 使用env檔案
const express = require('express')
const bcrypt = require('bcryptjs') // 雜湊加密
const jwt = require('jsonwebtoken') // jwt加密token
const app = express()

const { User: UserModel } = require('./model')

app.use(express.json()) // 處理請求
app.use(require('cors')()) // 處理跨域

app.listen(5000, () => {
    console.log(`app is start at localhost:5000`)
})
複製程式碼

用一個簡單的model User來實現這個樣例

建立model.js檔案 匯出一個User Model

const mongoose = require('mongoose')

mongoose.connect(process.env.db, {
    useCreateIndex: true,
    useNewUrlParser: true
})

const UserSchema = new mongoose.Schema({
    name: {
        type: String,
        unique: true, // 不可重複
    },
    pwd: {
        type: String
    }
})

const User = mongoose.model('User', UserSchema)

module.exports = { User }
複製程式碼

註冊邏輯

註冊需要處理的事情就是將使用者密碼雜湊加密,存入資料庫中

brypt加密,即使是同一個字串,每次加密生成的密文都是不一樣的

bcrypt雜湊有兩種方式,一種同步的hashSync(需要加密的字串, 密碼強度),一種非同步的hash(需要加密的字串, 密碼強度, 回撥函式)

密碼強度不宜太高,太高了效能低。太低了,加密力度又不夠,一般8-12差不多。

app.post('/api/register', async (req, res) => {
    const { name, pwd } = req.body
    if (!name || !pwd) return res.send('缺少引數') 

    // 雜湊使用者密碼
    const bcryptPwd = bcrypt.hashSync(pwd, 10)

    const user = await UserModel.create({
        pwd: bcryptPwd,
        name,
    })

    if (!user) return
    res.send(user)
})
複製程式碼

登陸邏輯

登陸時需要用到brypt的compareSync(解密字串, 已經加密的對應字串)或者compare(解密字串, 已經加密的對應字串).then(res)來解密pwd,也只有這玩意兒能解密了。

如果解密通過了,需要呼叫jwt.sign生成token傳給前端,由前端儲存登陸憑據

sign接收一個目標物件和一個加密的secret:

  • 目標物件就是需要傳給客戶端的用於登陸驗證的資訊
  • secret則儲存在伺服器,加密解密都需要這個欄位,不應該被外界知曉
app.post('/api/login', async (req, res) => {
    const { name, pwd } = req.body
    const user = await UserModel.findOne({ name })
    if (!user)
        return res.status(422).send({ errmsg: '使用者不存在' })

    // 驗證密碼 compareSync
    const isPass = await bcrypt.compareSync(pwd, user.pwd)
    if (!isPass)
        return res.status(422).send({ errmsg: '密碼錯誤' })
    
    // 驗證通過 給客戶端返回token 以備後面驗證登陸

    // 生成token
    const token = jwt.sign({
        id: String(user._id),
        unUse: '隨便加的欄位,不用在意'
    }, process.env.SECRET)

    res.send({ 
        success: '登陸成功',
        token
    })
})
複製程式碼

驗證登陸

首先,我們肯定有很多介面都需要登陸驗證,那我們肯定不可能在每一個介面裡面都去處理token,更好的方法就是封裝一個函式用於處理這個token。

那麼express提供了一個很好的解決方案:中介軟體

建立好中介軟體之後,之後每一個需要驗證許可權的介面,都使用這個中介軟體即可

增加一個許可權驗證中介軟體:

  • jwt一般來說都放在header裡面的Authorization,在後端只需要在headers欄位中拿到token
  • 拿到token之後使用verify(token, seceret)解密
  • 查詢使用者
  • 找到了就進行下一步,沒找到就返回錯誤
const authMiddleWare = async (req, res, next) => {
    const { authorization } = req.headers
    const token = String(authorization).split(' ')[1]

    if (!token) return  res.send({ errmsg: 'token已過期' })
    // 解析token
    const { id } = jwt.verify(token, process.env.SECRET)

    const user = await UserModel.findById(id)

    if (user) {
        req.user = user
        next()
    } else {
        res.send({
            errmsg: 'token已過期'
        })
    }
}
複製程式碼

使用這個中介軟體

// 獲取個人資訊
app.get('/api/getUserInfo', authMiddleWare, async (req, res) => {
    res.send(req.user)
})
複製程式碼

思路清晰,步驟簡單。當然這裡只是記錄jwt,bcrypt的使用,很多容錯處理就沒有省略了,在實際的業務中肯定還是得加上的。另外,.env這個檔案在實際專案中肯定也得忽略上傳的,這應該是一個私有檔案。

相關文章