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
user.js 
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
app.js

 

前端

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
http.js

 

posted @ 2021-11-29 18:12  _senjer  阅读(478)  评论(0)    收藏  举报