Koa - 中间件(理解中间件、实现一个验证token中间件)
前言
Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。
当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。
以上两句话,是我在官方文档中找到其对 Koa 中间件的描述。
在Koa中,中间件是一个很有意思的设计,它处于request和response中间,被用来实现某种功能。像上篇文章所使用的 koa-router 、koa-bodyparser 等都是中间件。
可能有些人喜欢把中间件理解为插件,但我觉得它们两者并不是同一种概念的东西。插件像是一个独立的工具,而中间件更像是流水线,将加工好的材料继续传递下一个流水线。所以中间件给我的感觉更灵活,可以像零件一样自由组合。
单看中间件有堆栈执行顺序的特点,两者就出现质的区别。
中间件的概念
这张图是 Koa 中间件执行顺序的图示,被称为“洋葱模型”。
中间件按照栈结构的方式来执行,有“先进后出“的特点。

一段简单的代码来理解上图:
app.use(async (ctx, next)=
console.log('--> 1')
next()
console.log('<-- 1')
}) app.use(async (ctx, next)=>{
console.log('--> 2')
//这里有一段异步操作
await new Promise((resolve)=>{
....
})
await next()
console.log('<-- 2')
}) app.use(async (ctx, next)=>{
console.log('--> 3')
next()
console.log('<-- 3')
})
app.use(async (ctx, next)=>{
console.log('--> 4')
})
当我们运行这段代码时,得到以下结果
--> 1
--> 2
--> 3
--> 4
<-- 3
<-- 2
<-- 1
中间件通过调用 next 一层层执行下去,直到没有执行权可以继续传递后,在以冒泡的形式原路返回,并执行 next 函数之后的行为。可以看到 1 第一个进去,却是最后一个出来,也体现出中间件栈执行顺序的特点。
在第二个中间件有一段异步操作,所以要加上await,让执行顺序按照预期去进行,否则可能会出现一些小问题。
中间件的使用方式
1.应用中间件
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
app.use(async (ctx,next)=>{
console.log(new Date());
await next();
})
router.get('/', function (ctx, next) {
ctx.body="Hello koa";
})
router.get('/news',(ctx,next)=>{
ctx.body="新闻页面"
});
app.use(router.routes()); //作用:启动路由
app.use(router.allowedMethods()); //作用: 当请求出错时的处理逻辑
app.listen(3000,()=>{
console.log('starting at port 3000');
});
2.路由中间件
router.get('/', async(ctx, next)=>{
console.log(1)
next()
})
router.get('/', function (ctx) {
ctx.body="Hello koa";
})
3.错误处理中间件
app.use(async (ctx,next)=> {
next();
if(ctx.status==404){
ctx.status = 404;
ctx.body="这是一个404页面"
}
});
4.第三方中间件
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());
实现验证token中间件
实现一个基于 jsonwebtoken 验证token的中间件,这个中间件由两个文件组成 extractors.js 、index.js,并放到check-jwt文件夹下。
生成token
const Router = require('koa-router')
const route = new Router()
const jwt = require('jsonwebtoken')
route.get('/getToken', async (ctx)=>{
let {name,id} = ctx.query
if(!name && !id){
ctx.body = {
msg:'不合法',
code:0
}
return
}
//生成token
let token = jwt.sign({name,id},'secret',{ expiresIn: '1h' })
ctx.body = {
token: token,
code:1
}
})
module.exports = route
使用 jwt.sign 生成token:
第一个参数为token中携带的信息;
第二个参数为key标识(解密时需要传入该标识);
第三个为可选配置选项,这里我设置过期时间为一小时;
详细用法可以到npm上查看。
使用中间件
app.js:
const {checkJwt,extractors} = require('./check-jwt')
app.use(checkJwt({
jwtFromRequest: extractors.fromBodyField('token'),
secretOrKeyL: 'secret',
safetyRoutes: ['/user/getToken']
}))
| 是否必选 | 接收类型 | 备注 | |
| jwtFromRequest | 否 | 函数 |
默认验证 header 的 authorization
extractors提供的提取函数,支持get、post、header方式提取
这些函数都接收一个字符串参数(需要提取的key)
对应函数:
fromUrlQueryParameter、
fromBodyField、
fromHeader
|
| secretOrKey | 是 | 字符串 | 与生成token时传入的标识保持一致 |
| safetyRoutes | 否 | 数组 | 不需要验证的路由 |
使用该中间件后,会对每个路由都进行验证
路由中获取token解密的信息
route.get('/getUser', async ctx=>{
let {name, id} = ctx.payload
ctx.body = {
id,
name,
code:1
}
})
通过ctx.payload来获取解密的信息
实现代码
extractors.js 工具函数(用于提取token)
let extractors = {}
extractors.fromHeader = function(header_name='authorization'){
return function(ctx){
let token = null,
request = ctx.request;
if (request.header[header_name]) {
token = header_name === 'authorization' ?
request.header[header_name].replace('Bearer ', '') :
request.header[header_name];
}else{
ctx.body = {
msg: `${header_name} 不合法`,
code: 0
}
}
return token;
}
}
extractors.fromUrlQueryParameter = function(param_name){
return function(ctx){
let token = null,
request = ctx.request;
if (request.query[param_name] && Object.prototype.hasOwnProperty.call(request.query, param_name)) {
token = request.query[param_name];
}else{
ctx.body = {
msg: `${param_name} 不合法`,
code: 0
}
}
return token;
}
}
extractors.fromBodyField = function(field_name){
return function(ctx){
let token = null,
request = ctx.request;
if (request.body[field_name] && Object.prototype.hasOwnProperty.call(request.body, field_name)) {
token = request.body[field_name];
}else{
ctx.body = {
msg: `${field_name} 不合法`,
code: 0
}
}
return token;
}
}
module.exports = extractors
index.js 验证token
const jwt = require('jsonwebtoken')
const extractors = require('./extractors')
/**
*
* @param {object} options
* @param {function} jwtFromRequest
* @param {array} safetyRoutes
* @param {string} secretOrKey
*/
function checkJwt({jwtFromRequest,safetyRoutes,secretOrKey}={}){
return async function(ctx,next){
if(typeof safetyRoutes !== 'undefined'){
let url = ctx.request.url
//对安全的路由 不验证token
if(Array.isArray(safetyRoutes)){
for (let i = 0, len = safetyRoutes.length; i < len; i++) {
let route = safetyRoutes[i],
reg = new RegExp(`^${route}`);
//若匹配到当前路由 则直接跳过 不开启验证
if(reg.test(url)){
return await next()
}
}
}else{
throw new TypeError('safetyRoute 接收类型为数组')
}
}
if(typeof secretOrKey === 'undefined'){
throw new Error('secretOrKey 为空')
}
if(typeof jwtFromRequest === 'undefined'){
jwtFromRequest = extractors.fromHeader()
}
let token = jwtFromRequest(ctx)
if(token){
//token验证
let err = await new Promise(resolve=>{
jwt.verify(token, secretOrKey,function(err,payload){
if(!err){
//将token解码后的内容 添加到上下文
ctx.payload = payload
}
resolve(err)
})
})
if(err){
ctx.body = {
msg: err.message === 'jwt expired' ? 'token 过期' : 'token 出错',
err,
code:0
}
return
}
await next()
}
}
}
module.exports = {
checkJwt,
extractors
}
Demo: https://gitee.com/ChanWahFung/koa-demo
Koa - 中间件(理解中间件、实现一个验证token中间件)的更多相关文章
- .Net Core 3.0 Api json web token 中间件签权验证和 Cors 中间件处理跨域请求
第一步:在Nuget上安装"Microsoft.AspNet.WebApi.Cors"包,并对api controller使用[EnableCors]特性以及Microsoft.A ...
- 网络游戏开发-服务器(01)Asp.Net Core中的websocket,并封装一个简单的中间件
先拉开MSDN的文档,大致读一遍 (https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/websockets) WebSocket 是一 ...
- nodejs 中间件理解
中间件概念 在NodeJS中,中间件主要是指封装所有Http请求细节处理的方法.一次Http请求通常包含很多工作,如记录日志.ip过滤.查询字符串.请求体解析.Cookie处理.权限验证.参数验证.异 ...
- Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务、WCF消息头添加安全验证Token
原文:Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务.WCF消息头添加安全验证Token 为什么选择wcf? 因为好像wcf和wpf就是哥俩,,, 为什么选择异步 ...
- express中的中间件理解
什么是中间件 中间件是一个可访问请求对象(req)和响应对象(res)的函数,在 Express 应用的请求-响应循环里,下一个内联的中间件通常用变量 next 表示.中间件的功能包括: 执行任何代码 ...
- idhttp访问DATASNAP有密码验证的中间件
idhttp访问DATASNAP有密码验证的中间件 用TIDHttp访问DataSnap Rest服务器,在服务器采用了用户验证的情况下,客户端需要提交密码,否则不能正常连接. procedure T ...
- 如何创建一个验证请求的API框架
开发一款成功软件的关键是良好的架构设计.优秀的设计不仅允许开发人员轻松地编写新功能,而且还能丝滑的适应各种变化. 好的设计应该关注应用程序的核心,即领域. 不幸的是,这很容易将领域与不属于这一层的职 ...
- 为什么你学不会递归?告别递归,谈谈我的一些经验 关于集合中一些常考的知识点总结 .net辗转java系列(一)视野 彻底理解cookie,session,token
为什么你学不会递归?告别递归,谈谈我的一些经验 可能很多人在大一的时候,就已经接触了递归了,不过,我敢保证很多人初学者刚开始接触递归的时候,是一脸懵逼的,我当初也是,给我的感觉就是,递归太神奇了! ...
- 理解JWT(JSON Web Token)认证及python实践
原文:https://segmentfault.com/a/1190000010312468?utm_source=tag-newest 几种常用的认证机制 HTTP Basic Auth HTTP ...
随机推荐
- 【集合系列】- 深入浅出分析Collection中的List接口
一.List简介 List 的数据结构就是一个序列,存储内容时直接在内存中开辟一块连续的空间,然后将空间地址与索引对应. 以下是List集合简易架构图 由图中的继承关系,可以知道,ArrayList. ...
- JAVA 调用HTTP接口POST或GET实现方式
HTTP是一个客户端和服务器端请求和应答的标准(TCP),客户端是终端用户,服务器端是网站.通过使用Web浏览器.网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认端口为80)的HTTP请 ...
- linux内核崩溃之kdump机制
kdump相关概念 standard(production) kernel 生产内核 ,是指我们正在使用的kernel. Crash(capture)kernel 捕 ...
- 6. SOFAJRaft源码分析— 透过RheaKV看线性一致性读
开篇 其实这篇文章我本来想在讲完选举的时候就开始讲线性一致性读的,但是感觉直接讲没头没尾的看起来比比较困难,所以就有了RheaKV的系列,这是RheaKV,终于可以讲一下SOFAJRaft的线性一致性 ...
- CSS(6)---通俗讲解浮动(float)
CSS(6)---通俗讲解浮动(float) CSS有三模块:盒子模型.浮动 .定位.上篇博客有讲到 盒子模型地址:CSS(5)---通俗讲解盒子模型 一.理解浮动 1.概念 概念 浮动可以理解为让某 ...
- nyoj 4 ASCII码排序
ASCII码排序 时间限制:3000 ms | 内存限制:65535 KB | 难度:2 描述 输入三个字符(可以重复)后,按各字符的ASCII码从小到大的顺序输出这三个字符. 输入 第一 ...
- nyoj 57-6174问题(相邻元素判断问题)
57-6174问题 内存限制:64MB 时间限制:1000ms Special Judge: No accepted:16 submit:31 题目描述: 假设你有一个各位数字互不相同的四位数,把所有 ...
- JAVA继承中子父类的构造方法
首先,构造方法本身会有一个隐式的无参构造(默认): ①不写构造方法,类中的第一行代码事实上有一个默认的无参构造(系统会隐式为你写好) public class Student { private St ...
- vuejs之路由应用之一
什么是‘路由’,路由相当于一个映射,一个url地址对应一个组件,当url地址A变为url地址B,那么对应地址A的组件就会改变为对应地址B的组件.应用于spa,即:单页应用,url地址改变,它不会跳转页 ...
- 程序员用于机器学习数据科学的3个顶级 Python 库
NumPy NumPy(数值 Python 的简称)是其中一个顶级数据科学库,它拥有许多有用的资源,从而帮助数据科学家把 Python 变成一个强大的科学分析和建模工具.NumPy 是在 BSD 许可 ...