前后端分離:使用 token 登錄解決方案

登錄的時(shí)候獲得token,后面調(diào)用接口的時(shí)候會(huì)帶上token去請(qǐng)求服務(wù)器

前后端分離使用 Token 登錄解決方案

這篇文章寫一下前后端分離下的登錄解決方案,目前大多數(shù)都采用請(qǐng)求頭攜帶 Token 的形式。

開寫之前先捋一下整理思路:

  • 首次登錄時(shí),后端服務(wù)器判斷用戶賬號(hào)密碼正確之后,根據(jù)用戶id、用戶名、定義好的秘鑰、過期時(shí)間生成 token ,返回給前端;
  • 前端拿到后端返回的 token ,存儲(chǔ)在 localStroage 和 Vuex 里;
  • 前端每次路由跳轉(zhuǎn),判斷 localStroage 有無 token ,沒有則跳轉(zhuǎn)到登錄頁,有則請(qǐng)求獲取用戶信息,改變登錄狀態(tài);
  • 每次請(qǐng)求接口,在 Axios 請(qǐng)求頭里攜帶 token;
  • 后端接口判斷請(qǐng)求頭有無 token,沒有或者 token 過期,返回401;
  • 前端得到 401 狀態(tài)碼,重定向到登錄頁面。

我這里前端使用 Vue ,地址:vue-token-login

后端使用阿里的 egg,地址:egg-token-login

首先,我們先輕微封裝一下 Axios:

我把 Token 存在localStroage,檢查有無 Token ,每次請(qǐng)求在 Axios 請(qǐng)求頭上進(jìn)行攜帶

if (window.localStorage.getItem('token')) {
  Axios.defaults.headers.common['Authorization'] = `Bearer ` + window.localStorage.getItem('token')
}
復(fù)制代碼

使用 respone 攔截器,對(duì) 2xx 狀態(tài)碼以外的結(jié)果進(jìn)行攔截。

如果狀態(tài)碼是401,則有可能是 Token 過期,跳轉(zhuǎn)到登錄頁。

instance.interceptors.response.use(
  response => {
    return response
  },
  error => {
    if (error.response) {
      switch (error.response.status) {
        case 401:
          router.replace({
            path: 'login',
            query: { redirect: router.currentRoute.fullPath } // 將跳轉(zhuǎn)的路由path作為參數(shù),登錄成功后跳轉(zhuǎn)到該路由
          })
      }
    }
    return Promise.reject(error.response)
  }
)
復(fù)制代碼

定義路由:

const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'Index',
      component: Index,
      meta: {
        requiresAuth: true
      }
    },
    {
      path: '/login',
      name: 'Login',
      component: Login
    }
  ]
})
復(fù)制代碼

上面我給首頁路由加了 requiresAuth,所以使用路由鉤子來攔截導(dǎo)航,localStroage 里有 Token ,就調(diào)用獲取 userInfo 的方法,并繼續(xù)執(zhí)行,如果沒有 Token ,調(diào)用退出登錄的方法,重定向到登錄頁。

router.beforeEach((to, from, next) => {
  let token = window.localStorage.getItem('token')
  if (to.meta.requiresAuth) {
    if (token) {
      store.dispatch('getUser')
      next()
    } else {
      store.dispatch('logOut')
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    }
  } else {
    next()
  }
})
復(fù)制代碼

這里使用了兩個(gè) Vuex 的 action 方法,馬上就會(huì)說到 。

Vuex

首先,在 mutation_types 里定義:

export const LOGIN = 'LOGIN' // 登錄
export const USERINFO = 'USERINFO' // 用戶信息
export const LOGINSTATUS = 'LOGINSTATUS' // 登錄狀態(tài)
復(fù)制代碼

然后在 mutation 里使用它們:

const mutations = {
  [types.LOGIN]: (state, value) => {
    state.token = value
  },
  [types.USERINFO]: (state, info) => {
    state.userInfo = info
  },
  [types.LOGINSTATUS]: (state, bool) => {
    state.loginStatus = bool
  }
}
復(fù)制代碼

在之前封裝 Axios 的 JS里定義請(qǐng)求接口:

export const login = ({ loginUser, loginPassword }) => {
  return instance.post('/login', {
    username: loginUser,
    password: loginPassword
  })
}

export const getUserInfo = () => {
  return instance.get('/profile')
}
復(fù)制代碼

在 Vuex 的 actions 里引入:

import * as types from './types'
import { instance, login, getUserInfo } from '../api'
復(fù)制代碼

定義 action

