前段時間,本身擼了個web項目,後端用的是koa2,並使用了koa-jwt進行鑑權,在使用的時候遇到了些小問題。前端
用koa-jwt鑑權的時候,能夠經過設置unless路徑,使得某些api不用通過鑑權,好比登陸接口:git
app.use(jwtKoa({secret}).unless({
path: [/^\/api\/login/]
}));
複製代碼
固然還有些好比獲取文章內容接口,我須要在未登陸時顯示文章,修改和刪除是須要鑑權,但因爲使用了restful api設計,獲取文章和刪除文章的api路徑是同樣的,使用前面的這種方式沒法區分get與delete請求,致使鑑權沒有任何意義了。github
看了下koa-jwt文檔上,並無我須要的,只能看下代碼咯,畢竟,比文檔更全面的就只有代碼了。web
查看koa-jwt代碼,發現unless匹配與驗證這塊是使用的koa-unless,在package.json中也能夠看到: json
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。因此得大改一下了。
我想要的功能是這樣的,
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/…
歡迎關注微信公衆號[ 我不會前端 ]或掃描下方二維碼!