koa2使用jwt鉴权

2022-04-25 19:22:40 浏览数 (2)

安装

koa-jwt基于jsonwebtoken进行封装。

代码语言:javascript复制
yarn add jsonwebtoken koa-jwt

使用

app.js
代码语言:javascript复制
const koajwt = require('koa-jwt');

// 错误处理
app.use((ctx, next) => {
  return next().catch((err) => {
    if (err.status === 401) {
      ctx.status = 401;
      ctx.body = 'use authorization to get accessn';
    } else {
      throw err;
    }
  })
})

app.use(koajwt({
  secret: 'key'
}).unless({
  path: [//user/login/]
}));
user.js
代码语言:javascript复制
require('../db/connection');
const request = require('request-promise');
const jwt = require('jsonwebtoken');
const User = require('../schemas/user');
const appid = require('../config').appid;
const secret = require('../config').secret;
const jwtKey = require('../config').jwtKey;

class UserController {
  async login(ctx, next) {
    const data = ctx.request.body;

    if (!data.username || !data.password) {
      return ctx.body = {
        code: '0',
        data: null,
        msg: 'invalid parameter'
      }
    }

    const result = await User.findOne({
      username: data.username,
      password: data.password
    })

    if (result !== null) {
      const token = jwt.sign({
        _id: result._id
      }, jwtKey, { expiresIn: '2h' });
      return ctx.body = {
        code: '1',
        data: token,
        msg: 'success'
      }
    } else {
      return ctx.body = {
        code: '0',
        data: null,
        msg: 'invalid username or password'
      }
    }
  }
}

module.exports = new UserController();
router
代码语言:javascript复制
const router = require('koa-router')();
const userctrl = require('../controllers/user');

router.post('/user/login', userctrl.login);

module.exports = router;
未认证
获取token
已认证

扩展

至此已经实现了基础鉴权,但是由于这里使用了restful api,获取资源的操作与修改、删除操作使用的是相同的路径,而koa-unless只支持单一的url匹配或者method匹配,两者无法混用,于是需要改写koa-unless:

index.js
代码语言:javascript复制
/**
 * Koa unless middleware. Attach to any middleware and configure it to prevent/permit the
 * middleware in question to be executed.
 *
 * @module koa-unless
 */

'use strict';
var url = require('url');
// 请求方式
var methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'TRACE', 'CONNECT'];
// 存储 method: [url1, url2 ...]
var map = {};

/** Creates a wrapper middleware that verifies if the original middleware should be skipped. */
module.exports = function (options) {
  var originalMiddleware = this;

  // If a custom function was passed directly, creates a new object literal that holds it as a property called custom.
  var opts = typeof options === 'function' ? { custom: options } : options;
  opts.useOriginalUrl = (typeof opts.useOriginalUrl === 'undefined') ? true : opts.useOriginalUrl;

  filterUnless(map, options);
  // console.log('map', map);

  // Returns the middleware that wraps the original one.
  return function (ctx, next) {
    var requestedUrl = url.parse((opts.useOriginalUrl ? ctx.originalUrl : ctx.url) || '', true);

    // any match means 'skip original middleware'
    if (matchesCustom(ctx, opts) || matchesPathAndMethod(requestedUrl, ctx.method, opts) || 
      matchesExtension(requestedUrl, opts)) {
      return next();
    }

    return originalMiddleware(ctx, next);
  };
};

/**
 * Returns boolean indicating whether the custom function returns true.
 *
 * @param ctx - Koa context
 * @param opts - unless configuration
 * @returns {boolean}
 */
function matchesCustom(ctx, opts) {
  if (opts.custom) {
    return opts.custom(ctx);
  }
  return false;
}

/**
 * Returns boolean indicating whether the requestUrl matches against the paths configured.
 *
 * @param requestedUrl - url requested by user
 * @param opts - unless configuration
 * @returns {boolean}
 */
function matchesPath(requestedUrl, opts) {
  var paths = !opts.path || Array.isArray(opts.path) ? 
    (Object.prototype.toString.call(opts.path[0]) === '[object Object]' ? getParams(opts.path, 'url') : opts.path) : 
    [opts.path];

  if (paths) {
    return paths.some(function (p) {
        return (typeof p === 'string' && p === requestedUrl.pathname) ||
          (p instanceof RegExp && !!p.exec(requestedUrl.pathname));
      });
  }

  return false;
}

/**
 * Returns boolean indicating whether the requestUrl ends with the configured extensions.
 *
 * @param requestedUrl - url requested by user
 * @param opts - unless configuration
 * @returns {boolean}
 */
function matchesExtension(requestedUrl, opts) {
  var exts = !opts.ext || Array.isArray(opts.ext) ?
    opts.ext : [opts.ext];

  if (exts) {
    return exts.some(function(ext) {
      return requestedUrl.pathname.substr(ext.length * -1) === ext;
    });
  }
}

/**
 * Returns boolean indicating whether the request method matches the configured methods.
 *
 * @param method - method used (GET, POST, ...)
 * @param opts - unless configuration
 * @returns {boolean}
 */
function matchesMethod(method, opts) {
  var methods = !opts.method || Array.isArray(opts.method) ?
    (Object.prototype.toString.call(opts.path[0]) === '[object Object]' ? getParams(opts.path, 'method') : opts.method) : 
    [opts.method];

  if (methods) {
    return !!~methods.indexOf(method);
  }
}
/**
 * 处理当前请求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));
  });
}

/**
 * 将用户写的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) => {
      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);
  });
}

将index.js拷贝至node_modules下koa-unless文件夹即可。

使用
代码语言:javascript复制
app.use(jwtKoa({secret}).unless({
    method: ['POST'],
    path: [/^/api/login/,
        {url: /^/api/publicKey/, method: ['GET']}]
}));

以上method表示总的支持的请求方式,path下是具体路径,path中路径跟随的method的优先级更高,所以以上login是post请求,publicKey是get请求。若与path同级的method不写,则所有method都支持。具体路径内的method不写,则跟随外层method。

0 人点赞