1. 程式人生 > >Vue結合Django-Rest-Frameword結合實現登入認證(二)

Vue結合Django-Rest-Frameword結合實現登入認證(二)

>作者:小土豆biubiubiu > >部落格園:https://www.cnblogs.com/HouJiao/ > >掘金:https://juejin.im/user/2436173500265335 > > >微信公眾號:土豆媽的碎碎念(掃碼關注,一起吸貓,一起聽故事,一起學習前端技術) > >作者文章的內容均來源於自己的實踐,如果覺得有幫助到你的話,可以點贊❤️給個鼓勵或留下寶貴意見 # 前言 在上一篇 [Vue結合Django-Rest-Frameword實現登入認證(一)](https://juejin.im/post/6874872436962099208/) 文章中,我們利用`token`實現了一個非常基礎的`使用者登入認證`功能。 那這一節需要對前面實現的內容進行優化: 1. 優化axios:請求封裝、認證資訊的封裝 2. 登出 3. 設定token過期時間 # 優化axios `axios`的優化就是對`axios`進行一個封裝,單獨抽離出來一個模組,負責編寫請求的`API`,在元件中只需要呼叫這個`API`傳入對應的引數,就能在`請求傳送`的同時實現`認證資訊的設定`。 ```javascript // 程式碼位置:/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`做一個封裝,以`登入`為例。 ```javascript // 程式碼位置:/src/api/login.js import {get, post} from '@/utils/request.js' export const login = (loginForm) => post('userAuth/login', loginForm) ``` 然後我們在登入元件中呼叫這個`API`發起請求。 ```javascript // 引入前面封裝好的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`欄位。 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2dfac90b39f7463a86db96aff4a00292~tplv-k3u1fbpfcp-zoom-1.image) ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b19d7013e0ed4739bfcfd4a8403bfaa5~tplv-k3u1fbpfcp-zoom-1.image) # 登出 當用戶點選`登出`時,我們應該做的就是清除本地儲存的`token`。 ```javascript logout: function(){ // 清除token localStorage.removeItem("token"); // 跳轉至登入頁 登入頁面在router.js中的配置的path就是‘/’ this.$router.push("/"); } ``` 清除以後呢,如果我們直接在瀏覽器中手動輸入`url`進入某個頁面,就可以看到響應出現`401`。 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/283e7bac4ccd46769d17510f411d7460~tplv-k3u1fbpfcp-zoom-1.image) 此時使用者只有再次進入`登入頁面`進行登入,才能正常訪問頁面。 那對於上面`登出`之後返回的`401`,實際上比較合理的結果應該是直接跳轉到`登入頁`。因此我們還需要在發起請求前對`token`進行一個判斷,如果沒有`token`存在,則直接跳轉至登入頁。 > 上面描述的功能使用 [守衛導航](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%85%A8%E5%B1%80%E5%89%8D%E7%BD%AE%E5%AE%88%E5%8D%AB) 實現 > 程式碼定義在`router.js`中 ```javascript // 給路由定義前置的全域性守衛 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`。 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c26cae3debb84a608e19392fc750f4a3~tplv-k3u1fbpfcp-watermark.webp) 那接下來就是這個功能的程式碼實現了。 ### 後端配置token有效期 後端在`userAuth`模組下新建一個`auth.py`,自定義一個`使用者認證類`,繼承`TokenAuthentication`,並且實現`token`過期的處理。 ```python # -*- 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`。 ```python # 設定全域性身份認證方案 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`。 ```python @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`時,就彈框提示使用者登入過期,強制使用者退出登入。 ```javascript //響應攔截器 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`的建立時間。 ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bcabf41c5bd3452a9c425d0c04580fd5~tplv-k3u1fbpfcp-watermark.image) 可以看到資料庫中已有的`token`的建立時間是`2020-09-17`,現在的時間是`2020-10-10`號,已經超出`token`的有效期。 > 前面設定`token`的有效期是`7`天 然後我們重新整理一下頁面。 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3caf447cf833411c9ab5cbaca7ea9f85~tplv-k3u1fbpfcp-watermark.image) 發現已經成功彈出強制使用者`重新登入`。 當我們點選`重新登入`按鈕後,就會請求後端的`logout`介面,資料庫中已有的`token`會被刪除,刪除成功之後本地快取在`localStorage`中的`token`也會被刪除,最後會跳轉到產品的登入頁面。 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b8b6babf1a73458a8475da3500026569~tplv-k3u1fbpfcp-watermark.image) > 資料庫中的`token`已經被刪除 接著在登入頁面輸入`使用者名稱`和`密碼`重新登入,就會發現資料庫中的`token`已經更新。 ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f71c248ae28b4854bc9e2dd6a38e5322~tplv-k3u1fbpfcp-watermark.image) # 最後 關於 《`vue`結合`Django-Rest-Frameword`結合實現登入認證》這個系列的文章就結束了。 文章基本都是實戰操作,希望可以給大家一個參考。 # 文章索引 [《Vue結合Django-Rest-Frameword實現登入認證(一)》](https://juejin.im/post/6874872436962099208) [《Vue結合Django-Rest-Frameword實現登入認證(二)》](https://juejin.im/post/6882566665524281357/) # 關於 ### 作者 小土豆biubiubiu > 一個努力學習的前端小菜鳥,知識是無限的。堅信只要不停下學習的腳步,總能到達自己期望的地方 > > 同時還是一個喜歡小貓咪的人,家裡有一隻美短小母貓,名叫土豆 ### 部落格園 https://www.cnblogs.com/HouJiao/ ### 掘金 https://juejin.im/user/2436173500265335 ### 微信公眾號 土豆媽的碎碎念 > 微信公眾號的初衷是記錄自己和身邊的一些故事,同時會不定期更新一些技術文章 > > 歡迎大家掃碼關注,一起吸貓,一起聽故事,一起學習前端技術 ### 作者寄語 小小總結,歡迎大家指導~ # 參考文章