Koa2(五)-token鉴权

1,210 阅读4分钟

Koa2 token 鉴权

Token认证流程

    1. 客户端使用账号和密码进行登录
    1. 服务端获取用户登录请求后,进行验证
    1. 当验证通过之后,生成Token,并返回给客户端
    1. 客户端获取到Token并存储在 Web Storage 中
    1. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
    1. 服务端收到请求,然后去验证客户端请求里面带着的 Token(request头部添加Authorization),如果验证成功,就向客户端返回请求的数据,如果不成功返回401错误码,鉴权失败,401表示没有访问权限,需要进行身份认证

jsonwebtoken 和 koa-jwt

  • jsonwebtoken 用于生成token
  • koa-jwt 为处理token的中间件
  • 其中他们两个的密钥必须相同
  • 安装 npm install jsonwebtoken koa-jwt -S

jsonwebtoken使用

  • 定义生成token方法,以及加密的密钥

image.png

// 自定义密钥
const secret = "fl.b-18#cd_/12*+chan&koa^!.nam$milo"
const { sign } = require('jsonwebtoken')

/**
 * @param {*} data 加密的数据,最好不要包括密码之类的
 * @param {*} time 有效时长,数字 1000 表示 1000s; 字符串 '10h' 表示 10小时,'1d' 表示1天;具体参考文档
 */
const singToken = (data, time) => {
  const token = sign(
    data,
    secret,
    {
      expiresIn: time
    }
  )
  return token
}

module.exports = {
  singToken,
  secret
}
  • 定义登录路由

image.png

const { 
  exec,
  escape
} = require('../db/mysql')

// 用于md5加密
const { genPassword } = require('./../utils/cryp')

// 预防xss攻击
const xss = require('xss')

// 检查用户是否已经注册
const checkIsRegister = async (username) => {
  username = escape(username)
  username = xss(username)
  const sql = `
    SELECT * from users WHERE username=${username}
  `
  const result = await exec(sql)
  if (result.length) {
    // 表示该用户已经注册
    return true
  }
  return false
}

// 注册
const register = async (name, username, password) => {
  name = xss(escape(name))
  username = xss(escape(username))
  password = genPassword(password)
  const sql = `
    INSERT INTO users (name, username, password)
    VALUES (${name}, ${username}, '${password}')
  `
  const result = await exec(sql)
  return {
    id: result.insertId
  }
}

// 登录
const login = async (username, password) => {
  username = xss(escape(username))
  password = genPassword(password)
  const sql = `
    SELECT id, name, username FROM users WHERE username=${username} AND password='${password}'
  `
  const result = await exec(sql)
  return result
}

module.exports = {
  checkIsRegister,
  register,
  login
}

image.png

const router = require('koa-router')()
const { checkIsRegister, register, login } = require('./../controller/users')
const { SuccessModel, ErrorModel } = require('./../model/resModel')

router.prefix('/users')

router.post('/register', async function (ctx, next) {
  try {
    const { name, username, password } = ctx.request.body
    const isRegister = await checkIsRegister(username)
    if (isRegister) {
      ctx.body = new ErrorModel('该用户已被注册')
    } else {
      const registerResult = await register(name, username, password)
      ctx.body = new SuccessModel(registerResult, '注册成功')
    }
  }catch {
    ctx.body = new ErrorModel('注册失败')
  }
})

router.post('/login', async ctx => {
  try {
    const { username, password } = ctx.request.body
    const loginResult = await login(username, password)
    if (loginResult.length) {
      const data = {
        userinfo: {
          ...loginResult[0]
        },
        token: 'token'
      }
      ctx.body = new SuccessModel(data, '登录成功')
    } else {
      ctx.body = new ErrorModel('登录失败')
    }
  }catch {
    ctx.body = new ErrorModel('登录失败')
  }
})

module.exports = router

  • 登录路由使用情况

image.png

image.png

image.png

image.png

  • 登录成功时返回真正的token

image.png

image.png

koa-jwt使用

全局使用

image.png

// app.js
// koa-jwt token中间件使用,得放到路由中间件后面使用
const jwt = require('koa-jwt')
const { secret } = require('./utils/singToken')
const { ErrorModel } = require('./model/resModel')

