为什么使用JWT?
随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。
JWT架构图
贴代码贴代码贴代码
首页,在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
}
下面执行一下:
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
一分钟之后 ,我们在Postman执行一下:
当token过期或者认证失败的情况下就回调401,让前端去执行统一跳转,完美解决token认证与失效的问题~~~~~~~~~~