Node教程——封裝一個token驗證器

BM老李發表於2020-05-11

重要說明

這個輪子是 使用 express@5.0 + MongoDB構建起來的一個 node後臺通用的驗證器,裡面主要講的就是使用jwt,token進行驗證,當然你想使用session也沒問題,但是這個藍圖工程只包含了token欄位內容

首先是初始化我們的專案,

主要是 安裝一些東西

  1. 專案的初始化

如下是我們的專案資料夾結構

![img](file:///C:\Users\ADMINI~1\AppData\Local\Temp\1589190039(1).jpg)

  1. 專案的包管理

首先我們需要使用express@next框架,因為只用next才能在裡面使用async es7的一些東西,
我們還需要mongoose來運算元據庫
我們還需要bcrypt對資料庫裡面的密碼進行加密
我們還需要jsonwebtoken快捷的生成token

npm install express@next
npm install mongoose
npm install bcrypt
npm install jsonwebtoken

好了以上就是我們需要做的

設計路由邏輯

首先我們需要在這裡設計幾個介面,他們是,並且完成post請求的配置解析json

  • 測試介面
  • 註冊介面
  • 登入介面
  • 獲取所有使用者資訊介面
  • 等錄之後的許可權校驗介面
  1. 構建入口,並且完成json的解析
    /app.js

const express = require('express');

const app = express();


//解析一遍post引數
app.use(express.urlencoded({ extended: true }))
app.use(express.json())


//路由分發器
app.get('/test', async(req, res) => {
    res.send('測試打通!沒問題')
})


app.use('/api', require('./route/index'))


app.listen(3001, async() => {
    console.log('http://localhost:3001');
})
  1. 構建分路由

注意這裡只是簡單的做一些功能測試,後續我們會把這個東西都刪掉,把驗證丟給router去驗證,目的就是模組化處理業務,請求主頁不需要token請求管理的頁的介面就需要驗證

/router/index.js

const express = require('express');

const indexApi = express.Router()


indexApi.post('/register', async(req, res) => {
    console.log(req.body);
    res.send('register!!ok')
})


indexApi.post('/login', async(req, res) => {
    console.log(req.body);
    res.send('login!!ok')
})


indexApi.get('/users', async(req, res) => {
    res.send('users!!ok')
})


indexApi.get('/profile', async(req, res) => {
    res.send('profile!!ok')
})




  1. 完成對應的介面測試
    /.http

@uri = http://localhost:3001/api


### 測試
GET {{uri}}

### 展示出所有的使用者
GET  {{uri}}/users



###  註冊
POST {{uri}}/register
Content-Type: application/json

{
    "username":"user2",
    "password":"123456"
}

### 登入
POST {{uri}}/login
Content-Type: application/json

{
    "username":"user2",
    "password":"123456"
}


### 獲取個人資訊,傳遞的是當前保持狀態了的使用者
GET {{uri}}/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlYjhhNDIzOTM4YzdhNmQ3NDg5ZDJlMyIsImlhdCI6MTU4OTE2MDY0Nn0.UeSGbDgUrQaThemD18iIAGW6t-lc8R_R5tDvFamrgDw


設計實現資料庫Model

在這裡我們需要完成的工作有:

  • 使用mongoose連上資料庫
  • 建立shcema規則
  • 使用規則建立集合
  • 倒出集合操作物件,建立集合交給理由或者中介軟體去做,這裡功能比較簡單我們可以直接丟給路由去做,但是為了保持程式設計的風格一致,我打算丟到中介軟體裡面去

我們把model都寫在一個檔案裡有點不太妥當,當然這樣做是完全沒有問題的,如果專案有良好的架構我們可以後期考慮把他們分類的去構建model,比如與使用者相關的molde都放在一個檔案裡,等等

/model/model.js

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');

mongoose.connect('mongodb://localhost:27017/express-auth', { useUnifiedTopology: true, useNewUrlParser: true, useCreateIndex: true });


const UserSchma = new mongoose.Schema({

    username: {
        type: String,
        unique: true //只需要usernam為唯一值
    },

    password: {
        type: String,
    }

})

const User = mongoose.model('User', UserSchma) //雖然這個表的名字是User但是實際上資料庫建立的時候會給你變成users

// 倒出資料操作物件
module.exports = { User }

注意啊,我們只是定義的集合還有資料庫的表(集合)操作物件,還米有拿去業務中使用,接下里的章節我們會拿到具體的業務裡去使用
好了我們階段性的回顧一下我們現在的資料夾裡面都有哪些東西吧

實現新增(實際上就是註冊)使用者介面

這裡我們定義就能實現我們的業務功能了,首先要說明的是,我們這裡依然使用標準化的專案開放方式,把功能寫在中介軟體裡,

這裡的定義的中介軟體處理一個專門的業務就是User相關的業務,這是我工作中編寫nodejs的全棧專案的一個習慣
/middleware/users.js

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

module.exports = {

    register: async(req, res, next) => {
        // console.log(req.body);
        let { username, password } = req.body

        const user = await User.create({
            username: username,
            password: password
        })
        req.user = user

        next()
    }
}

在路由裡面你只需要弄這個就好了。實際上中介軟體就是一個物件

const users = require('../middleware/users')
+++
indexApi.post('/register', users.register, (req, res) => {
    res.send(req.user)
})

+++

實現展示使用者功能

前面我們實現了使用者的註冊新增功能,那麼我們就來實現一些檢視所有使用者功能。
用了前面的架構模式,我們的這個業務就可以全部寫在middelwear裡了,如果有設計賦值的操作。我們還可以建立一個工具middlewear,來幫助我們實現複雜的功能,這就是模組化開發

/middleware/users.js

+++
 //檢視所有使用者
    showUser: async(req, res, next) => {

        //為什麼是find就可以了,因為mongoose給你封裝了
        const user = await User.find();

        req.user = user;
        next();

    }
+++


/router/index.js

const users = require('../middleware/users')
+++
indexApi.get('/users', users.showUser, async(req, res) => {
    res.send(req.user)
})
+++

實現bcrypt加密密碼

實際上實現bcrypt的加密非常的簡單,只需要呼叫方法就好了

+++
  password: {
        type: String,
        set(val) { //val是自定義的儲存前的加密,返回的值就是加密之後的密碼
            return require('bcrypt').hashSync(val, 10) //進行雜湊之後的密碼,10就是加密強度
        }
    }
+++

加密之後的密碼驗證怎麼做?實際上這裡就是登入功能

實際上也非常的簡單,先驗證使用者名稱是否正確 如果正確就去根據使用者名稱查使用者資料,然後拿到加密之後的密碼,然後使用bcrypt自己的驗證方式去驗證就好了

/middleware/users.js

+++
   //登入器
    login: async(req, res, next) => {

        let { username, password } = req.body

        const user = await User.findOne({
            username: username
        })

        //驗證使用者名稱
        if (!user) {
            return res.status(422).send({ message: '使用者名稱不存在' })
        }
        const isPasswordValid = bcrypt.compareSync(password,
            user.password
        )

        //驗證密碼
        if (!isPasswordValid) {
            return res.status(422).send({ message: '密碼無效' })
        }
        req.user = user
        next()
    }
+++

/router/index.js

indexApi.post('/login', users.login, async(req, res) => {
    res.send(req.user)
})



實現token的下發

實際上這個也非常的簡單,我們只需要在登入成功的時候給使用者加上一個token就好了

這裡的業務邏輯核心,其實就是這個token該如何加

  • 修改一下我們的登入功能,使得使用者登入的時候加上一個用以驗證使用者登入狀態的token
    /middleware/users.js
+++
 const jwt = require('jsonwebtoken')
+++

+++
 //登入器
    login: async(req, res, next) => {
    +++

        //生成token,jwtToken   
        //生成簽名,我們給id丟進去據好了
    const token = jwt.sign({
            //加密的簽名
            id: String(user._id),
            //金鑰
        }, 'asdasdasdasdasdasdasdasdasdasdasd') //這個東西實際上是一串祕鑰,用來對應每一個的tonken驗證器,它應該被寫一個單獨的檔案裡
    res.user = {
        user,
        token

    }
    +++
}
  • 我們看一些測試的結果
{
  "user": {
    "_id": "5eb933c9cf3c3f33fcadb560",
    "username": "user2",
    "password": "$2b$10$n2OHQzuSuUtwWpg.YuiDO.FPM4Q9nrBdqANLB3Wkh67P.MonpIyYi",
    "__v": 0
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlYjkzM2M5Y2YzYzNmMzNmY2FkYjU2MCIsImlhdCI6MTU4OTE5Nzc4MX0.a4vrQwTeGsuI320m1OYsjSB8abdxxm8TReKYg6UKbVQ"
}

實現使用者的token校驗.

這裡我們把auth做成一箇中介軟體,這個中介軟體可以載入需要驗證的路由的前面,如果通過驗證就放行,要不然就放行next,這就是驗證器auth的實現原理,非常的簡單
/middleware/auth.js


const jwt = require('jsonwebtoken')
const { User } = require('../model/model')
module.exports = {
    auth: async(req, res, next) => {
        //注意啊這個欄位是我們前端需要實現的,因為這是後臺要求的
        let raw = String(req.headers.authorization).split(' ').pop() //我為啥要用空格分隔,因為我發起請求的時候多加了一個欄位,

        const tokenData = jwt.verify(raw, 'asdasdasdasdasdasdasdasdasdasdasd')
        let { id } = tokenData

        
        //加到req上以便以給下一個中介軟體使用
        req.user = await User.findById(id)
        
        next()

    }
}

假設我們現在需要把這個auth用於我們的 profile介面做驗證,那麼我們可以這樣來使用

//核心token驗證器
indexApi.get('/profile', auth.auth, async(req, res) => {
    res.send(req.user)
})

注意,以上的所有都只是一個小小的demo。正式的打包再我這裡

整理好所有的目錄,打包構建成藍圖並且釋出上git

  • 我為什麼要整理成藍圖?因為我希望我的token驗證其能複用到很多地方去,假設我以後的專案需要用這個那麼我就直接下載藍圖,這樣我的token就不用我再去囉嗦的寫了,這也實際上已經是一個初步的node框架的雛形了。

我把它寫在了blueprint_for_token_v3中。你可以直接git clone去使用它構建你的node專案

優化專案結構打包,我做了那些事?(主要就是以下愛的事情)

  1. 優化目錄結構
  2. 整理介面 去掉了沒有用的介面,只保留了一些基礎的介面
  3. 使用說明:你只需要npm install 就能實現token的驗證了,需要驗證之後的介面請在admin之後的路由書寫,當然你可以自定義路由,拿著我的auth去做驗證就可以了
  4. 建議你使用非對稱加密的金鑰對,進行token的加密,你可以通過引入檔案去配置你的加密資訊

相關文章