nuxt+koa-session2+redis 实现用户登录

前言

最近再用nuxt2+elementui编写用户登录和注销,在实际操作过程中发现了许多问题,目前关于登录的会话保存除了用token就是用session,我依然采用传统的session方式,同时由于我nuxt的服务端使用的是koa2,所以打算采用koa的session相关插件来实现此功能,这时市面上主流的就两种,一种是koa-session,一种是koa-session2。首先我们需要理清二者的区别,从而选择使用哪一个。
koa-session和koa-session2在不采用外部存储的时候,koa-session会直接将要保存的数据存入客户端cookie中,koa-session2则不同,它是将要保存的数据存入内存中,(注意:koa-session和koa-session2都会保存cookie到客户端,该cookie类似于sessionid用于去找保存的内容,对于要保存的数据内容,存储的地方不一样)从这一点看,koa-session单纯的存入客户端cookie中,不利于数据的安全性,如果保存的数据中有密码之类,则更加不安全,相比koa-session2,它将数据存入服务端内存中,安全性较高,但相对而言,但是只要服务器重启,内存中保存的session都会释放,所以即使客户端的cookie未失效,也找不到服务端的相关信息。这两者的优缺点明确之后,我们才可以选择对应的模块,我这里选择使用koa-session2,ok,既然决定了使用方法,我们就来看下怎么使用。
我这里不光使用了koa-session2,同时使用了redis作为session数据的外部保存,不再将数据默认存储到内存,而是保存到redis,正因为使用了redis,所以使用了ioredis模块来连接本地的redis服务

正文

安装koa-session2

npm install --save koa-session2
npm install --save ioredis

然后在nuxt项目中的server/index.js中使用

// const Koa = require('koa')
import Koa from 'koa'
import session from 'koa-session2'
import Store from './util/redisStore'
import users from './interface/users'
import posts from './interface/posts'
const bodyParser = require('koa-bodyparser')

const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')

const app = new Koa()

//设置配置session的加密字符串,可以任意字符串
app.keys = ['some secret hurr']

// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
// const redisStore = require('./util/redisStore')
config.dev = app.env !== 'production'

// 获取数据连接和初始化方法
const { connect, initSchema } = require('./dbs/init')

async function start() {
  // Instantiate nuxt.js
  const nuxt = new Nuxt(config)

  const {
    host = process.env.HOST || '127.0.0.1',
    port = process.env.PORT || 3000
  } = nuxt.options.server

  // Build in development
  if (config.dev) {
    const builder = new Builder(nuxt)
    await builder.build()
  } else {
    await nuxt.ready()
  }

  // 立即执行函数,连接数据库
  ;(async () => {
    await connect()
    initSchema()
  })()

  // 配置session
  app.use(
    session({
      store: new Store()
    })
  )

  // 配置解析post的bodypaser
  app.use(bodyParser())
  // 配置服务端路由
  app.use(users.routes()).use(users.allowedMethods())
  app.use(posts.routes()).use(posts.allowedMethods())

  app.use((ctx) => {
    ctx.status = 200
    ctx.respond = false // Bypass Koa's built-in response handling
    // ctx.req.session = ctx.session
    ctx.req.ctx = ctx // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
    nuxt.render(ctx.req, ctx.res)
  })

  app.listen(port, host)
  consola.ready({
    message: `Server listening on http://${host}:${port}`,
    badge: true
  })
}

start()

这里的./util/redisStore.js文件的内容:

import Redis from 'ioredis'
import { Store } from 'koa-session2'

export default class RedisStore extends Store {
  constructor() {
    super()
    this.redis = new Redis()
  }
//根据sessionid从redis取数据
  async get(sid) {
    const data = await this.redis.get(`SESSION:${sid}`)
    return JSON.parse(data)
  }
//根据sessionid往redis中放数据
  async set(session, opts) {
    if (!opts.sid) {
      opts.sid = this.getID(24)
    }
    console.log(`SESSION:${opts.sid}`)
    await this.redis.set(`SESSION:${opts.sid}`, JSON.stringify(session))
    return opts.sid
  }
//根据sessionid从redis删除数据
  async destroy(sid) {
    await this.redis.del(`SESSION:${sid}`)
  }
}

