用戶身份驗證一般有兩種方式,一種是基於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) })