koa2,koa-jwt中token验证实战详解
用户身份验证通常有两种方式,一种是基于cookie的认证方式,另一种是基于token的认证方式。当前常见的无疑是基于token的认证方式。以下所提到的koa均为koa2版本。
token认证的优点是无状态机制,在此基础之上,可以实现天然的跨域和前后端分离等。
token认证的缺点是服务器每次都需要对其进行验证,会产生额外的运行压力。此外,无状态的api缺乏对用户流程或异常的控制,为了避免一些例如回放攻击的异常情况,大多会设置较短的过期时间。
准备工作
使用koa-jwt的大致流程是:
1. 用户通过身份验证API(登录)获取当前用户在有效期内的token
2. 需要身份验证的API则都需要携带此前认证过的token发送至服务端
3. koa会利用koa-jwt中间件的默认验证方式进行身份验证,中间件会进行验证成功和验证失败的分流。
koa-jwt中间件的验证方式有三种:
1. 在请求头中设置 authorization为Bearer + token,注意Bearer后有空格。(koa-jwt的默认验证方式)
1 | { 'authorization' : "Bearer " + token} |
2. 自定义getToken方法
3. 利用Cookie(此cookie非彼cookie)此处的Cookie只作为存储介质发给服务端的区域,校验并不依赖于服务端的session机制,服务端不会进行任何状态的保存。
实战逻辑:
1.在登录路由中进行验证,可携带用户名等必要信息,并将其放至上下文对象中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | router.post( '/login' , async (ctx, next) => { const user = ctx.request.body; if (user && user.username === 'tate' ) { let {username} = user; const token = sign({username, test: 'testok' }, secret, {expiresIn: '1h' }); ctx.body = { mssage: 'GET TOKEN SUCCESS' , code: 1, token } } else { ctx.body = { message: 'param error' , code: -1 } } }) |
2. 客户端登录成功并获取token信息后,将其保存在客户端中。如localstorage。
3. 在访问需要用户登录信息验证的接口是,需要将请求头设置authorization。此处我使用过两种方式:
(1)利用jquery或axios等前端库在对应的钩子中进行拦截设置请求头,此处以jq为例。这种思路有一个比较麻烦的点就是,所有需要验证的接口都需要单独设置请求头。如果用户自己通过url上拼装token进行访问,则不能实现对应效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $.ajax({ url: '/userinfo' , type: 'get' , data: { param1: 'post1' , param2: 'post2' , token: localStorage.getItem( 'token' ) }, beforeSend: function (xhr) { xhr.setRequestHeader( "authorization" , "Bearer " + localStorage.getItem( 'token' )); }, success: function (msg) { console.log(msg); }, fail: function (err) { console.log(err); } }) |
(2)第二种就是利用koa的中间件在总路由中进行拦截处理。只要存在拼装了token字段的参数,就进行验证。此方法最大的优点就是遍历,但注意的一点是,需要在后端总路由拦截时做好架构,以免对其他路由造成干扰。
1 2 3 4 5 6 7 | app.use(bodyParser()) app.use(async (ctx, next) => { console.log(ctx) let params =Object.assign({}, ctx.request.query, ctx.request.body); ctx.request.header = { 'authorization' : "Bearer " + ( params .token || '' )} await next(); }) |
3.利用koa-jwt设置需要验证才能访问的接口,验证成功后可在上下文中的state中获取状态信息。
1 2 3 4 5 6 7 8 | router. get ( '/userinfo' , jwt, async (ctx, next) => { ctx.body = {username: ctx.state.user.username} console.log(ctx) }) . get ( '/viplist' , jwt, async (ctx, next) => { console.log(ctx.state) ctx.body = 'check ok' }) |
以下为核心后端文件的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | const koa = require( 'koa' ); const app = new koa(); const bodyParser = require( 'koa-bodyparser' ); const Router = require( 'koa-router' ); const router = new Router(); const views = require( 'koa-views' ); const static = require( 'koa-static' ); const path = require( 'path' ); const { sign } = require( 'jsonwebtoken' ); const secret = 'demo' ; const jwt = require( 'koa-jwt' )({secret}); app.use(bodyParser()) app.use(views(__dirname + '/views' , { map: {html: 'ejs' } })) app.use( static (path. join (__dirname, '/static' ))) app.use(async (ctx, next) => { console.log(ctx) let params =Object.assign({}, ctx.request.query, ctx.request.body); ctx.request.header = { 'authorization' : "Bearer " + ( params .token || '' )} await next(); }) router. get ( '/' , async (ctx, next) => { await ctx.render( 'index' ) }) router.post( '/login' , async (ctx, next) => { const user = ctx.request.body; if (user && user.username === 'tate' ) { let {username} = user; const token = sign({username, test: 'testok' }, secret, {expiresIn: '1h' }); ctx.body = { mssage: 'GET TOKEN SUCCESS' , code: 1, token } } else { ctx.body = { message: 'param error' , code: -1 } } }) . get ( '/userinfo' , jwt, async (ctx, next) => { ctx.body = {username: ctx.state.user.username} console.log(ctx) }) . get ( '/viplist' , jwt, async (ctx, next) => { console.log(ctx.state) ctx.body = 'check ok' }) router. get ( '/404' , async (ctx, next) => { await ctx.render( '404' ) }) app .use(router.routes()) .use(router.allowedMethods()) app.listen(3000, () => { console.log( 'server is running at port 3000' ); console.log(3) }) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?