UNPKG

cjwt

Version:
291 lines (247 loc) 7.52 kB
const util = require('util'); const uuid = require('uuid/v4'); const debug = require('debug')('cjwt'); const { decode, verify, sign } = require('jsonwebtoken'); const expressJwt = require('express-jwt'); const verifyP = promisify(verify); const signP = promisify(sign); /** * @module CJWT * @class {CJWT} */ module.exports = class CJWT { /** * Casstime JWT * @constructor * @param {Object} options * @param {String|Function} options.secret jwtSecretCode或取得方式,需要返回Promise * @param {Object} options.signOptions jwt签名选项,参考https://github.com/auth0/node-jsonwebtoken * @param {Object} options.verifyOptions jwt校验选项,参考https://github.com/auth0/node-jsonwebtoken */ constructor(options = {}) { const secret = options.secret; const { signOptions, verifyOptions } = options; if (!secret) { throw new Error('options 必须指定 secret'); } if (!util.isObject(signOptions)) { throw new Error('必须指定 signOptions'); } if (!util.isObject(verifyOptions)) { throw new Error('必须指定 verifyOptions'); } if (!util.isFunction(secret) && !util.isString(secret)) { throw new Error('secret 必须为Function或者String类型'); } let secretCallback = secret; if (util.isString(secret)) { secretCallback = () => Promise.resolve(secret); } this._options = Object.assign({}, options, { secret: secretCallback }); this.decode = decode; this.buildSubject = buildSubject; this.parseSubject = parseSubject; } /** * 结合options.signOptions签名 * @param {String} type 签名身份的类型,可以是个人用户或服务 * @param {String} id 签名身份的id * @param {Object} ctx 本次请求的上下文,比如微服务可以已用户的ctx来请求数据 * @return {Promise.<String>} */ sign(type, id, ctx = {}) { const subject = buildSubject(type, id); const jwtid = uuid(); const { secret, signOptions } = this._options; const finalOptions = Object.assign({ subject, jwtid }, signOptions); const payload = Object.assign({ ctx }, getPayloadByOptions(finalOptions)); return secret(payload) .then((secretCode) => { return signP({ ctx }, secretCode, finalOptions); }); } /** * 结合verifyOptions校验jwt * @param {String} jwtString jwt字符串 * @param {Object} [options] 附加选项,参考https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback * @return {Promise.<Object>} 如果正确,返回payload */ verify(jwtString, options) { const payload = decode(jwtString); const { secret, verifyOptions } = this._options; const finalOptions = Object.assign({}, verifyOptions, options); return secret(payload) .then((secretCode) => { return verifyP(jwtString, secretCode, finalOptions); }); } /** * 结合verifyOptions生成express中间件 * @param {Object} [options] 参考https://github.com/auth0/express-jwt * @return {Function} */ middleware(options) { const { secret, verifyOptions } = this._options; const defaultOptions = { getToken, requestProperty: 'jwtPayload', credentialsRequired: false, maxAge: '7d' }; const secretCallback = (req, payload, done) => { try { secret(payload).then(done.bind(null, null)); } catch (e) { done(e); } }; const finalOptions = Object.assign({}, defaultOptions, verifyOptions, { secret: secretCallback }, options); const verifyFn = this.verify.bind(this); const signFn = this.sign.bind(this); return createJwtMiddleware(finalOptions, { verify: verifyFn, sign: signFn, decode, parseSubject, buildSubject}); } }; /** * 将函数转换成promise,内部使用,简单处理 * @param fn * @return {function(...[*])} */ function promisify(fn) { return (...args) => { return new Promise((resolve, reject) => { const callback = (error, result) => { error ? reject(error) : resolve(result); }; fn(...args, callback); }); } } /** * 从express request取jwt字符串,优先从headers.authorization取,如果没有,从cookies.jwt取 * @param req * @return {*} */ function getToken(req) { // 优先从头部取 if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { return req.headers.authorization.split(' ')[1]; } // 然后从cookies中取 const jwt = req.cookies['jwt'] || req.cookies['JWT']; if (jwt) { return req.cookies['jwt']; } return null; } /** * 解析subject,所有subject包含两部分内容,第一为身份类型,第二为身份标识 * @param {String} sub * @return {Object} */ function parseSubject(sub = '') { // 兼容原有格式 const results = /^casstime:\/\/(.+?)\/(.+?)$/.exec(sub); if (results) { return { type: results[1], id: results[2] } } // 现用冒号分割类型和标识 if (sub.indexOf(':') > -1) { const [ type, id ] = sub.split(':'); return { type, id }; } return {}; } /** * 构建subject字符串 * @param {String} type 身份类型 * @param {String} id 身份ID * @return {string} */ function buildSubject(type, id) { return `${type}:${id}`; } /** * 生成express中间件 * @param {Object} options 参照https://github.com/auth0/express-jwt * @param {Object} exposedFns 需要暴露出的函数 * @return {function(*=, *=, *)} */ function createJwtMiddleware(options, exposedFns) { return (req, res, next) => { const callback = (error) => { if (error) { debug('jwt校验失败 %s', error.message); req.jwtPayload = null; } if (req.jwtPayload) { const jwtPayload = req.jwtPayload; const { sub } = req.jwtPayload; if (jwtPayload.from) { jwtPayload._from = jwtPayload.from; } jwtPayload.from = parseSubject(sub); const getTokenCallback = getToken.bind(null, req); req.cjwt = createProxy(req.jwtPayload, Object.assign({}, exposedFns, { getToken: getTokenCallback })); } process.nextTick(() => next()); }; expressJwt(options)(req, res, callback); } } function createProxy(jwtPayload, exposedFns) { const keyAlias = { jwtid: 'jti', expires: 'exp', issuer: 'iss', audience: 'aud', issuedAt: 'iat', notBefore: 'nbf' }; return new Proxy(jwtPayload, { get(target, key) { const actualKey = keyAlias[key] || key; if (target[actualKey]) { return target[actualKey]; } if (exposedFns[actualKey]) { return exposedFns[actualKey]; } const subject = parseSubject(jwtPayload.sub); if (actualKey === 'subject') { return subject; } if (actualKey === 'isService') { return subject.type === 'service'; } if (actualKey === 'subjectType') { return subject.type; } return undefined; }, set(target, key, value) { console.error('cjwt对象只读'); } }); } function getPayloadByOptions(options) { const map = { audience: 'aud', issuer: 'iss', jwtid: 'jti', subject: 'sub' }; const result = {}; Object.getOwnPropertyNames(options) .filter(name => map[name]) .forEach((name) => { const key = map[name]; result[key] = options[name]; }); return result; }