koa2,koa-jwt中token驗證實戰詳解

牙疼哥哥發表於2019-06-15

使用者身份驗證通常有兩種方式,一種是基於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的預設驗證方式)

{'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)
})

  

 

相關文章