vue + koa2 實現 session | token 登錄狀態驗證

Session 登錄與 Token 登錄的區別

一、Session 登錄是在服務器端生成用戶相關 session 數據,發給客戶端 session_id 存放到 cookie 中,這樣在客戶端請求時帶上 session_id 就能夠驗證服務器端是否存在 session 數據,以此完成用戶認證。這種認證方式,能夠更好的在服務端對會話進行控制,安全性比較高(session_id 隨機),可是服務端須要存儲 session 數據(如內存或數據庫),這樣無疑增長維護成本和減弱可擴展性(多臺服務器)。 CSRF 攻擊通常基於 cookie。另外,若是是原生 app 使用這種服務接口,由於沒有瀏覽器 cookie 功能,因此接入會相對麻煩。
二、基於 token 的用戶認證是一種服務端無狀態的認證方式,服務端不用存放 token 數據。用戶驗證後,服務端生成一個 token(hash 或 encrypt)發給客戶端,客戶端能夠放到 cookie 或 localStorage 中,每次請求時在 Header 中帶上 token,服務端收到 token,經過驗證後便可確認用戶身份。這種方式相對 cookie 的認證方式就簡單一些,服務端不用存儲認證數據,易維護擴展性強,token 存在 localStorage 可避免 CSRF,web 和 app 應用都比較簡單。不過這種方式在加密或解密的時候會有一些性能開銷(好像也不是很大),有些對稱加密存在安全隱患(aes cbc 字節翻轉攻擊)。前端

koa + session 登錄驗證

一、首先要安裝 koa-sisson 包

npm install koa-session -S

koa-session 其實是經過 cookie 來保存信息的。koa-session 在服務器上生成一個信息,經過加密後,以 cookie 的形式發送到用戶的瀏覽器,在用戶瀏覽器上能夠看到是一個加密的 cookie 字段,而後在服務端路由中經過 ctx.session.xx 來獲取 xx 這個信息。而當每次當用戶發送請求時候,就能夠獲取到這個 cookie,koa-sesscion 會內部會幫咱們解密爲最初的存儲的信息,因而咱們能夠經過判斷這個 cookie 是存在來校驗用戶是否已經登陸了。vue

二、app.js 中初始化

const Koa = require('koa')
const app = new Koa()
const session = require('koa-session')
const bodyParser = require('koa-bodyparser')
const Router = require('koa-router')
const router = new Router()

const CONFIG = {
  key: 'koa:sess', /** (string) cookie key (default is koa:sess) cookie 的Name */
  /** (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, /** cookie 的過時時間 */
  autoCommit: true, /** (boolean) automatically commit headers (default true) */
  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) */
  renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/
}
app.keys = ['login secret'] // 加密密鑰
app.use(session(CONFIG, app));

app.use(bodyParser())
app.use(router.routes()).use(router.allowedMethods())

三、登錄路由

router.post('/login', async (ctx) => {
  try {
    const data = ctx.request.body.data
    const { username, password } = data
    if (true) {
      // 保存登陸狀態,這句代碼會在瀏覽器中生成一個以 "koa:sess" 爲 Name 的 cookie
      ctx.session.userInfo = {username: '', userID: ''}
      ctx.body = {code: 1, message: '登錄成功'}
    } else {
      ctx.body = {code: 0, message: '帳號或密碼錯誤'}
    }
  } catch(err) {
    throw new Error(err)
  }
})

// 前端
axios.post('/login', {username: '', password: ''}).then(res => {})

四、校驗是否已登錄

router.get('/getSession', async (ctx) => {
  try {
    if (ctx.session.userInfo) {
      ctx.body = {code: 1, message: '已登錄'}
    } else {
      ctx.body = {code: 0, message: '未登錄'}
      // 跳轉到登陸頁
      // ctx.response.redirect('/login') 
    }
  } catch(err) {
    throw new Error(err)
  }
})

// 前端
axios.get('/getSession').then(res => {})

五、退出登錄

router.post('/logout', async (ctx) => {
  try {
    // 將登陸信息清空
    ctx.session = null
    // 跳轉到登陸頁或網站首頁
    ctx.response.redirect('/')
  } catch(err) {
    throw new Error(err)
  }
})

// 前端
axios.post('/logout').then(res => {})

koa + token 登錄驗證

一、安裝 jsonwebtoken 包

npm install jsonwebtoken -S

二、app.js 初始化

