登錄的時候獲得token,后面調用接口的時候會帶上token去請求服務器
前后端分離使用 Token 登錄解決方案
這篇文章寫一下前后端分離下的登錄解決方案,目前大多數都采用請求頭攜帶 Token 的形式。
開寫之前先捋一下整理思路:
- 首次登錄時,后端服務器判斷用戶賬號密碼正確之后,根據用戶id、用戶名、定義好的秘鑰、過期時間生成 token ,返回給前端;
- 前端拿到后端返回的 token ,存儲在 localStroage 和 Vuex 里;
- 前端每次路由跳轉,判斷 localStroage 有無 token ,沒有則跳轉到登錄頁,有則請求獲取用戶信息,改變登錄狀態;
- 每次請求接口,在 Axios 請求頭里攜帶 token;
- 后端接口判斷請求頭有無 token,沒有或者 token 過期,返回401;
- 前端得到 401 狀態碼,重定向到登錄頁面。
我這里前端使用 Vue ,地址:vue-token-login
后端使用阿里的 egg,地址:egg-token-login
首先,我們先輕微封裝一下 Axios:
我把 Token 存在localStroage,檢查有無 Token ,每次請求在 Axios 請求頭上進行攜帶
if (window.localStorage.getItem('token')) {
Axios.defaults.headers.common['Authorization'] = `Bearer ` + window.localStorage.getItem('token')
}
復制代碼
使用 respone 攔截器,對 2xx 狀態碼以外的結果進行攔截。
如果狀態碼是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 ,調用退出登錄的方法,重定向到登錄頁。
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 方法,馬上就會說到 。
Vuex
首先,在 mutation_types 里定義:
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)
})
}
}
復制代碼
登錄成功后,跳轉到首頁之前重定向過來的頁面。
整體流程跑完了,實現的主要功能就是:
- 訪問登錄注冊之外的路由,都需要登錄權限,比如首頁,判斷有無token,有則訪問成功,沒有則跳轉到登錄頁面;
- 成功登錄之后,跳轉到之前重定向過來的頁面;
- token 過期后,請求接口時,身份過期,跳轉到登錄頁,繼續第二步;這一步主要用了可以做7天自動登錄等功能。
轉載自:http://www.yyyweb.com/5144.html
# [在vue中獲取token,并將token寫進header的方法](https://www.cnblogs.com/xiaoyingainiaa/p/9810610.html)