用户身份验证一般有两种方式,一种是基于cookie的认证方式,另外一种是基于token的认证方式。当前常见的无疑是基于token的认证方式。如下所提到的koa均为koa2版本。html
token认证的优势是无状态机制,在此基础之上,能够实现自然的跨域和先后端分离等。前端
token认证的缺点是服务器每次都须要对其进行验证,会产生额外的运行压力。此外,无状态的api缺少对用户流程或异常的控制,为了不一些例如回放攻击的异常状况,大多会设置较短的过时时间。jquery
准备工做ios
使用koa-jwt的大体流程是:web
1. 用户经过身份验证API(登陆)获取当前用户在有效期内的tokenajax
2. 须要身份验证的API则都须要携带此前认证过的token发送至服务端json
3. koa会利用koa-jwt中间件的默认验证方式进行身份验证,中间件会进行验证成功和验证失败的分流。axios
koa-jwt中间件的验证方式有三种:后端
1. 在请求头中设置 authorization为Bearer + token,注意Bearer后有空格。(koa-jwt的默认验证方式)api
{'authorization': "Bearer " + token}
2. 自定义getToken方法
3. 利用Cookie(此cookie非彼cookie)此处的Cookie只做为存储介质发给服务端的区域,校验并不依赖于服务端的session机制,服务端不会进行任何状态的保存。
实战逻辑:
1.在登陆路由中进行验证,可携带用户名等必要信息,并将其放至上下文对象中。
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进行访问,则不能实现对应效果。
$.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字段的参数,就进行验证。此方法最大的优势就是遍历,但注意的一点是,须要在后端总路由拦截时作好架构,以避免对其余路由形成干扰。
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中获取状态信息。
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' })
如下为核心后端文件的源码:
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) })