export default {
  toLogin ({ commit }, info) {
    return new Promise((resolve, reject) => {
      login(info).then(res => {
        if (res.status === 200) {
          commit(types.LOGIN, res.data.token) // 存儲(chǔ) token
          commit(types.LOGINSTATUS, true)     // 改變登錄狀態(tài)為 
          instance.defaults.headers.common['Authorization'] = `Bearer ` + res.data.token // 請(qǐng)求頭添加 token
          window.localStorage.setItem('token', res.data.token)  // 存儲(chǔ)進(jìn) localStroage
          resolve(res)
        }
      }).catch((error) => {
        console.log(error)
        reject(error)
      })
    })
  },
  getUser ({ commit }) {
    return new Promise((resolve, reject) => {
      getUserInfo().then(res => {
        if (res.status === 200) {
          commit(types.USERINFO, res.data) // 把 userInfo 存進(jìn) Vuex
        }
      }).catch((error) => {
        reject(error)
      })
    })
  },
  logOut ({ commit }) { // 退出登錄
    return new Promise((resolve, reject) => {
      commit(types.USERINFO, null)        // 情況 userInfo
      commit(types.LOGINSTATUS, false)  // 登錄狀態(tài)改為 false
      commit(types.LOGIN, '')          // 清除 token
      window.localStorage.removeItem('token')
    })
  }
}
復(fù)制代碼

接口

這時(shí)候,我們?cè)撊懞蠖私涌诹恕?/p>

我這里用了阿里的 egg 框架,感覺很強(qiáng)大。

首先定義一個(gè) LoginController

const Controller = require('egg').Controller;
const jwt = require('jsonwebtoken'); // 引入 jsonwebtoken

class LoginController extends Controller {
  async index() {
    const ctx = this.ctx;
/*
 把用戶信息加密成 token ,因?yàn)闆]連接數(shù)據(jù)庫,所以都是假數(shù)據(jù)
正常應(yīng)該先判斷用戶名及密碼是否正確
*/
    const token = jwt.sign({       
      user_id: 1,      // user_id
      user_name: ctx.request.body.username // user_name
    }, 'shenzhouhaotian', { // 秘鑰
        expiresIn: '60s' // 過期時(shí)間
    });
    ctx.body = {             // 返回給前端
      token: token
    };
    ctx.status = 200;           // 狀態(tài)碼 200
  }
}

module.exports = LoginController;
復(fù)制代碼

UserController:

class UserController extends Controller {
  async index() {
    const ctx = this.ctx
    const authorization = ctx.get('Authorization');
    if (authorization === '') { // 判斷請(qǐng)求頭有沒有攜帶 token ,沒有直接返回 401
        ctx.throw(401, 'no token detected in http header "Authorization"');
    }
    const token = authorization.split(' ')[1];
    // console.log(token)
    let tokenContent;
    try {
        tokenContent = await jwt.verify(token, 'shenzhouhaotian');     //如果 token 過期或驗(yàn)證失敗,將返回401
        console.log(tokenContent)
        ctx.body = tokenContent     // token有效,返回 userInfo ;同理,其它接口在這里處理對(duì)應(yīng)邏輯并返回
    } catch (err) {
        ctx.throw(401, 'invalid token');
    }
  }
}
復(fù)制代碼

在 router.js 里定義接口:

module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  router.get('/profile', controller.user.index);
  router.post('/login', controller.login.index);
};
復(fù)制代碼

前端請(qǐng)求

接口寫好了,該前端去請(qǐng)求了。

這里我寫了個(gè)登錄組件,下面是點(diǎn)擊登錄時(shí)的 login 方法:

    login () {
      if (this.username === '') {
        this.$message.warning('用戶名不能為空哦~~')
      } else if (this.password === '') {
        this.$message.warning('密碼不能為空哦~~')
      } else {
        this.$store.dispatch('toLogin', {      // dispatch toLogin action
          loginUser: this.username,
          loginPassword: this.password
        }).then(() => {
          this.$store.dispatch('getUser')      // dispatch getUserInfo action
          let redirectUrl = decodeURIComponent(this.$route.query.redirect || '/')
          console.log(redirectUrl)
          // 跳轉(zhuǎn)到指定的路由
          this.$router.push({
            path: redirectUrl
          })
        }).catch((error) => {
          console.log(error.response.data.message)
        })
      }
    }
復(fù)制代碼

登錄成功后,跳轉(zhuǎn)到首頁之前重定向過來的頁面。

整體流程跑完了,實(shí)現(xiàn)的主要功能就是:

  1. 訪問登錄注冊(cè)之外的路由,都需要登錄權(quán)限,比如首頁,判斷有無token,有則訪問成功,沒有則跳轉(zhuǎn)到登錄頁面;
  2. 成功登錄之后,跳轉(zhuǎn)到之前重定向過來的頁面;
  3. token 過期后,請(qǐng)求接口時(shí),身份過期,跳轉(zhuǎn)到登錄頁,繼續(xù)第二步;這一步主要用了可以做7天自動(dòng)登錄等功能。

轉(zhuǎn)載自:http://www.yyyweb.com/5144.html

# [在vue中獲取token,并將token寫進(jìn)header的方法](https://www.cnblogs.com/xiaoyingainiaa/p/9810610.html)

https://www.cnblogs.com/xiaoyingainiaa/p/9810610.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容