没事自己撸了个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,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。
原文链接:https://www.f2er.com/nodejs/454294.html