为什么使用JWT?
随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。

JWT架构图

java Jwts还需要使用redis保存token嘛_sql

贴代码贴代码贴代码

首页,在conf/db.js新增一下JWT生成的秘钥等配置

const env = process.env.NODE_ENV  // 环境参数
// 配置
let MYSQL_CONF

// TOKEN配置
let AUTHORIZATION = {
    jwtSecret: 'jwtSecret', // token秘钥
    tokenExpiresTime: 60 * 1 // token过期时间1分钟
}

if (env === 'dev') {
    ...
}

if (env === 'production') {
   ...
}

module.exports = {
    MYSQL_CONF,
    AUTHORIZATION
}

在是在原来的router/users.js文件中新增login API

const router = require('koa-router')()
const jwt = require('jsonwebtoken')
const { login, register, userNameFilter } = require('../controller/users')
const { AUTHORIZATION } = require('../conf/db')
const { SuccessModel, ErrorModel } = require('../model/resModel')

router.prefix('/api/users')

router.post('/register', async function (ctx, next) {
    ...
})

router.post('/login', async function (ctx, next) {
    const { username, password } = ctx.request.body
    if(!username || !password){
        ctx.body = new ErrorModel('用户名或者密码不能为空')
        return
    }
    const data = await login(username, password)
    if (data.username) {
        // 生成验证token
        const token = jwt.sign({
            username: data.username,
            id: data.id
        }, AUTHORIZATION.jwtSecret, { expiresIn: AUTHORIZATION.tokenExpiresTime });

        // 设置 session
        // ctx.session.username = data.username
        // ctx.session.realname = data.realname
        const userData = {
            token: token
        }
        ctx.body = new SuccessModel(userData)
        return
    }
    ctx.body = new ErrorModel('登录失败,请检查用户名或密码!')
})

module.exports = router

在controller/users.js文件中新增login方法

const { exec } = require('../db/mysql')
const { genPassword } = require('../utils/cryp')

const register = async (username, password) => {
    // 生成加密密码
    ...
}
const userNameFilter = async (username) => {
   ...
}

const login = async (username, password) => {
    // 生成加密密码
    password = genPassword(password)

    const sql = `
        select id, username, realname from users where username='${username}' and password='${password}'
    `
    // console.log('sql is', sql)

    const rows = await exec(sql)
    return rows[0] || ''
}


module.exports = {
    login,
    register,
    userNameFilter
}

下面执行一下:

java Jwts还需要使用redis保存token嘛_用户名_02

token已经生成了,过期时间为1分钟,那么我们怎么利用中间件来判断用户的登录情况呢????

接下来写中间件登录认证 middleware/loginCheck.js

/**
 * des:登录判断的中间件
 * **/
const { ErrorModel, LoginFailure } = require('../model/resModel')
const verify = require('../utils/verify')
const { AUTHORIZATION } = require('../conf/db')
const { userInfo } = require('../controller/users')
module.exports = async (ctx, next) => {
    // 获取jwt
    const token = ctx.header.authorization;
    if (token !== null && token) {
        try {
            // 解密payload,获取用户名和ID
            let payload = await verify(token, AUTHORIZATION.jwtSecret);
            if (payload) {
                // 根据用户id获取用户信息
                let user = await userInfo(payload.id)
                if (!!user) {
                    const userData =  {
                        name: payload.username,
                        id: payload.id
                    }
                    ctx.state.user = userData // 存用户数据
                }
            }
        } catch (err) {
            ctx.body = new LoginFailure('认证失效,请重新登录')
            return
        }
    } else {
        ctx.body = new ErrorModel('token不能为空')
        return
    }
    await next()
}

统一封装一个认证失效的回调模型:model/resModel.js

class BaseModel {
    constructor(data, message) {
        if (typeof data === 'string') {
            this.message = data
            data = null
            message = null
        }
        if (data) {
            this.data = data
        }
        if (message) {
            this.message = message
        }
    }
}
// 成功
class SuccessModel extends BaseModel {
    constructor(data, message) {
        super(data, message)
        this.code = 0
    }
}
// 错误
class ErrorModel extends BaseModel {
    constructor(data, message) {
        super(data, message)
        this.code = -1
    }
}
// token 认证失败
class LoginFailure extends BaseModel {
    constructor(data, message) {
        super(data, message)
        this.code = 401
    }
}

// 自定义code,方便特殊情况下返回特殊code,不能和上面已建的code重复
class CustomModel extends BaseModel {
    constructor(code, data, message) {
        super(data, message)
        this.code = code
    }
}
module.exports = {
    SuccessModel,
    ErrorModel,
    LoginFailure,
    CustomModel
}

token认证方法封装:utils/verify.js

/**
 * des: token验证
 * **/
const jwt = require('jsonwebtoken');
module.exports = (...args) => {
    return new Promise((resolve, reject) => {
        jwt.verify(...args, (error, decoded) => {
            error ? reject(error) : resolve(decoded);
        });
    });
};

controller/users.js加入查询用户信息的控制层

...
const userInfo = async (id) => {
    const sql = `
        select id, username, realname from users where id=${id}
    `
    const rows = await exec(sql)
    return rows[0] || ''
}

module.exports = {
    ...
    userInfo
}

接下来我们创建一个router 需要登录认证的接口:

const router = require('koa-router')()
const {
  getList
} = require('../controller/blog')
const { SuccessModel, ErrorModel } = require('../model/resModel')
const loginCheck = require('../middleware/loginCheck')

router.prefix('/api/blog')
// 查询博客
router.get('/list', async function (ctx, next) {
    let author = ctx.query.author || ''
    const keyword = ctx.query.keyword || ''
    const listData = await getList(author, keyword)
    // ctx.body = listData
    ctx.body = new SuccessModel(listData, '成功') // 修改
})
// 查询自己的博客
router.get('/mylist',loginCheck, async function (ctx, next) {
    if (ctx.state.user) {
        const author = ctx.state.user.name
        const listData = await getList(author)
        ctx.body = new SuccessModel(listData)
    } else {
        ctx.body = new ErrorModel('找不到用户,查询失败')
    }
})
module.exports = router

登录一下 重新获取一下token,执行一下接口:/api/blog/mylist 加入请求头 Authorization:token

java Jwts还需要使用redis保存token嘛_sql_03

一分钟之后 ,我们在Postman执行一下:

java Jwts还需要使用redis保存token嘛_sql_04

当token过期或者认证失败的情况下就回调401,让前端去执行统一跳转,完美解决token认证与失效的问题~~~~~~~~~~