作者:小土豆biubiubiu
部落格園: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結合實現登入認證(二)》
關於
作者
小土豆biubiubiu
一個努力學習的前端小菜鳥,知識是無限的。堅信只要不停下學習的腳步,總能到達自己期望的地方
同時還是一個喜歡小貓咪的人,家裡有一隻美短小母貓,名叫土豆
部落格園
https://www.cnblogs.com/HouJiao/
掘金
https://juejin.im/user/2436173500265335
微信公眾號
土豆媽的碎碎念
微信公眾號的初衷是記錄自己和身邊的一些故事,同時會不定期更新一些技術文章
歡迎大家掃碼關注,一起吸貓,一起聽故事,一起學習前端技術
作者寄語
小小總結,歡迎大家指導~
參考文章
? django-rest-framework官方文件#許可權篇
? django-rest-framework官方文件#授權認證篇