zoukankan      html  css  js  c++  java
  • 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的默认验证方式)

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

      

  • 相关阅读:
    sbt commands
    SBT Assembly
    There is no setter for property named 可能产生的原因!
    JSP页面分页显示数据
    CentOS7配置FTP服务器增强版~(零基础学会FTP配置)
    Windows下将程序打包为安装包(最为简易的方式)
    Linux多线程编程详细解析----条件变量 pthread_cond_t
    在Linux中使用线程
    关于verilog中语句可不可综合
    32位先行进位加法器的实现
  • 原文地址:https://www.cnblogs.com/pomelott/p/11026626.html
Copyright © 2011-2022 走看看