Koa - 中間件(理解中間件、實現一個驗證token中間件)

来源:https://www.cnblogs.com/chanwahfung/archive/2019/08/31/11427205.html
-Advertisement-
Play Games

前言 Koa 應用程式是一個包含一組中間件函數的對象,它是按照類似堆棧的方式組織和執行的。 當一個中間件調用 next() 則該函數暫停並將控制傳遞給定義的下一個中間件。當在下游沒有更多的中間件執行後,堆棧將展開並且每個中間件恢復執行其上游行為。 以上兩句話,是我在官方文檔中找到其對 Koa 中間件 ...


前言

Koa 應用程式是一個包含一組中間件函數的對象,它是按照類似堆棧的方式組織和執行的。

當一個中間件調用 next() 則該函數暫停並將控制傳遞給定義的下一個中間件。當在下游沒有更多的中間件執行後,堆棧將展開並且每個中間件恢復執行其上游行為。

以上兩句話,是我在官方文檔中找到其對 Koa 中間件的描述。

在Koa中,中間件是一個很有意思的設計,它處於request和response中間,被用來實現某種功能。像上篇文章所使用的 koa-router 、koa-bodyparser 等都是中間件。

可能有些人喜歡把中間件理解為插件,但我覺得它們兩者並不是同一種概念的東西。插件像是一個獨立的工具,而中間件更像是流水線,將加工好的材料繼續傳遞下一個流水線。所以中間件給我的感覺更靈活,可以像零件一樣自由組合。

單看中間件有堆棧執行順序的特點,兩者就出現質的區別。

中間件的概念

這張圖是 Koa 中間件執行順序的圖示,被稱為“洋蔥模型”。

中間件按照棧結構的方式來執行,有“先進後出“的特點。

 

一段簡單的代碼來理解上圖:

app.use(async (ctx, next)=
   console.log('--> 1')
   next()
   console.log('<-- 1')
})

app.use(async (ctx, next)=>{
   console.log('--> 2')
   //這裡有一段非同步操作
   await  new Promise((resolve)=>{
       ....
   })
   await  next()
   console.log('<-- 2')
})

app.use(async (ctx, next)=>{
   console.log('--> 3')
   next()
   console.log('<-- 3')
})  
app.use(async (ctx, next)=>{
   console.log('--> 4')
}) 

當我們運行這段代碼時,得到以下結果

--> 1

--> 2

--> 3

--> 4

<-- 3

<-- 2

<-- 1

 

 中間件通過調用 next 一層層執行下去,直到沒有執行權可以繼續傳遞後,在以冒泡的形式原路返回,並執行 next 函數之後的行為。可以看到 1 第一個進去,卻是最後一個出來,也體現出中間件棧執行順序的特點。

在第二個中間件有一段非同步操作,所以要加上await,讓執行順序按照預期去進行,否則可能會出現一些小問題。

 

中間件的使用方式

1.應用中間件

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();
app.use(async (ctx,next)=>{
    console.log(new Date());
    await next();
})
router.get('/', function (ctx, next) {
    ctx.body="Hello koa";
})
router.get('/news',(ctx,next)=>{
    ctx.body="新聞頁面"
});
app.use(router.routes()); //作用:啟動路由
app.use(router.allowedMethods()); //作用: 當請求出錯時的處理邏輯
app.listen(3000,()=>{
    console.log('starting at port 3000');
});

 

2.路由中間件

router.get('/', async(ctx, next)=>{
    console.log(1)
    next()
})
router.get('/', function (ctx) {
    ctx.body="Hello koa";
})

 

3.錯誤處理中間件

app.use(async (ctx,next)=> {
    next();
    if(ctx.status==404){
        ctx.status = 404;
        ctx.body="這是一個404頁面"
    }
});

 

4.第三方中間件

const bodyParser = require('koa-bodyparser');
app.use(bodyParser());

 

實現驗證token中間件

實現一個基於 jsonwebtoken 驗證token的中間件,這個中間件由兩個文件組成 extractors.js 、index.js,並放到check-jwt文件夾下。

生成token

const Router = require('koa-router')
const route = new Router()
const jwt = require('jsonwebtoken')

route.get('/getToken', async (ctx)=>{
    let {name,id} = ctx.query
    if(!name && !id){
        ctx.body = {
            msg:'不合法',
            code:0
        }
        return
    }
    //生成token
    let token = jwt.sign({name,id},'secret',{ expiresIn: '1h' })
    ctx.body = {
        token: token,
        code:1
    }
})

module.exports = route

使用 jwt.sign 生成token:

第一個參數為token中攜帶的信息;

第二個參數為key標識(解密時需要傳入該標識);

第三個為可選配置選項,這裡我設置過期時間為一小時;

詳細用法可以到npm上查看。

 

使用中間件

app.js:

const {checkJwt,extractors} = require('./check-jwt')

app.use(checkJwt({
  jwtFromRequest: extractors.fromBodyField('token'),
 secretOrKeyL: 'secret', safetyRoutes: [
'/user/getToken'] }))
  是否必選 接收類型 備註
jwtFromRequest 函數 預設驗證 header 的 authorization extractors提供的提取函數,支持get、post、header方式提取 這些函數都接收一個字元串參數(需要提取的key) 對應函數: fromUrlQueryParameter、 fromBodyField、 fromHeader  
secretOrKey 字元串 與生成token時傳入的標識保持一致
safetyRoutes 數組 不需要驗證的路由

 

                   

 

使用該中間件後,會對每個路由都進行驗證

 

路由中獲取token解密的信息

route.get('/getUser', async ctx=>{
    let {name, id} = ctx.payload 
    ctx.body = {
        id,
        name,
        code:1
    }
})

通過ctx.payload來獲取解密的信息

 

實現代碼

