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


作者:小土豆

博客園: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)
      })
  })
}

可以看到,我們對axiosgetpost請求進行了封裝,同時我們將認證需要添加到請求頭部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

微信公眾號:不知名寶藏程序媛(關注"不知名寶藏程序媛"免費領取前端電子書籍。文章公眾號首發,關注公眾號第一時間獲取最新文章。)

作者文章的內容均來源於自己的實踐,如果覺得有幫助到你的話,可以點贊❤️給個鼓勵或留下寶貴意見


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM