前言
最近再用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
接着我们来看下前端登录部分的实现
后台管理系统
登录
Tips : 用户名和密码暂时可以随便填。
前端核心的点就是将登录后的,从服务端传回的用户数据存入到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',