作者:小土豆
博客園:https://www.cnblogs.com/HouJiao/
掘金:https://juejin.im/user/2436173500265335
微信公眾號:不知名寶藏程序媛(關注"不知名寶藏程序媛"免費領取前端電子書籍。文章公眾號首發,關注公眾號第一時間獲取最新文章。)
作者文章的內容均來源於自己的實踐,如果覺得有幫助到你的話,可以點贊❤️給個鼓勵或留下寶貴意見
前言
在上一篇 Vue結合Django-Rest-Frameword實現登錄認證(一) 文章中,我們利用token
實現了一個非常基礎的用戶登錄認證
功能。
那這一節需要對前面實現的內容進行優化:
1. 優化axios:請求封裝、認證信息的封裝
2. 注銷
3. 設置token過期時間
優化axios
axios
的優化就是對axios
進行一個封裝,單獨抽離出來一個模塊,負責編寫請求的API
,在組件中只需要調用這個API
傳入對應的參數,就能在請求發送
的同時實現認證信息的設置
。
// 代碼位置:/src/utils/request.js
/*
* @Description: 封裝axios請求 axios官網:http://www.axios-js.com/zh-cn/
* @version: 1.0.0
* @Author: houjiaojiao
* @Date: 2020-07-23 16:32:19
* @LastEditors: houjiaojiao
* @LastEditTime: 2020-09-01 17:30:46
*/
import axios from 'axios'
// 新建一個 axios 實例
let instance = axios.create({
baseURL: '/api/cert/',
});
// 請求攔截器
instance.interceptors.request.use(
// 在發送請求前做一些事情
request => {
// 在發送請求前給每個請求頭帶上Authorization字段
const auth = 'Token ' + localStorage.getItem('token');
request.headers.Authorization
return request;
},
// 請求出現錯誤做一些事情
error => {
console.log('There are some problems with this request');
console.log(error);
return Promise.reject(error);
}
)
//響應攔截器
instance.interceptors.response.use(
response => {
return response;
},
error => {
return Promise.reject(error);
}
)
// 封裝get請求
export function get(url, params){
return new Promise((resolve, reject) => {
instance.get(url, {
params
})
.then(response => {
resolve(response);
}).catch(error => {
reject(error)
})
})
}
// 封裝post請求
export function post(url, params){
return new Promise((resolve, reject) => {
instance.post(url, params)
.then(response => {
resolve(response)
}).catch(error => {
reject(error)
})
})
}
可以看到,我們對axios
的get
、post
請求進行了封裝,同時我們將認證需要添加到請求頭部
的Authorization
字段定義在了axios
的請求攔截器
中,這樣每一個請求都會攜帶這個頭部字段
。
接着我們在對請求的API
做一個封裝,以登錄
為例。
// 代碼位置:/src/api/login.js
import {get, post} from '@/utils/request.js'
export const login = (loginForm) => post('userAuth/login', loginForm)
然后我們在登錄組件中調用這個API
發起請求。
// 引入前面封裝好的API接口
import {login} from '@/api/login.js'
export default {
name: 'Login',
data() {
return {
loginForm: {
username: '',
password: '',
}
}
},
methods: {
login: function(){
// 直接調用API接口
login(this.loginForm).then(res => {
const {result, detail, errorInfo} = res.data;
if(result == true){
// 登錄成功 設置token
localStorage.setItem('token', detail.token);
// 跳轉頁面
this.$router.push('/certMake');
}else{
this.$message({
showClose: true,
message: errorInfo,
type: 'error'
});
}
})
}
}
}
以上省略登錄組件中
template
中的代碼
最后在登錄界面輸入用戶名
和密碼
,就可以正常登陸了。
之后我們在瀏覽器
中點擊其他的頁面,會發現每個發出的請求頭部
都攜帶了Authorization
字段。
注銷
當用戶點擊注銷
時,我們應該做的就是清除本地保存的token
。
logout: function(){
// 清除token
localStorage.removeItem("token");
// 跳轉至登錄頁 登錄頁面在router.js中的配置的path就是‘/’
this.$router.push("/");
}
清除以后呢,如果我們直接在瀏覽器中手動輸入url
進入某個頁面,就可以看到響應出現401
。
此時用戶只有再次進入登錄頁面
進行登錄,才能正常訪問頁面。
那對於上面注銷
之后返回的401
,實際上比較合理的結果應該是直接跳轉到登錄頁
。因此我們還需要在發起請求前對token
進行一個判斷,如果沒有token
存在,則直接跳轉至登錄頁。
上面描述的功能使用 守衛導航 實現
代碼定義在router.js
中
// 給路由定義前置的全局守衛
router.beforeEach((to, from, next) => {
let token = localStorage.getItem('token');
if(token){
// token存在 訪問login 跳轉至產品證書制作頁面
if(to.path == '/' || to.path == '/login'){
next('/certMake');
}else{
next();
}
}else{
// token不存在 路徑'/'就是登錄頁面設置的path
if(to.path === '/'){
next();
}else{
next('/')
}
}
})
設置Token有效期
前面我們完成的登錄功能
,除了注銷后需要登錄,其他任何時候只要用戶成功登錄過一次,就不需要在此登錄了。這樣存在一個很大的安全隱患,那就是當用戶的token
不慎泄露后,別人是可以沒有限制的操作我們的頁面。
因此最好的辦法就是給token
設置一個有效期
,當有效期
到了以后,強制用戶退出登錄
,在下一次登錄的時候重新生成新的token
。
那接下來就是這個功能的代碼實現了。
后端配置token有效期
后端在userAuth
模塊下新建一個auth.py
,自定義一個用戶認證類
,繼承TokenAuthentication
,並且實現token
過期的處理。
# -*- coding: utf-8 -*-
# Create your views here.
from rest_framework.authentication import TokenAuthentication
from rest_framework import exceptions
from django.utils import timezone
from datetime import timedelta
from django.conf import settings
# token過期時間處理
class ExpiringTokenAuthentication(TokenAuthentication):
def authenticate_credentials(self, key):
model = self.get_model()
try:
token = model.objects.select_related('user').get(key=key)
except model.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token.')
if not token.user.is_active:
raise exceptions.AuthenticationFailed('User inactive or deleted.')
# 重點就在這句了,這里做了一個Token過期的驗證
# 如果當前的時間大於Token創建時間+DAYS天,那么就返回Token已經過期
if timezone.now() > (token.created + timedelta(days=7)):
print "Token has expired"
# 過期以后 響應為401
raise exceptions.AuthenticationFailed('Token has expired')
return (token.user, token)
這里設置
token
的有效期是7天
接着修改setting
中配置的全局認證方案
為我們自定義的用戶認證ExpiringTokenAuthentication
。
# 設置全局身份認證方案
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'userAuth.auth.ExpiringTokenAuthentication', # token過期時間
# 'rest_framework.authentication.TokenAuthentication', # token認證
)
}
接着我們在userAuth
模塊的views.py
中定義退出登錄
的邏輯:退出登錄時刪除數據庫中的token
。
@api_view(['GET'])
@permission_classes((AllowAny,))
@authentication_classes(())
def logout(request):
"""退出登錄"""
result = True
errorInfo = u''
detail = {}
token = ''
authInfo = request.META.get('HTTP_AUTHORIZATION')
if authInfo:
token = authInfo.split(' ')[1]
try:
# 退出登錄 刪除token
tokenObj = Token.objects.get(key=token)
tokenObj.delete()
except Exception as e:
traceback.print_exc(e)
print 'token not exist'
result = False
errorInfo = u'退出登錄失敗'
return Response({"result": result, "detail": {}, "errorInfo": errorInfo})
前端設置
當token
過期以后,后端會返回401
,因此我們需要在響應攔截器
中處理這個401
,即當后端響應為401
時,就彈框提示用戶登錄過期,強制用戶退出登錄。
//響應攔截器
instance.interceptors.response.use(
response => {
return response;
},
error => {
// 在這里處理一下token過期的邏輯
// 后端驗證token過期以后 會返回401
if(error.response.status == 401){
MessageBox.confirm('登錄過期,請重新登錄', '確定登出', {
confirmButtonText: '重新登錄'
type: 'warning'
}).then(() => {
// 調用接口退出登錄
get('/userAuth/logout').then( response => {
// 移除本地緩存的token
localStorage.removeItem("token");
location.reload();
})
})
}
return Promise.reject(error);
}
)
結果演示
到此前后端的邏輯就完成了,我們來演示一下最后的結果。
首先我們先看一下數據庫中已有的token
的創建時間。
可以看到數據庫中已有的token
的創建時間是2020-09-17
,現在的時間是2020-10-10
號,已經超出token
的有效期。
前面設置
token
的有效期是7
天
然后我們刷新一下頁面。
發現已經成功彈出強制用戶重新登錄
。
當我們點擊重新登錄
按鈕后,就會請求后端的logout
接口,數據庫中已有的token
會被刪除,刪除成功之后本地緩存在localStorage
中的token
也會被刪除,最后會跳轉到產品的登錄頁面。
數據庫中的
token
已經被刪除
接着在登錄頁面輸入用戶名
和密碼
重新登錄,就會發現數據庫中的token
已經更新。
最后
關於 《vue
結合Django-Rest-Frameword
結合實現登錄認證》這個系列的文章就結束了。
文章基本都是實戰操作,希望可以給大家一個參考。
文章索引
《Vue結合Django-Rest-Frameword實現登錄認證(一)》
《Vue結合Django-Rest-Frameword實現登錄認證(二)》
作者:小土豆
博客園:https://www.cnblogs.com/HouJiao/
掘金:https://juejin.im/user/2436173500265335
微信公眾號:不知名寶藏程序媛(關注"不知名寶藏程序媛"免費領取前端電子書籍。文章公眾號首發,關注公眾號第一時間獲取最新文章。)
作者文章的內容均來源於自己的實踐,如果覺得有幫助到你的話,可以點贊❤️給個鼓勵或留下寶貴意見