Koa 中间件使用之 koa-jwt
Koa 中间件使用之 koa-jwt
koa-jwt
是 koa
的一个验证 JSON Web Tokens
的中间件, 它可以将浏览器携带在请求里面的 token
获取出来进行验证, 并将验证之后的信息携带在上下文(context
)里以供使用。本文将介绍 JWT
的基础知识、使用以及 koa-jwt
中间件的使用。
什么是 JWT
JWT(JSON Web Tokens)
是一种方便地实现服务器与客户端安全通讯的规范, 是目前最流行的跨域认证解决方案。
JWT 原理
在介绍 JWT
之前, 先来看看如何使用 session
和 cookie
做用户验证, 流程一般如下:
- 服务器验证客户端发送的用户名和密码后, 在当前对话(
session
)保存用户信息相关数据并返回一个session_id
给用户, 写入用户cookie
- 之后用户的每次请求都会通过
cookie
将session_id
传回服务器, 服务器收到session_id
, 找到之前保存的数据并获得用户信息
上面这种方式, session
数据共享不方便, 不好实现跨域服务, 如果是服务器集群, 需要实现 session
共享才能让每台服务器都能够进行用户验证。
使用 JWT
, 服务器认证用户之后, 会生成包含一个 JSON
对象信息的 token
返回给用户, 如:
{"name": "xiaoming","role": "admin"
}
然后客户端请求服务的时候, 都要带上该 token
以供服务器做验证。服务器还会为这个 JSON
添加签名以防止用户篡改数据。通过使用 JWT
, 服务端不再保存 session
数据, 更加容易实现扩展。
JWT 结构
JWT
是一行使用 .
分割成三个部分的字符串, 这被分隔的三个部分分别是: Header
(头部)、Payload
(负载)、Signature
(签名), 访问 https://jwt.io/
, 可以通过修改算法查看签名的计算公式以及结算结果。
第一部分(Header
)实际上是一个 JSON
对象, 是描述 JWT
的元数据, 其中 alg
表示的是签名的算法, 默认 HS256
, typ
表示 token
的类型是 JWT
, 比如:
{"alg": "HS256","typ": "JWT"
}
第二部分(Payload
)就是前面提到的 JSON
数据, 是希望通过服务器发送给客户端的用户信息, 可在这个 JSON
里面定义需要发送的字段, 比如:
{"sub": "1234567890","name": "xiaoming","iat": 1516239022
}
当然, JWT
官方提供了7个字段以供选用:
iss
(issuer
): 签发人exp
(expiration time
): 过期时间sub
(subject
): 主题aud
(audience
): 受众nbf
(Not Before
): 生效时间iat
(Issued At
): 签发时间jti
(JWT ID
): 编号
第三部分(Signature
)用来对 header
和 payload
两部分的数据进行签名, 从而防止数据篡改, 这个 signature
需要制定一个密钥(Secret
), 然后通过 header
里面制定的算法来产生签名。产生签名的算法也可以在 https://jwt.io/
看到, 比如:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),your - 256 - bit - secret
)
最终通过把上面三个部分组合成 Header.Payload.Signature
的形式返回给用户。
JWT 使用
客户端收到服务器返回的 token
, 可以储存在 cookie
或者 localStorage
里, 在之后的请求需要上这个 token
, 通过以下方式携带 token
:
- 通过
cookie
自动发送, 但是这样不能跨域 - 放在
HTTP
请求的头信息Authorization
字段里面:Authorization: Bearer <token>
- 将
token
放在POST
请求的数据体里面
Koa-jwt 的使用
创建 Koa 应用
下面的代码创建了一个 koa web
服务, 监听了 3000
端口, 并使用 koa-router
中间件来管理路由:
// app.jsconst Koa = require('koa');
const Router = require('koa-router');const app = new Koa(); // 创建koa应用
const router = new Router(); // 创建路由router.get('/', async (ctx) => {ctx.type = 'html';ctx.body = '<h1>hello world!</h1>';
})app.use(router.routes());
app.use(router.allowedMethods());// 启动服务监听本地3000端口
app.listen(3000, () => {console.log('应用已经启动, http://localhost:3000');
})
安装 koa-jwt
执行 npm install
安装 koa-jwt
中间件:
$ npm install koa-jwt
使用 require
引入 koa-jwt
, 并配置好密钥, 在代码里面新增一个 GET
接口 /auth
来使用该中间件:
// app.js// ...
const jwt = require('koa-jwt');// ...
const secret = 'xiaoming'; // 定义一个密钥secret, 这里只是做演示, 建议放在项目配置里面router.get('/', async (ctx) => {ctx.type = 'html';ctx.body = '<h1>hello world!</h1>';
})// 这里调用引入的jwt方法, 最终会得到一个中间件, 将中间件匹配到 / 路径
router.use(jwt({secret,debug: true // 开启debug可以看到准确的错误信息
}));router.get('/auth', async (ctx, next) => {ctx.body = ctx.state.user; // 该中间件将验证后的用户数据直接返回给浏览器
});// ...
通过浏览器访问 http://localhost:3000/auth
返回结果, 这表示 /auth
的路由开始工作了, 由于浏览器请求里面没有携带任何 token
信息, 服务返回了认证错误 Token not found
。
官方示例简介
var Koa = require('koa');
var jwt = require('koa-jwt');var app = new Koa();// 中间件 自定义了 401 响应, 将用户验证失败的相关信息返回给浏览器
app.use(function (ctx, next) {return next().catch((err) => {if (401 == err.status) {ctx.status = 401;ctx.body = 'Protected resource, use Authorization header to get access\n';} else {throw err;}});
});// 未受保护的中间件, 一般用于开放的不需要用户验证的接口
app.use(function (ctx, next) {if (ctx.url.match(/^\/public/)) {ctx.body = 'unprotected\n';} else {return next();}
});// 后面的中间件只有jwt验证通过才会执行, 达到用户验证的效果
app.use(jwt({secret: 'shared-secret'}));// 受保护的中间件, 通常是需要用户验证的接口
app.use(function (ctx) {if (ctx.url.match(/^\/api/)) {ctx.body = 'protected\n';}
});app.listen(3000);
Token 验证
由于 koa-jwt
从 koa-v2
分支开始不再导出 jsonwebtoken
的 sign
、 verify
和 decode
方法, 若要单独生成 token
、验证 token
等, 需另从 jsonwebtoken
中将其引入:
// app.js// ...
const jwt = require('koa-jwt');
const {sign} = require('jsonwebtoken');// ...// 这里调用引入的jwt方法, 最终会得到一个中间件
router.use(jwt({secret,cookie: 'token', // 从 cookie 中获取tokendebug: true // 开启debug可以看到准确的错误信息}).unless({path: [/^\/public/]}) // 以 public 开头的请求地址不使用 jwt 中间件
);router.get('/auth', async (ctx, next) => {ctx.body = ctx.state.user; // 该中间件将验证后的用户数据直接返回给浏览器
});router.get('/public/token', async (ctx, next) => {const token = jsonwebtoken.sign({name: 'xiaoming'}, secret, {expiresIn: '3h'}) // token 有效期为3小时ctx.cookies.set('token',token,{domain: 'localhost', // 设置 cookie 的域path: '/', // 设置 cookie 的路径maxAge: 3 * 60 * 60 * 1000, // cookie 的有效时间 msexpires: new Date('2021-12-30'), // cookie 的失效日期, 如果设置了 maxAge, expires 将没有作用httpOnly: true, // 是否要设置 httpOnlyoverwrite: true // 是否要覆盖已有的 cookie 设置})ctx.body = token;
})// ...
上面的代码从 jsonwebtoken
中引入了 sign
方法来生成 token
(由于 koa-jwt
本身依赖于 jsonwebtoken
, 这里没有再安装), 新增了一个 /public/token
路由使用 GET
请求方便浏览器查看, 代码中还为 jwt
中间件增加了 .unless({ path: [/^\/public/] })
方法调用来允许 /public/token
不经过 jwt
验证, 并设置 cookie: 'token'
, 表示从 cookie
中获取 token
的值作为 token
。
通过浏览器访问 http://localhost:3000/public/token
看到返回 token
, 结构如:
xxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxx
接着, 浏览器再次访问 http://localhost:3000/auth
, 可以看到获取的用户信息, 如:
{"name":"xiaoming","iat":1626690585,"exp":1626701385}
由于代码已经将生成的 token
设置到 cookie
里面, 浏览器后面发送的请求都会自动携带该 cookie
, 因此 koa-jwt
可以获取并经过验证, 得到存放在 token
里面的信息。
上面的代码示例通过 cookie
来存储 token
, 但大多数场景则通过请求头的携带 token
, 这时可以用 Authorization
作为请求头的 key
, 它的值为Bearer <token>
的结构, 注意中间的空格 。
Koa-jwt 如何从请求获取 token?
Koa-jwt
有三种途径获取 token
:
- 通过自定义
getToken
方法获取 - 通过配置里的
cookie key
值获取 - 通过请求头里面的
Authorization
获取,Authorization
的格式一般是Bearer <token>
下面是 koa-jwt
从请求头获取 token
的源码片段:
module.exports = function resolveAuthorizationHeader(ctx, opts) {if (!ctx.header || !ctx.header.authorization) { // 判断请求头里面是否有 authorizationreturn;}const parts = ctx.header.authorization.trim().split(' '); // authorization是 Bearer + ' ' + jwt字符串if (parts.length === 2) {const scheme = parts[0];const credentials = parts[1];if (/^Bearer$/i.test(scheme)) { // 判断 authorization 请求头是不是以 Bearer 开头return credentials; // 返回 token}}if (!opts.passthrough) {ctx.throw(401, 'Bad Authorization header format. Format is "Authorization: Bearer <token>"');}
};
下面是 koa-jwt
从 cookie
获取 token
的源码片段:
module.exports = function resolveCookies(ctx, opts) {return opts.cookie && ctx.cookies.get(opts.cookie); // 单纯从 cookie 配置获取token
};
Koa-jwt 获取 token 的优先级?
opts.getToken > opts.cookie > Authorization
请求头, 可参考源码:
const resolveAuthHeader = require('./resolvers/auth-header'); // 从 header 获取
const resolveCookies = require('./resolvers/cookie'); // 从 cookie 获取module.exports = (opts = {}) => {const {debug, getToken, isRevoked, key = 'user', passthrough, tokenKey} = opts;const tokenResolvers = [resolveCookies, resolveAuthHeader]; // 定义获取token的方法数组if (getToken && typeof getToken === 'function') {tokenResolvers.unshift(getToken); // 往前追加自定义的获取方法}const middleware = async function jwt(ctx, next) {let token;tokenResolvers.find(resolver => token = resolver(ctx, opts)); // 获取 tokenif (!token && !passthrough) {ctx.throw(401, debug ? 'Token not found' : 'Authentication Error');}}
}
// ...
Koa-jwt 支持的配置有哪些?
getToken
: 自定义获取token
的方法secret
:jwt
的密钥key
: 自定义token
验证后的数据存放在ctx.state
的key
值, 默认是ctx.state.user
isRevoked
: 定义对无效的token
进行报错Token revoked
passthrough
: 是否不进行401
报错, 如果为true
, 将在ctx.state.jwtOriginalError
获取到验证错误信息, 可以让后面的中间件自行处理cookie
: 设置包含token
的cookie
, 如果设置了值,Koa-jwt
会优先从cookie
获取token
audience
: 提供给依赖的jsonwebtoken
使用, 用于检查JWT
的aud
(audience
受众)issuer
: 提供给依赖的jsonwebtoken
使用,JWT
的iss (issuer)
: 签发人, 是iss
字段中有效的string
或string
数组debug
: 是否开启调试, 如果为true
则会提示出准确信息
Koa-jwt 如何忽略特定路径?
Koa-jwt
使用 unless
表达式忽略滤路径, 可以参考 koa-unless
, 使用示例:
app.use(jwt({secret: 'shared-secret'}).unless({path: [/^\/public/]}));
完整代码
// app.jsconst Koa = require('koa');
const Router = require('koa-router');
const jwt = require('koa-jwt');
const jsonwebtoken = require('jsonwebtoken');const app = new Koa(); // 创建koa应用
const router = new Router(); // 创建路由const secret = 'xiaoming'; // 定义一个密钥secret, 这里只是做演示, 建议放在项目配置里面router.get('/', async (ctx) => {ctx.type = 'html';ctx.body = '<h1>hello world!</h1>';
})// 这里调用引入的jwt方法, 最终会得到一个中间件
router.use(jwt({secret,cookie: 'token', // 从 cookie 中获取tokendebug: true // 开启debug可以看到准确的错误信息}).unless({path: [/^\/public/]}) // 以 public 开头的请求地址不使用 jwt 中间件
);router.get('/auth', async (ctx, next) => {ctx.body = ctx.state.user; // 该中间件将验证后的用户数据直接返回给浏览器
});router.get('/public/token', async (ctx, next) => {const token = jsonwebtoken.sign({name: 'moyufed'}, secret, {expiresIn: '3h'}) // token 有效期为3小时ctx.cookies.set('token',token,{domain: 'localhost', // 设置 cookie 的域path: '/', // 设置 cookie 的路径maxAge: 3 * 60 * 60 * 1000, // cookie 的有效时间 msexpires: new Date('2021-12-30'), // cookie 的失效日期, 如果设置了 maxAge, expires 将没有作用httpOnly: true, // 是否要设置 httpOnlyoverwrite: true // 是否要覆盖已有的 cookie 设置})ctx.body = token;
})app.use(router.routes());
app.use(router.allowedMethods());// 启动服务监听本地3000端口
app.listen(3000, () => {console.log('应用已经启动, http://localhost:3000');
})
总结
JWT(JSON Web Tokens)
是一种方便地实现服务器与客户端安全通讯的解决方案, 分为三个部分:Header
、Payload
、Signature
, 浏览器发送请求时将token
带到服务器Koa-jwt
是koa
的一个中间件, 帮助koa web
服务获取并解析请求里面的token
, 有默认的获取token
的方式, 并且支持各种配置, 依赖于jsonwebtoken
库进行工作
Koa 中间件使用之 koa-jwt相关推荐
- koa 接口返回数据_一文搞定 Koa 中间件实现原理
Koa是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小.更富有表现力.更健壮的基石. 通过利用 async 函数, Koa ...
- web前端技术分享:koa中间件是如何实现的?
在前端开发过程中我们可能会使用到koa中间件,但很多同学却不知道它是如何实现的,下面小千就来给大家介绍一下这个koa中间件(洋葱模型). 一.问题分析 async await是promise的语法糖, ...
- generator探幽(1)--koa中间件机制浅析
本系列旨在通过对co,koa等库源码的研究,进而理解generator在异步编程中的重大作用(ps:所有代码请在node --harmony或者iojs环境中运行) koa中间件的形式 相信用过koa ...
- koa中间件机制详解
转自:https://cnodejs.org/topic/58fd8ec7523b9d0956dad945 koa是由express原班人马打造的一个更小.更富有表现力.更健壮的web框架. 在我眼中 ...
- Koa 中间件的执行
Node.js 中请求的处理 讨论 Koa 中间件前,先看原生 Node.js 中是如何创建 server 和处理请求的. node_server.js const http = require(&q ...
- 【nodejs原理源码赏析(2)】KOA中间件的基本运作原理
[摘要] KOA中间件的基本运作原理 示例代码托管在:http://www.github.com/dashnowords/blogs 在中间件系统的实现上,KOA中间件通过async/await来在不 ...
- 什么是koa中间件,他们的执行顺序是什么样的?
koa中间件 koa中,中间件分为应用级和路由级 //应用级 app.use(async (ctx, next) => { //应用级中间件 ,先执行中间件,再匹配路由console.log(& ...
- 深入理解 Koa 中间件之 “ 洋葱模型 ”
欢迎关注我的公众号『 前端我废了 』,查看更多文章!!! 前言 我们知道创建一个 Koa 应用主要分三步: const Koa = require('koa'); // 1. 创建一个 Koa 实例 ...
- 初识洋葱模型,分析中间件执行过程,浅析koa中间件源码
前言 作为洋葱模型的第一篇文章,这里仅介绍了一些入门级知识,比如 了解洋葱模型执行顺序 分析部分 koa 中间件的源码来加深对中间件的认识 为第二篇文章:分析洋葱模型实现原理,在自己项目中接入洋葱模型 ...
最新文章
- 缩小sql server 日志文件
- python androidhelper kivy_顶SLA4、QPython学习笔记
- java之Arrays工具类的使用
- 02 掌握实现数据导入导出的方法 1214
- 降价200!华为部分手机已取消充电器和数据线,网友表示可以接受
- GIMP 教程:如何在 GIMP 中创建曲线文本
- excel计算机一级知识点,计算机一级考试考点:Excel电子表格
- HTML5期末大作业:一款基于HTML+CSS+JavaScript +Bootstrap 响应式的花店/花卉装饰/花卉网上商店/婚礼花/花束商店购物网站
- python吃鸡透视_绝地求生仅需这个设置!让你的电脑自带透视!吃鸡到手软
- JAVA SE — Day 18
- python创意网络爬虫_python之网络爬虫
- 关于mysql的时区(下):如何设置mysql的时区
- 无主3局域网找不到服务器,无主之地3局域网模式设置方法攻略 无主之地3局域网怎么用...
- mysql 报1055错误_MySQL数据库报1055错误
- mongodb java api chm_MongoDB中文手册chm版
- z变换判断稳定性和因果性_判断因果性.PPT
- 一政网:考教师编制,掌握出题结构很重要!
- matlab多元回归模型分析,matlab多元回归工具箱 Excel数据分析工具进行多元回归分析.doc...
- java软件工程师工作业绩_java软件工程师个人简历
- Java解析Xml的三种方式总结
热门文章
- 史上最强网推案例,没有之一【ZW团队实战经典】
- Vue.js系列之入门手册整理
- 查看麒麟操作系统版本
- 二十世纪西方文化三大发现:莫菲定律 派金森定理 彼得原理
- MySQL 一个字段多个id查询
- openWRT路由器实现内网穿透,公网远程连接局域网/内网路由器实现方案
- linux 时钟漂移,RH214|第十一章 分析和存储日志
- hsrp 切换_冠县双电源切换原理图详情
- 川大的计算机类和电气工程,四川大学网络教育学院电气工程及其自动化专业怎么样...
- 【对讲机的那点事】从对讲机的今生窥观前世对讲机行业的模转数