./util/redisStore.js文件的内容不是我自己写的,参考了官网的写法,网上太多乱七八糟的写法了,还是官网靠谱。
当然这里我遇到一个问题,那就是使用原版的koa-session2会出错,出错的原因在于我使用了babel-node,不是原始的node,因而,我是用的koa-session2是特殊的koa-session2@babel版的,
所以我这里重新安装了koa-session2@babel

npm install --save koa-session2@babel

同时参考了官网的写法,一定要参考官网,上面的store的配置文件才妥妥的。
到此,所有的配置都ok了,接着我们来看登录的服务端的内容,我是将登录的内容写在/server/interface/users.js中

const Router = require('koa-router')
// const axios = require('axios')
const mongoose = require('mongoose')
const router = new Router({ prefix: '/users' })

// 管理员登录
router.post('/signin', async (ctx, next) => {
  console.log('1.----signin')
  const UsersModel = mongoose.model('users')
//从前端获取登录的表单信息
  const loginInfo = ctx.request.body
//利用mongoose从数据库中查询用户信息,排除密码不查
  const result = await UsersModel.findOne(
    {
      username: loginInfo.username,
      password: loginInfo.password,
      type: 'administrator'
    },
    {
      _id: 1,
      username: 1,
      tel: 1,
      email: 1,
      createAt: 1,
      lastLoginAt: 1,
      type: 1
    }
  ).then((res) => {
    if (res) {
      // 将数据库中查询出的用户信息存入session中
      console.log('userinfo***********res', res)
      ctx.session.user = res
      return {
        result: 'success',
        user: res
      }
    } else {
      return {
        result: 'failed'
      }
    }
  })
  ctx.body = result
})

// 管理员登出
router.get('/signout', (ctx, next) => {
  console.log('2.----signout')
  ctx.session = null
  ctx.body = {
    result: 'success'
  }
})
export default router

接着我们来看下前端登录部分的实现







前端核心的点就是将登录后的,从服务端传回的用户数据存入到vuex的state中,就是this.set_user(res.data.user)这句话,但是使用vuex会有一个bug,那就是在页面刷新的时候,vuex中的state的内容会丢失,所以我们在传统的vue项目中,会再采用localstorage去保存,这样在刷新之后,state可以利用localstorage来还原,但是因为我们使用的基于ssr的nuxt项目,nuxt提供了一个方法沟通前后端,那就是nuxtServerInit,该方法在/store/index.js中

export const state = () => ({
  authUser: null
})

export const mutations = {
  set_user(state, user) {
    state.authUser = user
  }
}
export const actions = {
  // 该方法用于解决当页面刷新时,vuex内容丢失,
//同时由于每次刷新都会调用nuxtServerInit方法,
//这时,我们可以将session取出来再次放入state中
  nuxtServerInit({ commit }, { req, app }) {
    // 将session中的用户存储到vuex的state中
    console.log('**********nuxtserverInit')
    if (req.ctx.session.user) {
      console.log('store---', req.ctx.session.user)
      commit('set_user', req.ctx.session.user)
    }
  }
}

注意:req.ctx.session.user,因为session是放在koa的ctx中的,nuxt2考虑到了这点,所以将ctx放入到了req中,参考server/index.js中的这段:

app.use((ctx) => {
    ctx.status = 200
    ctx.respond = false // Bypass Koa's built-in response handling
    ctx.req.ctx = ctx // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
    nuxt.render(ctx.req, ctx.res)
  })

最后

最后我们还需要解决一个问题,就可以开心的使用了,那就是权限认证,当我访问其他页面的时候,如果是未登录用户,那么会自动跳转到login.vue去显示,所以这时我们需要编写一个nuxt的中间件(middleware):/middleware/auth.js

export default function({ app, req, redirect, route, store }) {
  // 该中间件用于判断state中用户是否存在,如果不存在,则跳回登录页面
  console.log('middleware--auth')
  console.log('************req', req.ctx.session)
  //虽然这里在刷新后,能取到req.ctx.session,但是单纯的router.push类的客户端跳转
  //是不会走服务端的,所以是拿不到req.ctx.session的,所以只能使用state来判断
  if (!store.state.authUser) {
    redirect('/login')
  }
}

编写完的中间件auth.js,我们将它放在指定的路由上,当刷新访问这个路由的时候,会经过该中间件进行判断,我这里是将中间件放在/pages/index.vue中,middleware: 'auth',






你可能感兴趣的:(nuxt+koa-session2+redis 实现用户登录)