本身動手改造開源庫

前段時間,本身擼了個web項目,後端用的是koa2,並使用了koa-jwt進行鑑權,在使用的時候遇到了些小問題。前端

1、問題

用koa-jwt鑑權的時候,能夠經過設置unless路徑,使得某些api不用通過鑑權,好比登陸接口:git

app.use(jwtKoa({secret}).unless({
    path: [/^\/api\/login/]
}));
複製代碼

固然還有些好比獲取文章內容接口,我須要在未登陸時顯示文章,修改和刪除是須要鑑權,但因爲使用了restful api設計,獲取文章和刪除文章的api路徑是同樣的,使用前面的這種方式沒法區分get與delete請求,致使鑑權沒有任何意義了。github

看了下koa-jwt文檔上,並無我須要的,只能看下代碼咯,畢竟,比文檔更全面的就只有代碼了。web

2、處理問題

查看koa-jwt代碼,發現unless匹配與驗證這塊是使用的koa-unless,在package.json中也能夠看到: json

png
既然這樣,咱們就去查看koa-unless的文檔,只能找到單一的url匹配和method匹配,並無二者結合的判斷方式。沒辦法,只能去看koa-unless的源碼了。

koa-unless

koa-unless目錄比較簡單,源碼就一個index.js,細看代碼,後端

if (matchesCustom(ctx, opts) || matchesPath(requestedUrl, opts) ||
    matchesExtension(requestedUrl, opts) ||matchesMethod(ctx.method, opts)) {
    return next();
}
複製代碼

焦點到這個if判斷上,此處就是在處理http請求的數據與unless函數是否匹配的地方,當可以匹配上時,則能夠繼續執行後面的函數。api

function matchesPath(requestedUrl, opts) {
    // ...
}
複製代碼

matchesPath,該方法用於判斷當前請求的url是否符合unless中的路徑。數組

function matchesMethod(method, opts) {
    // ...
}
複製代碼

該方法用於判斷當前請求方式是否在unless的容許範圍內。緩存

咱們發現,這幾個match匹配函數之間都是或運算符,所以只要任何一者知足,if就能夠經過,因此,url與method之間並無關係,所以,並無我想要的功能。bash

小改如下

既然用||沒有關聯,改爲&&是否是就能夠了呢?

依舊不行,由於解析規則並無改變,我無法給每一個url增長獨立的method。因此得大改一下了。

3、修改組件

我想要的功能是這樣的,

app.use(jwtKoa({secret}).unless({
    method: ['POST'],
    path: [/^\/api\/login/,
        {url: /^\/api\/publicKey/, method: ['GET']}]
}));
複製代碼

path中能夠像原先同樣直接些路徑的正則或者字符串,若是這麼寫了,他的method就由外側與path同級的method控制(不寫這個method,默認全部方法)。固然也能夠在path中設置對象的方式規定url和method,這種方式中的method優先級更高。

考慮到路由多的狀況下,在初始化的時候,將unless中的url與method解析到一個空對象之中,並以method爲key值,而容許的路由則放在key對應的value數組中。這樣,當請求的時候就不用整個unless遍歷了。

// 請求方式
var methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'TRACE', 'CONNECT'];
// 存儲 method: [url1, url2 ...]
var map = {};
複製代碼

在初始化的時候,解析unless配置:

/**
 * 將用戶寫的unless配置轉到map數據中
 * @param {Object} map 須要存儲到的空對象
 * @param {Object} opts 填寫的unless配置
 */
function filterUnless(map, opts) {
    // 處理不寫外層method時,默認支持全部請求方式
    var mes = opts.method ? opts.method : methods;
    if (Array.isArray(opts.path)) {
        opts.path.forEach((item) => {
            var method = [],
                url='';
            if (Object.prototype.toString.call(item) === '[object Object]') {
                // path中的是對象的,則查找他的path和method
                url = item.url;
                method = item.method || mes;
            } else {
                // 單個字符串或正則
                url = item;
                method = mes;
            }
            // 記錄
            record(map, method, url);
        });
    } else if (Array.isArray(opts.method)) {
        // 沒有path,檢測下是否有method
        opts.method.forEach((met) => {
            // 當方法後的value爲空數組時,表示全部url均會符合
            map[ met.toLowerCase() ] = [];
        });
    }
}
// 將 key: ulr1記錄到map中
function record(map, method, url) {
    method.forEach((met) => {
        if (!map[ met.toLowerCase() ]) {
            // 無值時,須要先建立空數組
            map[ met.toLowerCase() ] = [];
        }
        map[ met.toLowerCase() ].push(url);
    });
}
複製代碼

既然想要url和method可以相互關聯,那麼彼此之間確定要有制約,那麼將這二者的判斷放到同個方法中。刪掉if判斷中的matchesPath()與matchesMethod()方法,增長matchesPathAndMethod()方法,參數是當前請求的url和請求方式method。

由於前面已經用map緩存了鍵值關係,因此後面的處理就會簡單多了。

/**
 * 處理當前請求url和method是否符合unless中的配置
 * @param {Object} requestedUrl 請求url相關信息
 * @param {String} method 請求方式
 */
function matchesPathAndMethod(requestedUrl, method) {
    var path = requestedUrl.pathname,
        mets = map[ method.toLowerCase() ];
    if (!mets) {
        // 沒這個方法
        return false;
    }
    if (!mets.length) {
        // 長度是0,證實全部請求均可以
        return true;
    }
    return mets.some(function (p) {
        return (typeofp==='string'&&p===path) ||
            (p instanceof RegExp && !!p.exec(path));
    });
}
複製代碼

對應method中沒有url,則直接false,當對應method下是空數組,則是全部url都ok。其餘狀況下,則須要依次遍歷method下的url是否匹配。

文件附件:koa-unless.js

原文連接:www.zhuyuntao.cn/2019/03/24/…

歡迎關注微信公衆號[ 我不會前端 ]或掃描下方二維碼!

png
相關文章
相關標籤/搜索