koa2实现用户登录、jwt签发token
功能点:
1.注册账号:异常判断-用户名已存在;密码加密存库
2.账号登录:异常判断-用户不存在、密码错误;jwt签发token凭证;前端接收存入localStorage
3.token校验:接口携带token axios config.headers['Authorization'] = 'Bearer ' + token 后端接收校验;异常判断-校验失败、过期,前端跳转登录
新建 config\index.js
const config = { jwt: { SECRET: 'custom' // jwt密钥 }, bcrypt: { saltRounds: 12 // 生成salt迭代次数 }, crypto: { JOINSTR: 'custom' // md5拼接字符串 }, } module.exports = config
新建 utils\user.js
const crypto = require('crypto') // node 自带
const bcrypt = require('bcryptjs') // 需安装依赖 npm i bcryptjs
const { bcrypt: bcryptConfig, crypto: cryptoConfig } = require('../config')
// md5加密
function md5(v) {
const { JOINSTR } = cryptoConfig
return crypto.createHash('md5').update(v + JOINSTR).digest('hex')
}
// 密码加密
function encryptPassword(password) {
const { saltRounds } = bcryptConfig
const pwdEnCode = md5(password)
const salt = bcrypt.genSaltSync(saltRounds)
return bcrypt.hashSync(pwdEnCode, salt)
}
// 密码验证
function verifyPassword(inputPwd, userPwd) {
const pwdEnCode = md5(inputPwd)
return bcrypt.compareSync(pwdEnCode, userPwd)
}
module.exports = {
encryptPassword,
verifyPassword
}
调用方式
const { encryptPassword, verifyPassword } = require('../utils/user')
const { jwt } = require('../config')
// 注册模块中 - 密码加密 const pwdEncrypt = encryptPassword(password) // 登录模块中 - 密码校验 const verifyPwd = verifyPassword(password, userInfo.password) // 登录模块中 - 签发token token: jsonwebtoken.sign( { name: userInfo.username, id: userInfo.id }, jwt.SECRET, { expiresIn: '1d' } )
// app.js中
// token校验失败处理 app.use(async (ctx, next) => { return next().catch((err) => { if (err.status === 401) { ctx.status = 401; ctx.body = { code: 401, msg: err.message } } else { throw err; } }) }); // token验证 app.use(koajwt({ secret: jwt.SECRET }).unless({ // 不验证接口 path: [/^\/login$/, /^\/register$/] }));
完整代码 https://gitee.com/senjer/txintegration-node.git
后端
const router = require('koa-router')()
const exec = require('../utils/mysql');
const jsonwebtoken = require('jsonwebtoken')
const { jwt } = require('../config')
const { encryptPassword, verifyPassword } = require('../utils/user')
router.prefix('/')
router.get('/', async (ctx, next) => {
await ctx.render('index', {
title: 'demo'
})
})
router.post('/register', async(ctx, next) => {
const { username, password } = ctx.request.body
const userInfo = await exec(`select * from user where username = '${username}'`)
if (userInfo.length) {
ctx.body = {
code: 201,
msg: '用户名已存在'
}
return
}
const pwdEncrypt = encryptPassword(password)
const sql = `insert into user (username, password) values ('${username}', '${pwdEncrypt}')`
await exec(sql)
ctx.body = {
code: 200,
msg: '',
}
})
router.post('/login', async (ctx, next) => {
const { username, password } = ctx.request.body
const userInfo = (await exec(`select * from user where username = '${username}'`))[0]
if (!userInfo) {
ctx.body = {
code: 201,
msg: '用户名不存在',
}
return
}
const verifyPwd = verifyPassword(password, userInfo.password)
if (verifyPwd) {
ctx.body = {
code: 200,
msg: '登录成功',
data: {
token: jsonwebtoken.sign(
{ name: userInfo.username, id: userInfo.id },
jwt.SECRET,
{ expiresIn: '1d' }
)
}
}
} else {
ctx.body = {
code: 201,
msg: '用户名密码不匹配'
}
}
})
router.post('/list', async (ctx, next) => {
const { page, limit } = ctx.request.body // POST 参数
const sql = `select u.*, group_concat(c.course) as course from user u left join course c on c.userId = u.id group by u.id limit ${(page - 1) * limit}, ${limit};`
const list = await exec(sql)
ctx.body = {
code: 200,
msg: '',
data: {
list
}
}
})
module.exports = router
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const cors = require('koa2-cors');
const logger = require('koa-logger')
const koajwt = require('koa-jwt')
const { jwt } = require('./config')
const index = require('./routes')
// error handler
onerror(app)
// middlewares
app.use(bodyparser({
enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))
app.use(views(__dirname + '/views', {
map: {
html: 'ejs'
}
}))
app.use(cors({
origin: function (ctx) {
if (ctx.url === '/login') {
return "*"
}
return 'http://localhost:8080'
},
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
maxAge: 5,
credentials: true,
allowMethods: ['GET', 'POST', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}))
// logger
app.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
// token校验失败处理
app.use(async (ctx, next) => {
return next().catch((err) => {
if (err.status === 401) {
ctx.status = 401;
ctx.body = {
code: 401,
msg: err.message
}
} else {
throw err;
}
})
});
// token验证
app.use(koajwt({ secret: jwt.SECRET }).unless({
// 不验证接口
path: [/^\/login$/, /^\/register$/, /^\/$/]
}));
// routes
app.use(index.routes(), index.allowedMethods())
// error-handling
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
});
module.exports = app
前端
const baseUrl = '' let service = axios.create({ baseUrl: baseUrl, timeout: 30 * 1000, crossDomain: true, withCredentials: true }) // 设置 post 默认 Content-Type service.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'; // 请求拦截器 service.interceptors.request.use((config) => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = 'Bearer ' + token; } return config }, (error) => { return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use((response) => { let { data } = response; return data }, (error) => { let { status } = error.response; if (status == 401) { // token 过期 window.location.href = '/#/login' } } ) export default service

浙公网安备 33010602011771号