nuxt框架中路由鑑權——Koa和Session

AdityaSui發表於2018-05-07

引子

部落格的後臺管理頁面需要有登入系統,所以考慮做一下路由鑑權,實現方式也是 Nuxt 官網給出栗子來改寫,順便也將前後端路由給統一了。

路由攔截

前端方面主要通過利用 Nuxt 的中介軟體來做路由攔截,這裡也是需要 Vuex 狀態樹來做。

middleware

middleware/auth.js
複製程式碼

export default function ({ store, redirect }) {
  
  if (!store.state.user) {
    return redirect('/login')
  }
}

複製程式碼

通過對狀態樹上的使用者資訊是否存在來鑑權,來對頁面進行重定向

layouts/admin.vue
複製程式碼
export default {
    middleware: 'auth',
    components: {
      AdminAside
    }
  }
複製程式碼

在後臺管理系統的頁面佈局上新增 中介軟體

nuxtServerInit

NuxtJs 的渲染流程中,當請求打入時,最先呼叫的即是 nuxtServerInit 方法,可以通過這個方法預先將伺服器的資料儲存。

我們可以利用該方法來接收儲存使用者資訊的 Session 資訊。

nuxtServerInit ({ commit }, { req, res }) {
    if (req.session && req.session.user) {
      const { username, password } = req.session.user
      const user = {
        username,
        password
      }

      commit('SET_USER', user)
    }
  },
複製程式碼

當應用完畢時,一些我們從伺服器獲取到的資料就會被填充到這個狀態樹 (store) 上。

按照 NuxtJs 官網給出的栗子來看,到這裡基本算把頁面中路由鑑權部分寫完了,接下來是對伺服器端該部分程式碼的寫作

使用Koa和koa-session

Koa和koa-session

後端程式碼我採用是 Koa 框架,以及 koa-session 來對Session做處理。

在新建 nuxt 專案的時候直接選用 Koa 框架即可

 vue init nuxt/koa
複製程式碼

相關依賴

npm install koa-session
複製程式碼

在 server.js 中改寫

import Koa from 'koa'
import { Nuxt, Builder } from 'nuxt'
// after end

import session from 'koa-session'


async function start () {
  const app = new Koa()
  const host = process.env.HOST || '127.0.0.1'
  const port = process.env.PORT || 7998

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

  // Instantiate nuxt.js
  const nuxt = new Nuxt(config)

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

  // body-parser
  app.use(bodyParser())

  // mongodb

  // session
  app.keys = ['some session']

  const CONFIG = {
    key: 'SESSION', /** (string) cookie key (default is koa:sess) */
    /** (number || 'session') maxAge in ms (default is 1 days) */
    /** 'session' will result in a cookie that expires when session/browser is closed */
    /** Warning: If a session cookie is stolen, this cookie will never expire */
    maxAge: 86400000,
    overwrite: true, /** (boolean) can overwrite or not (default true) */
    httpOnly: true, /** (boolean) httpOnly or not (default true) */
    signed: true, /** (boolean) signed or not (default true) */
    rolling: false /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. default is false **/
  }
  app.use(session(CONFIG, app))

  // routes

  app.use(async (ctx, next) => {
    await next()
    ctx.status = 200 // koa defaults to 404 when it sees that status is unset
    return new Promise((resolve, reject) => {
      ctx.res.on('close', resolve)
      ctx.res.on('finish', resolve)
      nuxt.render(ctx.req, ctx.res, promise => {
        // nuxt.render passes a rejected promise into callback on error.
        promise.then(resolve).catch(reject)
      })
    })
  })

  app.listen(port, host)
  console.log('Server listening on ' + host + ':' + port) // eslint-disable-line no-console
}

start()

複製程式碼

對於 koa-session 的用法,可以參考:從koa-session中介軟體學習cookie與session

登入路由

// 登入
router.post('/api/login', async (ctx, next) => {
  const { username, password } = ctx.request.body
  let user,
    match

  try {
    user = await Admin.findOne({ user: username }).exec()
    if (user) {
      match = await user.comparePassword(password, user.password)
    }
  } catch (e) {
    throw new Error(e)
  }

  if (match) {
    ctx.session.user = {
      _id: user._id,
      username: user.user,
      nickname: user.nickname,
      role: user.role
    }

    console.log(ctx.session)
    return (ctx.body = {
      success: true,
      data: {
        username: user.user,
        nickname: user.nickname
      }
    })
  }

  return (ctx.body = {
    success: false,
    err: '密碼錯誤'
  })
})
複製程式碼

寫到這裡,整個功能流程基本完畢了,也非常的順暢,但是對我來說一帆風順的程式碼是不存在的。

session is not defined

問題

nuxtServerInit ({ commit }, { req, res }) {
    if (req.session && req.session.user) { // res.session is not defined
      const { username, password } = req.session.user
      const user = {
        username,
        password
      }

      commit('SET_USER', user)
    }
  }
複製程式碼

nuxtServerInit 獲取不到有關 session 的任何資訊,然而其他的 api 均可獲取到 session ,當時由於苦苦找不到原因,一度懷疑栗子有問題。。

原因

最終的問題還是因為自己的粗心,忽視了一些細節,在官網給出的栗子中:

app.post('/api/login', function (req, res) {
  if (req.body.username === 'demo' && req.body.password === 'demo') {
    req.session.authUser = { username: 'demo' }
    return res.json({ username: 'demo' })
  }
  res.status(401).json({ error: 'Bad credentials' })
})
複製程式碼

它將 session 儲存在了 req.session , 所以在 nuxtServerInit session也確實存在於 req.session ,而我使用的 Koa2 和 Koa-sessionKoa-sessioncookie 解析到了 ctx.session , 它並不存在於 req.session

解決

所以在將 nuxt.render 注入的時候,將 session 新增進 request

app.use(async (ctx, next) => {
    await next()
    ctx.status = 200 // koa defaults to 404 when it sees that status is unset
    ctx.req.session = ctx.session
    return new Promise((resolve, reject) => {
      ctx.res.on('close', resolve)
      ctx.res.on('finish', resolve)
      nuxt.render(ctx.req, ctx.res, promise => {
        // nuxt.render passes a rejected promise into callback on error.
        promise.then(resolve).catch(reject)
      })
    })
  })
複製程式碼

大功告成!

相關文章