這篇文章寫一下先後端分離下的登陸解決方案,目前大多數都採用請求頭攜帶 Token 的形式。前端
開寫以前先捋一下整理思路:vue
我這裏前端使用 Vue ,地址:vue-token-loginios
後端使用阿里的 egg,地址:egg-token-logingit
我把 Token 存在localStroage,檢查有無 Token ,每次請求在 Axios 請求頭上進行攜帶github
if (window.localStorage.getItem('token')) {
Axios.defaults.headers.common['Authorization'] = `Bearer ` + window.localStorage.getItem('token')
}
複製代碼
使用 respone 攔截器,對 2xx 狀態碼之外的結果進行攔截。web
若是狀態碼是401,則有多是 Token 過時,跳轉到登陸頁。數據庫
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 } // 將跳轉的路由path做爲參數,登陸成功後跳轉到該路由
})
}
}
return Promise.reject(error.response)
}
)
複製代碼
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Index',
component: Index,
meta: {
requiresAuth: true
}
},
{
path: '/login',
name: 'Login',
component: Login
}
]
})
複製代碼
上面我給首頁路由加了 requiresAuth,因此使用路由鉤子來攔截導航,localStroage 裏有 Token ,就調用獲取 userInfo 的方法,並繼續執行,若是沒有 Token ,調用退出登陸的方法,重定向到登陸頁。json
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()
}
})
複製代碼
這裏使用了兩個 Vuex 的 action 方法,立刻就會說到 。後端
首先,在 mutation_types 裏定義:api
export const LOGIN = 'LOGIN' // 登陸
export const USERINFO = 'USERINFO' // 用戶信息
export const LOGINSTATUS = 'LOGINSTATUS' // 登陸狀態
複製代碼
而後在 mutation 裏使用它們:
const mutations = {
[types.LOGIN]: (state, value) => {
state.token = value
},
[types.USERINFO]: (state, info) => {
state.userInfo = info
},
[types.LOGINSTATUS]: (state, bool) => {
state.loginStatus = bool
}
}
複製代碼
在以前封裝 Axios 的 JS裏定義請求接口:
export const login = ({ loginUser, loginPassword }) => {
return instance.post('/login', {
username: loginUser,
password: loginPassword
})
}
export const getUserInfo = () => {
return instance.get('/profile')
}
複製代碼
在 Vuex 的 actions 裏引入:
import * as types from './types'
import { instance, login, getUserInfo } from '../api'
複製代碼
定義 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) // 存儲 token
commit(types.LOGINSTATUS, true) // 改變登陸狀態爲
instance.defaults.headers.common['Authorization'] = `Bearer ` + res.data.token // 請求頭添加 token
window.localStorage.setItem('token', res.data.token) // 存儲進 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 存進 Vuex
}
}).catch((error) => {
reject(error)
})
})
},
logOut ({ commit }) { // 退出登陸
return new Promise((resolve, reject) => {
commit(types.USERINFO, null) // 狀況 userInfo
commit(types.LOGINSTATUS, false) // 登陸狀態改成 false
commit(types.LOGIN, '') // 清除 token
window.localStorage.removeItem('token')
})
}
}
複製代碼
這時候,咱們該去寫後端接口了。
我這裏用了阿里的 egg 框架,感受很強大。
首先定義一個 LoginController :
const Controller = require('egg').Controller;
const jwt = require('jsonwebtoken'); // 引入 jsonwebtoken
class LoginController extends Controller {
async index() {
const ctx = this.ctx;
/*
把用戶信息加密成 token ,由於沒鏈接數據庫,因此都是假數據
正常應該先判斷用戶名及密碼是否正確
*/
const token = jwt.sign({
user_id: 1, // user_id
user_name: ctx.request.body.username // user_name
}, 'shenzhouhaotian', { // 祕鑰
expiresIn: '60s' // 過時時間
});
ctx.body = { // 返回給前端
token: token
};
ctx.status = 200; // 狀態碼 200
}
}
module.exports = LoginController;
複製代碼
UserController:
class UserController extends Controller {
async index() {
const ctx = this.ctx
const authorization = ctx.get('Authorization');
if (authorization === '') { // 判斷請求頭有沒有攜帶 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 過時或驗證失敗,將返回401
console.log(tokenContent)
ctx.body = tokenContent // token有效,返回 userInfo ;同理,其它接口在這裏處理對應邏輯並返回
} catch (err) {
ctx.throw(401, 'invalid token');
}
}
}
複製代碼
在 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);
};
複製代碼
接口寫好了,該前端去請求了。
這裏我寫了個登陸組件,下面是點擊登陸時的 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)
// 跳轉到指定的路由
this.$router.push({
path: redirectUrl
})
}).catch((error) => {
console.log(error.response.data.message)
})
}
}
複製代碼
登陸成功後,跳轉到首頁以前重定向過來的頁面。
總體流程跑完了,實現的主要功能就是: