koa-unless增加method限制
没事自己撸了个web项目,后端用的是koa2,并使用了koa-jwt进行鉴权,在使用的时候遇到了些小问题。
一、问题
用koa-jwt鉴权的时候,可以通过设置unless路径,使得某些api不用经过鉴权,比如登录接口:
app.use(jwtKoa({secret}).unless({
path: [/^/api\/login/]
}));
当然还有些比如获取文章内容接口,我需要在未登录时显示文章,修改和删除是需要鉴权,但由于使用了restful api设计,获取文章和删除文章的api路径是一样的,使用前面的这种方式无法区分get与delete请求,导致鉴权没有任何意义了。
看了下koa-jwt文档上,并没有我需要的,只能看下代码咯,毕竟,比文档更全面的就只有代码了。
二、处理问题
查看koa-jwt代码,发现unless匹配与验证这块是使用的koa-unless,在package.json中也可以看到:
既然这样,我们就去查看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函数是否匹配的地方,当能够匹配上时,则可以继续执行后面的函数。
function matchesPath(requestedUrl, opts) {
// ...
}
matchesPath,该方法用于判断当前请求的url是否符合unless中的路径。
function matchesMethod(method, opts) {
// ...
}
该方法用于判断当前请求方式是否在unless的允许范围内。
我们发现,这几个match匹配函数之间都是或运算符,因此只要任何一者满足,if就可以通过,所以,url与method之间并没有关系,因此,并没有我想要的功能。
小改以下
既然用||没有关联,改成&&是不是就可以了呢? 依旧不行,因为解析规则并没有改变,我没法给每个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 (typeof p === 'string' && p === path) ||
(p instanceof RegExp && !!p.exec(path));
});
}
对应method中没有url,则直接false,当对应method下是空数组,则是所有url都ok。其他情况下,则需要依次遍历method下的url是否匹配。
文件下载地址:koa-unless。