// 需要token鉴权的路由,如果没有在头部传递token,自定义401错误处理,表示没有权限,需要身份认证
app.use(function(ctx, next){
  return next().catch((err) => {
    if (401 == err.status) {
      ctx.status = 401;
      ctx.body = new ErrorModel('Protected resource, use Authorization header to get access')
    } else {
      throw err;
    }
  });
});

// 表示除了路由以 /users 开头的不需要token鉴权,其他路由都要
// koa-jwt会自动解析token,并将解析的token内容存放在ctx.state.user里面
app.use(jwt({ secret }).unless({ path: [/^\/users/] }))
// app.js全部代码
const Koa = require('koa')
const app = new Koa()
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')

// 生成日志文件
const morgan = require('koa-morgan')
const path = require('path')
const fs = require('fs')

// 跨域处理
const cors = require('koa2-cors')

// koa-jwt token中间件使用
const jwt = require('koa-jwt')
const { secret } = require('./utils/singToken')
const { ErrorModel } = require('./model/resModel')

// 路由
const index = require('./routes/index')
const users = require('./routes/users')
const uploads = require('./routes/upload')
const download = require('./routes/download')
const jwtTest = require('./routes/jwtTest')


// error handler
onerror(app)

// middlewares
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(require('koa-static')(__dirname + '/public/'))

// cors use
app.use(cors({
  origin: function(ctx) {
    if (ctx.url === '/test') {
      return false
    }
    return '*'
  },
  exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
  maxAge: 5,
  credentials: true,
  allowMethods: ['GET', 'POST', 'DELETE'],
  allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}))

// production into logs,dev console.log
const ENV = process.env.NODE_ENV
if (ENV === 'dev') {
  // dev
  app.use(morgan('dev'))
} else {
  // production
  const logFileName = path.join(__dirname, 'logs', 'access.log')
  const writeStream = fs.createWriteStream(logFileName, {
    flags: 'a'
  })
  app.use(morgan('combined', {
    stream: writeStream
  }));
}

// 需要token鉴权的路由,如果没有在头部传递token,自定义401错误处理,表示没有权限,需要身份认证
app.use(function(ctx, next){
  return next().catch((err) => {
    if (401 == err.status) {
      ctx.status = 401;
      ctx.body = new ErrorModel('Protected resource, use Authorization header to get access')
    } else {
      throw err;
    }
  });
});

// 表示除了路由以 /users 开头的不需要token鉴权,其他路由都要
// koa-jwt会自动解析token,并将解析的token内容存放在ctx.state.user里面
app.use(jwt({ secret }).unless({ path: [/^\/users/] }))

// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())
app.use(uploads.routes(), uploads.allowedMethods())
app.use(download.routes(), download.allowedMethods())
app.use(jwtTest.routes(), jwtTest.allowedMethods())

// error-handling
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx)
});

module.exports = app

  • 测试,此处重新定义了一个新路由,用来测试jwt鉴权的效果,不传token

image.png

image.png

image.png

  • 传递token

image.png

image.png

image.png

upload() {
      const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIsIm5hbWUiOiJ0ZXN0MSIsInVzZXJuYW1lIjoidGVzdDEiLCJpYXQiOjE2MTkxNTc4MzcsImV4cCI6MTYxOTE5MzgzN30.-bUt82X5tCGMlmV46oHYzyC7wMvKxkdOfXxNPGBfniQ'

      axios.interceptors.request.use(
        config => {
          if (token) {
            // 判断token是否存在,存在就在header都加上toeken
            // Bearer 和 token之前要间隔一个空格
            config.headers.common["Authorization"] = "Bearer " + token
          }
          return config
        },
        err => {
          return Promise.reject(err)
        }
      )
      axios.post('http://localhost:3000/jwt-test').then(res => {
        console.log(res)
      })
}

image.png

局部中间件使用

  • 去掉app.js对koa-jwt的使用

image.png

image.png

  • 路由配置,此处也是对 /jwt-test路由 进行jwt-koa中间使用

image.png

image.png

image.png

image.png

const router = require('koa-router')()
const { SuccessModel } = require('./../model/resModel')
const { secret } = require('./../utils/singToken')
const jwt = require('koa-jwt')

router.prefix('/jwt-test')

router.post('/', jwt({ secret }), async ctx => {
  const userinfo = ctx.state.user
  ctx.body = new SuccessModel(userinfo)
})

module.exports = router

image.png

image.png

image.png

token过期,也是相当于没有token,无法进行权限认证,就会返回401状态码,

image.png

image.png