1、组件概览

middleware

jwtAuthenticator

.

./lib/middleware/jwtAuthenticator

logger

./lib/middleware/logger

permissions

./lib/middleware/permissions

Authorizer

./lib/authorizer

util

./lib/util

logger

./lib/logger

auth

m2m

./lib/auth/m2m

verifier

./lib/auth/verifier

2、组件分析

2.1 verifier

对外提供的是一个函数,接收validIssuers, jwtKeyCacheTime

返回的是对象,包含了validateToken(函数类型)

validateToken定义

validateToken: (token, secret, callback) => {// Decode it firstlet decodedToken = jwt.decode(token, {complete: true})// Check if it's HS or RSif (decodedToken && decodedToken.header){if (decodedToken.header.alg === "RS256") {if (validIssuers.indexOf(decodedToken.payload.iss) === -1){callback(new Error('Invalid token issuer.'))} else {// Get the key id (kid)let kid = decodedToken.header.kid// Get the public cert for verification then verifyif (!jwksClients[decodedToken.payload.iss]){jwksClients[decodedToken.payload.iss] = jwksRSA({cache: true,cacheMaxEntries: 5, // Default valuecacheMaxAge: ms(jwtKeyCacheTime), // undefined/0 means infintejwksUri: decodedToken.payload.iss + '.well-known/jwks.json'})}jwksClients[decodedToken.payload.iss].getSigningKey(kid, (err, key) => {if (err) {callback(new Error('Invalid Token.' + err))} else {jwtVerify(token, key.publicKey, callback)}})}}if (decodedToken.header.alg === "HS256") {jwtVerify(token, secret, callback)}} else {callback(new Error('Invalid Token.'))}}}

首先解析token,在token头部算法为RS256时,验证token消息体中的iss是否一致。然后通过头部的kid得到签名key,解析成功,则调用jwtVerify校验。在算法为HS256时,直接调用jwtVerify校验

jwtVerify定义

function jwtVerify(token, secretOrCert, callback) {  // verifies secret and checks expjwt.verify(token, secretOrCert, (err, decoded) => {if (err) {callback(new Error('Failed to authenticate token.'))} else if (validIssuers.indexOf(decoded.iss) === -1) {// verify issuercallback(new Error('Invalid token issuer.'))} else {callback(undefined, decoded)}})}

2.2 m2m

对外提供的是一个函数,参数为config,返回的是一个对象{getMachineToken:function(clientId, clientSecret)=>Promise}.主要是token是否存在以及是否已经过期,如果已经过期,则重新获取token。

function (config) {let auth0Url = _.get(config, 'AUTH0_URL', '')let auth0Audience = _.get(config, 'AUTH0_AUDIENCE', '')let auth0ProxyServerUrl = _.get(config, 'AUTH0_PROXY_SERVER_URL', auth0Url)return {/*** Generate machine to machine token from Auth0* V3 API specification* @param  clientId  client Id provided from Auth0* @param  clientSecret  client secret provided from Auth0* @return  Promise  promise to pass responses*/getMachineToken: (clientId, clientSecret) => {var options = {url: auth0ProxyServerUrl,headers: { 'content-type': 'application/json' },body: {grant_type: 'client_credentials',client_id: clientId,client_secret: clientSecret,audience: auth0Audience,auth0_url: auth0Url},json: true}return new Promise(function (resolve, reject) {// We cached the token to cachedToken variable,// So we check the variable and the time here.let appCachedToken = cachedToken[clientId]let appCachedTokenExpired = false//Check the token expiry  if (appCachedToken) {if (getTokenExpiryTime(appCachedToken) <= 0) {appCachedTokenExpired = true}}if (!appCachedToken || appCachedTokenExpired ) {request.post(options, function (error, response, body) {if (error) {return reject(new Error(error))}if (body.access_token) {cachedToken[clientId] = body.access_tokenresolve(cachedToken[clientId])} else if (body.error && body.error_description) {reject(new Error(body.error + ': ' + body.error_description +' ;Please check your auth credential i.e. AUTH0_URL, AUTH0_CLIENT_ID,' +' AUTH0_CLIENT_SECRET, AUTH0_AUDIENCE, AUTH0_PROXY_SERVER_URL'))} else {reject(new Error(body))}})}else {resolve(appCachedToken)}})}}
}const getTokenExpiryTime = function (token) {let expiryTime = 0if (token) {let decodedToken = jwt.decode(token)let expiryTimeInMilliSeconds = (decodedToken.exp - 60) * 1000 - (new Date().getTime())expiryTime = Math.floor(expiryTimeInMilliSeconds / 1000)}return expiryTime
}

2.3 logger

对外接口形式为function(options) => logger

基于bunyan日志库来创建日志,options参数对象有{level, name, streams,captureLogs, logentriesToken},如果options中的captureLogs为true时,会基于r7insight_node库创建buyan流,来填充buyan选项中的streams参数。

module.exports = function(options) {var opts = {name: options.name,level: options.level,streams: options.streams || [{stream: process.stdout}]}// capture logs ?if (_.get(options, 'captureLogs', 'false').toLowerCase() === 'true') {var leStream = logentries.bunyanStream({ token: options.logentriesToken, region: 'us' })opts.streams.push(leStream)}var logger = bunyan.createLogger(opts)return logger
}

2.4 util

主要是提供三个功能:封装错误响应,正常响应以及通过axios创建http客户端

对外提供接口形式为function (config) => {

wrapErrorResponse:function(reqId, status, message)=>{

id:reqId,

version:version,

result:{

success:false,

status:status,

content:{

message:mesage

}

}

},

wrapResponse:function (reqId, content, totalCount, status, err) => {

id:reqId,

version:version:

result:{

success:!err,

status:code,

content:content,

metadata:content.length|1

}

},

getHttpClient:function(req) => axios实例

}

module.exports = function(config) {let version = _.get(config, 'version', 'v3')var _httpClient = nullreturn {/*** Wrap error responses according to* V3 API specification* @param  reqId  request Id* @param  status  status of http response* @param  message error message*/wrapErrorResponse: (reqId, status, message) => {return {id: reqId,version: version,result: {success: false,status: status,content: {message: message}}}},/*** Wrap success response* @param  string reqId  request Id* @param  content  response body* @param  totalCount metadata* @param  status  status of http response* @param  message error message*/wrapResponse: (reqId, content, totalCount, status, err) => {// determine statusvar codeif (err) {code = err.status} else if (status) {code = status} else {code = 200}var resp = {id: reqId,version: version,result: {success: !err,status: code,content: content}}if (!totalCount) {totalCount = _.isArray(content) ? content.length : 1}resp.result.metadata = {totalCount}return resp},/*** Returns a promise based http client* @param  {request} req request object, can be null* @return {object}     http client*/getHttpClient: (req) => {var httpClient = axios.create()if (req) {httpClient.defaults.headers = httpClient.defaults.headers || {common: {}}httpClient.defaults.headers.common['X-Request-Id'] = req.id}httpClient.defaults.timeout = 3000return httpClient}}
}

2.5 Authorizer

其类结构如下图所示,主要是can方法来判断是否可以执行。通过policies属性对象来判断,其key是action,value是rule,其类型可以是boolean|function。

class Authorizer {constructor() {this._policies = {}this._deniedStatusCode = null || 403}getDeniedStatusCode() {return this._deniedStatusCode}setDeniedStatusCode(statusCode) {this._deniedStatusCode = statusCode}setPolicy(action, rule) {if (_.isUndefined(action))throw new Error('Policy must have an action.')if (_.isUndefined(rule))throw new Error('Policy ' + action + 'must have a rule')this._policies[action] = rule}getRegisteredPolicies() {return _.keys(this._policies)}unsetPolicy(action) {delete this._policies[action]}can(req, action) {var self = thisreturn new Promise((resolve, reject) => {let policy = _.get(self._policies, action, undefined)if (_.isUndefined(policy)) {return reject(new Error('Policy for action \'' + action + '\' not defined'))}if (_.isBoolean(policy)) {let err = new Error('You don\'t have permission to perform this action')return policy ? resolve(true) : reject(err)}if (_.isFunction(policy)) {return policy(req).then(() => {return resolve(true)}).catch((err) => {if (!_.isError(err))err = new Error(err)return reject(err)})}return reject(new Error('Unknown policy type'))})}
}// returning a singleton
module.exports = exports = new Authorizer()

2.6 middleware.jwtAuthenticator

对外提供的接口形式为

function(options)=>funciton (req, res, next)

主要是校验token,如果成功,则设置请求中的authUser(解码后的token),authUser.userId,authUser.handle, authUser.roles, authUser.email, authUser.scopes, authUser.azpHash

module.exports = function (options) {// retrieve secret from optionslet secret = _.get(options, "AUTH_SECRET") || ''let validIssuers = JSON.parse(_.get(options, "VALID_ISSUERS") || '[]')let jwtKeyCacheTime = _.get(options, "JWT_KEY_CACHE_TIME", '24h');if (!secret || secret.length === 0) {throw new Error('Auth secret not provided')}if (!validIssuers || validIssuers.length === 0) {throw new Error('JWT Issuers not configured')}let verifier = authVerifier(validIssuers, jwtKeyCacheTime)return function (req, res, next) {// check headervar tokenif (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {token = req.headers.authorization.split(' ')[1]}// decode token//TODO get auth secret from KMSif (token) {verifier.validateToken(token, secret, (err, decoded) => {if (err) {res.status(403).json(util.wrapErrorResponse(req.id, 403, err.message))res.send()}else {// if everything is good, save to request for use in other routesreq.authUser = decodedreq.authUser.userId = _.parseInt(_.find(req.authUser, (value, key) => {return (key.indexOf('userId') !== -1)}))req.authUser.handle = _.find(req.authUser, (value, key) => {return (key.indexOf('handle') !== -1)})req.authUser.roles = _.find(req.authUser, (value, key) => {return (key.indexOf('roles') !== -1)})if (!req.authUser.email) {req.authUser.email = _.find(req.authUser, (value, key) => {return (key.indexOf('email') !== -1)})}let scopes = _.find(req.authUser, (value, key) => {return (key.indexOf('scope') !== -1)})if (scopes) {req.authUser.scopes = scopes.split(' ')let grantType = _.find(decoded, (value, key) => {return (key.indexOf('gty') !== -1)})if (grantType === 'client-credentials' &&!req.authUser.userId &&!req.authUser.roles) {req.authUser.isMachine = truereq.authUser.azpHash = getAzpHash(req.authUser.azp)}}next()}})} else {// if there is no token// return an errorres.status(403).json(util.wrapErrorResponse(req.id, 403, 'No token provided.'))res.send()}}
}function getAzpHash(azp) {if (!azp || azp.length === 0) {throw new Error('AZP not provided.')}// default offset value  let azphash = 100000for (let i = 0; i < azp.length; i++) {let v = azp.charCodeAt(i)azphash += v * (i + 1)}return azphash * (-1)
}

2.7 middleware.logger

对外提供的接口形式为

function(options, logger)=>funciton (req, res, next)

其主要功能是对于非健康检查url时,会创建子日志,携带req.id,来记录日志,请求开始和请求结束,包含请求的方法、url,状态码及状态信息以及消耗时长。

module.exports = function logRequest(options, logger) {if (!logger) {throw new Error('Logger must be provided')}options = options || {}const _healthUrl = options.HEALTH_CHECK_URL || 'health'return (req, res, next) => {// skip _health check urlif (req.url.indexOf(_healthUrl) < 0) {var startOpts = {method: req.method,url: req.url,}// Create a per-request childreq.log = res.log = logger.child({requestId: req.id})req.log.info('start request', startOpts)var time = process.hrtime()res.on('finish', function responseSent() {var diff = process.hrtime(time)res.log.info('end request', {method: startOpts.method,url: startOpts.url,statusCode: res.statusCode,statusMessage: res.statusMessage,duration: diff[0] * 1e3 + diff[1] * 1e-6})})}next()}
}

2.8 middleware.permissions

对外提供的接口形式为

function(action, options)=>funciton (req, res, next)

其主要实现是使用authorizer来判断当前请求行为是否可以执行。如果可以,就进入处理链的下一个来处理,否则就捕获异常,填充错误信息。

module.exports = function(action, options) {options = options || {}return function(req, res, next) {return authorizer.can(req, action).then(() => {next()}).catch((err) => {err = err || new Error(info || 'You don\'t have permissions to perform this action')err.status = authorizer.getDeniedStatusCode()next(err)})}
}

参考资料:

https://github.com/appirio-tech/tc-core-library-js

https://www.npmjs.com/package/bunyan

https://www.npmjs.com/package/r7insight_node

https://www.npmjs.com/package/axios

tc-core-library-js学习笔记相关推荐

  1. ASP.NET Core 3.x 学习笔记(7)——Blazor

    ASP.NET Core 3.x 学习笔记(7)--Blazor ASP.NET Core 3.x 学习笔记(7)--Blazor 编程模式对比 Blazor 客户端宿主模型 Mono 服务器端宿主模 ...

  2. ArcGIS JS 学习笔记4 实现地图联动

    原文:ArcGIS JS 学习笔记4 实现地图联动 1.开篇 守望屁股实在太好玩了,所以最近有点懒,这次就先写个简单的来凑一下数.这次我的模仿目标是天地图的地图联动. 天地的地图联动不仅地图有联动,而 ...

  3. backbone.js学习笔记

    backbone.js学习笔记 之前只接触过jQuery,看来Backbone是除了jQuery的第二大JS框架... backbone到底是个啥? 其实刚开始我也不知道=_=,我是这周二才听说居然还 ...

  4. node.js学习笔记

    # node.js学习笔记标签(空格分隔): node.js---## 一 内置模块学习 ### 1. http 模块 ``` //1 导入http模块 const http =require('ht ...

  5. node.js学习笔记14—微型社交网站

    node.js学习笔记14-微型社交网站 1.功能分析 微博是以用户为中心,因此需要有注册和登录功能. 微博最核心的功能是信息的发表,这个功能包括许多方面,包括:数据库访问,前端显示等. 一个完整的微 ...

  6. WebGL three.js学习笔记 6种类型的纹理介绍及应用

    WebGL three.js学习笔记 6种类型的纹理介绍及应用 本文所使用到的demo演示: 高光贴图Demo演示 反光效果Demo演示(因为是加载的模型,所以速度会慢) (一)普通纹理 计算机图形学 ...

  7. html 流程控制,HTML5独家分享:原生JS学习笔记2——程序流程控制

    当当当当 .....楼主又来了!新一期的js学习笔记2--程序流程控制更新了! 想一键获取全部js学习笔记的可以给楼主留言哦! js中的程序控制语句 常见的程序有三种执行结构: 1.顺序结构 2.分支 ...

  8. 基于jquery的插件turn.js学习笔记

    基于jquery的插件turn.js学习笔记 简介 turn.js是一个可以实现3d书籍展示效果的jq插件,使用html5和css3来执行效果.可以很好的适应于ios和安卓等触摸设备. How it ...

  9. Node.js学习笔记8

    Node.js学习笔记8 HTTP服务器与客户端 Node.js的http模块,封装了一个高效的HTTP服务器和一个简易的HTTP客户端 http.server是一个基于事件的HTTP服务器,核心由N ...

  10. node.js学习笔记5——核心模块1

    node.js学习笔记5--核心模块1 Node.js核心模块主要内容包括:(1)全局对象 (2)常用工具 (3)事件机制 (4)文件系统访问 (5)HTTP服务器与客户端 一: 全局对象 Node. ...

最新文章

  1. docker安装redis提示没有日记写入权限_Docker 学习笔记(第六集:使用 Dockerfile 定制镜像)...
  2. 自动化测试框架设计模式
  3. 反编译DLL并修改再生成DLL
  4. ELK+Kafka 企业日志收集平台(一)
  5. WINCE的BIB文件解析
  6. linux下文件颜色说明
  7. python 40位的数减个位数_Python数据分析入门教程(五):数据运算
  8. jeewx-api.jar入门教程
  9. vue 后台数据列表获取图片_vue使用ajax获取后台数据进行显示的示例
  10. 新浪病毒NMGameX_AutoRun引起全公司所有打印共享器无法使用
  11. .NET中将图片文件流转成Base64字符串的实现
  12. 8.6 edu25 ,577#div2 CF补题(二分 ,dp 与 贪心
  13. python win32转pdf 横版_Python调用Win32com实现Office批量转PDF
  14. Spark实现jieba中文分词(scala)
  15. B2C商家怎样在有限的预算下展开营销
  16. iOS计算器:采用NSDecimalNumber 进行表达式的精准计算(计算字符串数学表达式)【案例:折扣计算器(完整demo源码)】
  17. eclipse 中 svn 代码报错如下 org.apache.subversion.javahl.ClientException:Filesystem has no item
  18. 经常使用的网页开发工具有哪些
  19. Tensorflow2.0 利用LSTM和爬虫做自动生成七言律诗
  20. 数据中台交付标准化参考框架

热门文章

  1. vs2012无法启动已配置的开发Web服务器
  2. struts2 spring jfreechart 整合
  3. VC++使用ADO连接SQL Server数据库
  4. monty python-网易云音乐
  5. python爬虫完整实例-python爬虫实战之爬取京东商城实例教程
  6. 学python要考什么证-这十个Python常用库,学习Python的你必须要知道!
  7. python常用命令大全-Python pip 常用命令汇总
  8. python urllib.request 爬虫 数据处理-python爬虫1--urllib请求库之request模块
  9. python快速入门第三版-Python3快速入门
  10. python自学步骤-学习Python最正确的步骤(0基础必备)