美文网首页
nuxt+koa-session2+redis 实现用户登录

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

作者: zshawk1982 | 来源:发表于2019-08-28 11:47 被阅读0次

前言

最近再用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

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

<template>
  <div class="login-wrap">
    <div class="ms-login">
      <div class="ms-title">后台管理系统</div>
      <el-form
        ref="loginForm"
        :model="param"
        :rules="rules"
        label-width="0px"
        class="ms-content"
      >
        <el-form-item prop="username">
          <el-input v-model="param.username" placeholder="username">
            <el-button slot="prepend" icon="el-icon-custom-user"></el-button>
          </el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input
            v-model="param.password"
            type="password"
            placeholder="password"
            @keyup.enter.native="submitForm()"
          >
            <el-button
              slot="prepend"
              icon="el-icon-custom-password"
            ></el-button>
          </el-input>
        </el-form-item>
        <div class="login-btn">
          <el-button type="primary" @click="submitForm()">登录</el-button>
        </div>
        <p class="login-tips">Tips : 用户名和密码暂时可以随便填。</p>
      </el-form>
    </div>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'
export default {
  data() {
    return {
      param: {
        username: 'admin',
        password: '123123'
      },
      rules: {
        username: [
          { required: true, message: '请输入用户名', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 3, message: '长度在 3 个字符以上', trigger: 'blur' }
        ]
      }
    }
  },
  methods: {
    ...mapMutations(['set_user']),
    submitForm() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.$axios
            .post('/users/signin', this.param, { withCredentials: true })
            .then((res) => {
              if (res.data.result === 'success') {
                this.$message.success('登录成功')
                //利用vuex的mutation,将登录用户的数据存入vuex中的state中
                this.set_user(res.data.user)
                this.$router.push('/dashboard')
              } else {
                this.$message.warning('登录失败')
              }
            })
        } else {
          this.$message.error('请重新输入账号和密码')
          return false
        }
      })
    }
  }
}
</script>

<style scoped>
.login-wrap {
  position: relative;
  width: 100vw;
  height: 100vh;
  background-image: url('~assets/img/login-bg.jpg');
  background-size: 100%;
}
.ms-title {
  width: 100%;
  line-height: 50px;
  text-align: center;
  font-size: 20px;
  color: #666;
  border-bottom: 1px solid #ddd;
}
.ms-login {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 350px;
  margin: -190px 0 0 -175px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.3);
  overflow: hidden;
}
.ms-content {
  padding: 30px 30px;
}
.login-btn {
  text-align: center;
}
.login-btn button {
  width: 100%;
  height: 36px;
  margin-bottom: 10px;
}
.login-tips {
  font-size: 12px;
  line-height: 30px;
  color: #fff;
}
</style>

前端核心的点就是将登录后的,从服务端传回的用户数据存入到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',

<template>
  <div class="wrapper">
    <v-head :auth-user="authUser"></v-head>
    <v-sidebar></v-sidebar>
    <div class="content-box">
      <div class="content">
        <transition name="move" mode="out-in">
          <nuxt-child />
        </transition>
        <el-backtop target=".content"></el-backtop>
      </div>
    </div>
  </div>
</template>

<script>
import vHead from '~/components/Header.vue'
import vSidebar from '~/components/Sidebar.vue'
export default {
  middleware: 'auth',
  components: {
    vHead,
    vSidebar
  },
  data() {
    return {
      authUser: null
    }
  },
  asyncData({ app, store }) {
    console.log('dashboard----', store.state.authUser)
//这里将state中的authUser传给v-head组件,v-head组件去显示登录用户信息
    return {
      authUser: store.state.authUser
    }
  }
}
</script>
<style lang="scss" scoped></style>

相关文章

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

    前言 最近再用nuxt2+elementui编写用户登录和注销,在实际操作过程中发现了许多问题,目前关于登录的会话...

  • node+ajax实战案例(4)

    4.用户登录实现 #4.1.用户登录实现思路 1 用户输入登录信息,点击登录的时候把用户登录的这些信息收集起来,然...

  • springMVC+request.session实现用户登录和

    用springmvc mybatis实现用户登录登出功能,使用session保持登录状态,并实现禁止未登录的用户访...

  • 第十二周

    1、搭建vsftpd,并实现虚拟用户 我们登录FTP有三种方式,匿名登录、本地用户登录和虚拟用户登录。 匿名登录:...

  • 2019-05-27 第十周作业

    1、实现sshd免密登录 2、编译安装dropbear实现SSH登录 3、实现单个用户及用户组使用sudo执行所有...

  • Apache安全--用户登录验证

    用户登录验证登录验证介绍登录验证实现一、登录验证当用户访问网站或者网站某个目录时,如果希望用户提供授权才能登录,那...

  • 登录界面效果图

    1.1用户登录界面 1.2收银员登录界面 1.3库管员登录界面 2.登录界面实现的功能描述 可实现不同用户类型的自...

  • 从零学习vue之登录页面demo

    目标:实现“主页”、“用户登录”、“用户登出”和“用户信息”四个页面,主页根据用户登录情况,展现不同信息,用户信息...

  • Flutter:一份无瑕疵的登录页代码

    最终实现的效果及需要实现的内容,如下图所示: 用户名登录的UI图.png 用户名登录的流程图如下: 用户名登录的流...

  • 猴子也能懂的springboot教程(四) - springbo

    摘要 本文介绍springboot使用拦截器,实现用户登录权限管理 前言 本demo实现功能:判断用户是否否登录 ...

网友评论

      本文标题:nuxt+koa-session2+redis 实现用户登录

      本文链接:https://www.haomeiwen.com/subject/jxfpectx.html