const Koa = require('koa')
const app = new Koa()
const jwt = require('jsonwebtoken')
const bodyParser = require('koa-bodyparser')
const Router = require('koa-router')
const router = new Router()
const tokenConfig = {privateKey: 'xxxxxxxxxxxx'} // 加密密鑰

app.use(bodyParser())
app.use(router.routes()).use(router.allowedMethods())

三、登錄路由

router.post('/login', async (ctx) => {
  try {
    const data = ctx.request.body.data
    const { username, password } = data
    if (true) {
      const userInfo = {username: '', userID: ''}
      const token = jwt.sign(userInfo, tokenConfig.privateKey, {expiresIn: '7d'}) // 簽發 token, 7天有效期
      ctx.body = {code: 1, message: '登錄成功', data: {token: 'Bearer ' + token}}
    } else {
      ctx.body = {code: 0, message: '帳號或密碼錯誤'}
    }
  } catch(err) {
    throw new Error(err)
  }
})

前端登錄ios

axios.post('/login', {username: '', password: ''}).then(res => {
  if (res.data.code === 1) {
    localStorage.setItem('token', res.data.data.token)
    // vuex 存儲 userInfo 和登錄狀態
    store.commit('SET_USERINFO', {userInfo: res.data.data.userInfo, status: true})
  }
})

四、校驗是否已登錄

router.get('/getUserInfo', async (ctx) => {
  try {
    const token = ctx.get('Authorization') // 獲取請求 Header 中 Authorization 值
    let userInfo = {}
    if (token === '') {
      ctx.body = {code: 0, message: '未登錄'}
    } else {
      try {
        userInfo = jwt.verify(token.split(' ')[1], tokenConfig.privateKey) // 驗證 token
        ctx.body = {code: 1, message: '已登錄', data: {userInfo: userInfo: loginStatus: true}}
      } catch(err) {
        // token 過時或無效
        ctx.body = {code: 0, message: '未登錄', data: {userInfo: {}: loginStatus: false}}}
      }
    }
  } catch(err) {
    throw new Error(err)
  }
})

要每次的請求中都帶上 token 信息,要給 axios 設置請求攔截程序員

// 請求攔截,在每次請求中的 header 中帶上 token
axios.interceptors.request.use(config => {
  let token = localStorage.getItem('token')
  if (token) {
    config.headers.common.Authorization = token
  }
  return config
}, error => {
  return Promise.reject(error);
})

每次進入頁面以前要判斷下是否已登錄,是否有權限進入該頁面,以前我是在每一個頁面的 created 鉤子函數中去請求 '/getUserInfo' 判斷是否以登錄,這樣作繁瑣,而且頁面會先呈現一下,而後一閃而過(驗證不過的狀況下),在路由鉤子函數中可全局配置web

// 路由守衛, 在跳轉以前執行
router.beforeEach((to, from, next) => {
  let token = localStorage.getItem('token')
  let requireAuth = to.meta.requireAuth // VueRouter 裏配置頁面是否須要登錄進入
  let root = to.meta.root // VueRouter 裏配置頁面是否須要登錄且管理員權限進入
  if (!token) {
    // vuex 清除 userInfo 和登錄狀態
    store.commit('SET_USERINFO', {userInfo: {}, status: false})
    requireAuth ? next({path: '/'}) : next()
  } else {
    axios.get(API.getUserInfo).then(res => {
      // vuex 存儲 userInfo 和登錄狀態
      store.commit('SET_USERINFO', {userInfo: res.data.userInfo, status: res.data.loginStatus})
      if (requireAuth) {
        if (!res.data.loginStatus || (root && !res.data.userInfo.root)) {
          next({path: '/'})
        } else {
          next()
        }
      } else {
        next()
      }
    })
  }
})

/** VueRouter
{
  path: '/admin',
  name: 'admin',
  meta: {
    requireAuth: true,
    root: true
  },
}
*/

五、退出登錄

由於服務器端並無存儲用戶登錄相關信息,只與前端是否存在 token 或是否能驗證經過有關,因此退出登錄就將 token 清除便可vuex

methods: {
  logout() {
    localStorage.removeItem('token')
    // vuex 清除登錄信息
    store.commit('SET_USERINFO', {userInfo: {}, status: false})
    if (this.$route.path !== '/') {
      this.$router.push({path: '/'})
    }
  }
}

程序員詞彙學習網:www.english4coder.com數據庫

相關文章
相關標籤/搜索