extractors.js 工具函數(用於提取token)

let extractors = {}

extractors.fromHeader = function(header_name='authorization'){
    return function(ctx){
        let token   = null,
            request = ctx.request;
        if (request.header[header_name]) {
            token = header_name === 'authorization' ? 
            request.header[header_name].replace('Bearer ', '') :
            request.header[header_name];
        }else{
            ctx.body = {
                msg: `${header_name} 不合法`,
                code: 0
            }
        }
        return token;
    }
}

extractors.fromUrlQueryParameter = function(param_name){
    return function(ctx){
        let token   = null,
            request = ctx.request;
        if (request.query[param_name] && Object.prototype.hasOwnProperty.call(request.query, param_name)) {
            token = request.query[param_name];
        }else{
            ctx.body = {
                msg: `${param_name} 不合法`,
                code: 0
            }
        }
        return token;
    }
}

extractors.fromBodyField = function(field_name){
    return function(ctx){
        let token   = null,
            request = ctx.request;
        if (request.body[field_name] && Object.prototype.hasOwnProperty.call(request.body, field_name)) {
            token = request.body[field_name];
        }else{
            ctx.body = {
                msg: `${field_name} 不合法`,
                code: 0
            }
        }
        return token;
    }
}

module.exports = extractors

index.js  驗證token

const jwt = require('jsonwebtoken')
const extractors = require('./extractors')

/**
 * 
 * @param {object} options 
 *    @param {function} jwtFromRequest
 *    @param {array} safetyRoutes
 *    @param {string} secretOrKey 
 */

function checkJwt({jwtFromRequest,safetyRoutes,secretOrKey}={}){
    return async function(ctx,next){
        if(typeof safetyRoutes !== 'undefined'){
            let url = ctx.request.url
            //對安全的路由 不驗證token
            if(Array.isArray(safetyRoutes)){
                for (let i = 0, len = safetyRoutes.length; i < len; i++) {
                    let route = safetyRoutes[i],
                        reg = new RegExp(`^${route}`);
//若匹配到當前路由 則直接跳過 不開啟驗證 if(reg.test(url)){ return await next() } } }else{ throw new TypeError('safetyRoute 接收類型為數組') } } if(typeof secretOrKey === 'undefined'){ throw new Error('secretOrKey 為空') } if(typeof jwtFromRequest === 'undefined'){ jwtFromRequest = extractors.fromHeader() } let token = jwtFromRequest(ctx) if(token){ //token驗證 let err = await new Promise(resolve=>{ jwt.verify(token, secretOrKey,function(err,payload){ if(!err){ //將token解碼後的內容 添加到上下文 ctx.payload = payload } resolve(err) }) }) if(err){ ctx.body = { msg: err.message === 'jwt expired' ? 'token 過期' : 'token 出錯', err, code:0 } return } await next() } } } module.exports = { checkJwt, extractors }

 Demo: https://gitee.com/ChanWahFung/koa-demo

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 網站是分為網站的前臺和網站的後臺. 前臺--給用戶看的 例如:商城 後臺--給管理員看的 例如:商城後臺 目的:用來添加維護數據 BootStrap:jsp 頁面顯示,效果好,美觀,適合作為用戶界面. EasyUI : jsp頁面,快速開發,格式統一,美觀效果一般. EasyUI裡面有很多組件(功能... ...
  • 一. "Vue的介紹及安裝和導入" 二. "Vue的使用" 三. "Vue成員獲取" 四. "Vue中的迴圈以及修改差值表達式" 五. "vue中methods,computed,filters,watch的總結" 六. "Vue中組件" 七. "Vue中插槽指令" 八. "Vue部分編譯不生效,解 ...
  • 0901自我總結 Vue CLI項目路由案例彙總 router.js components/Nav.vue views/Course.vue components/CourseCard.vue vue <! 邏輯跳轉 {{ card.title }} <! 鏈接跳轉 <! 第一種 <! <route ...
  • 身份互聯網和物聯網之間有什麼區別? 顧名思義,物聯網是關於物的——設備、控制器、致動器等等。但這些東西會執行任務、收集數據、連接到其他設備。換句話說, 每個設備都會有帶多重屬性的一個身份,而這些屬性必須得到良好的理解,才可以驅動好事發生,而阻止壞事降臨。 因此, 身份互聯網IOI (Internet ...
  • 盒子模型:(重點) 盒子模型(CSS框模型)規定了元素框處理元素內容、內邊距、邊框、外邊框等樣式 記住上面這一張圖!一定要記住!一定!一定! 內邊距、邊框和外邊距是可選參數屬性,預設值:0 ;很多元素由用戶代理樣式表設置外邊距和邊框,通過將元素的 margin外邊距和padding內邊距設置為 0 ...
  • jQuery介紹 在說jQuery之前,先說一個概念吧,什麼是JavaScript框架庫,其實就是一個普通的js文件,裡面封裝了很多函數或者說封裝了很多相容的代碼;當然啦,jQuery就是眾多庫的一員,那麼我們為什麼要學習jQuery呢,那就講一下他的特點; 1. 很好的解決了不同瀏覽器之間的相容性 ...
  • 1.判斷輸入值的長度 1.1 根據輸入值的類型不同,限制輸入值長度不同 此時需要使用自定義的校驗規則。 如長度要求:中文輸入5位,非中文10位 1 <FormItem label="名稱" {...formItemLayout}> 2 {getFieldDecorator('name', { 3 r ...
  • Node.js是一個基於 Chrome V8 引擎的 JavaScript 運行環境;Node.js使用一個事件驅動、非阻塞式 I/O 的模型,使其輕量且高效;Node.js的軟體包生態系統npm是全球最大的開源庫生態系統。本文詳細介紹了Node.js的安裝、配置及測試教程,希望對您有所幫助。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...