vue+django的JWT驗證

台友涛發表於2024-04-15

1、配置JWT和跨域請求

vue和django的請求是跨域請求, 因此要配置跨域請求

在settings.py中新增如下程式碼


INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "app01.apps.App01Config",
    'corsheaders', #允許跨域訪問之1--它是 Django REST framework 的一個擴充套件,它提供了一種簡單的方式來處理跨域資源共享(CORS)問題
    'rest_framework',#JWT之1--引入rest_framework,rest_framework是Django框架中的一個擴充套件包,能夠幫助開發者簡化RESTful API的開發過程,提高開發效率。
    'rest_framework_simplejwt',#JWT之2--是 Django REST framework 的一個擴充套件,它提供了 JWT 認證的功能
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    'corsheaders.middleware.CorsMiddleware',  #允許跨域訪問之2--它是 Django REST framework 的 corsheaders 擴充套件中的一箇中介軟體

]

#允許跨域訪問之3--域名白名單
CORS_ALLOWED_ORIGINS = [  
    "http://localhost:5173",  # Vue應用的地址,注意要和自己的專案對應有的人埠是8080 
]  

#JWT之3--配置JWT的引數
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),  # ACCESS_TOKEN的有效期為60分鐘
    'REFRESH_TOKEN_LIFETIME': timedelta(days=15),  # REFRESH_TOKEN的有效期為15天
    'ROTATE_REFRESH_TOKENS': False,  # 重新整理令牌輪換功能關閉
    'BLACKLIST_AFTER_ROTATION': False,  # 重新整理令牌輪換後不會將舊令牌列入黑名單
    'UPDATE_LAST_LOGIN': False,  # 不會更新使用者的最後登入時間

    'ALGORITHM': 'HS256',  # 使用HS256演算法進行簽名
    'SIGNING_KEY': 'suijizifc',  # 簽名金鑰
    'VERIFYING_KEY': None,  # 驗證金鑰
    'AUDIENCE': None,  # 受眾
    'ISSUER': None,  # 發行者
    'JWK_URL': None,  # JSON Web Key URL
    'LEEWAY': 0,  # 允許的時間偏差

    'AUTH_HEADER_TYPES': ('Bearer',),  # 授權頭型別
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',  # 授權頭名稱
    'USER_ID_FIELD': 'id',  # 使用者ID欄位
    'USER_ID_CLAIM': 'user_id',  # 使用者ID宣告
    'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',  # 使用者認證規則

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),  # 授權令牌類
    'TOKEN_TYPE_CLAIM': 'token_type',  # 令牌型別宣告
    'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',  # 令牌使用者類

    'JTI_CLAIM': 'jti',  # JWT ID宣告

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',  # 滑動令牌重新整理過期宣告
    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),  # 滑動令牌的有效期為5分鐘
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),  # 滑動令牌的重新整理期為1天,  # End of Selection
}

2、製作帶有JWT驗證功能的檢視

做JWT驗證的檢視函式,必須封裝在類內, 例如我將這個類寫在views/imgs_get.py檔案裡了

函式名稱是固定的, 開發者自定義名稱的函式不能被直接呼叫. 如果是get請求,函式名必須為get; 如果是post請求,函式名必須為post

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from app01.models import Imgs
from django.http import JsonResponse

class ImgsGetView(APIView):
    permission_classes = ([IsAuthenticated])#驗證access令牌
    
    #這裡是get請求,所以函式名必須為get
    def get(self, request):
        imgs=Imgs.objects.all()
        imgs_list=[]
        for img in imgs:
            imgs_list.append({'id':img.id,'img_num':img.img_num,'img_name':img.img_name,'img_time':img.img_time})
        return JsonResponse({'result': 'success', 'message': imgs_list})

由於函式封裝在類內,而非普通函式, 所以對應的urls.py的配置也要改變

from app01.views.imgs_get import ImgsGetView#載入檢視類
urlpatterns = [
    ...
    path('api/ImgsGet/',ImgsGetView.as_view(),name='ImgsGet'),
    ...
]

對應的Vue的請求方式也要傳送變化, 即要攜帶令牌資訊

axios.get('http://127.0.0.1:8000/api/ImgsGet/', {
      headers: {
        'Authorization': 'Bearer ' + 令牌資訊
      }
    })

3、令牌資訊如何獲取呢

在登陸時, 將使用者名稱和密碼傳給TokenObtainPairView, 這是應該django自帶的驗證使用者資訊並返回令牌的類, 我們只需要在urls.py裡給這個類指定一個訪問連結即可

path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair')

登入並獲取令牌,Login.vue

<template>
    <div class="login-container">
      <h1>Login</h1>
      <form @submit.prevent="handleSubmit">
        <label for="username">Username:</label>
        <input id="username" v-model="username" type="text" required>
        <label for="password">Password:</label>
        <input id="password" v-model="password" type="password" required>
  
        <button type="submit">Login</button>
      </form>
    </div>
  </template>

  
  <script setup>
    import axios from 'axios';
    import { ref } from 'vue';
    import router from '@/router';
 
    let username=ref()
    let password=ref()
       
    async function handleSubmit() {
      try {
          //提交登入資訊給TokenObtainPairView
          const res = await axios.post('http://127.0.0.1:8000/api/token/', {
          username: username.value,
          password: password.value
        });
  
        
        const { access, refresh } = res.data;
        
        //將獲取的JWT存到localstorage裡持久化, localStorage相當於一個全域性字典, 以後再任何檔案裡想要存資料都可以使用它,支援跨檔案存取,即a檔案中存的,b檔案中可以讀
        //儲存資料: localStorage.setItem('資料名', 資料值);
        //讀取資料: localStorage.getItem('資料名');
        localStorage.setItem('access', access);//access令牌
        localStorage.setItem('refresh', refresh);//refresh令牌, 其他功能的令牌,本次用不到,暫不展開
        
        localStorage.setItem('username', username.value);
  
  
        router.push('/home/video');
      } catch (error) {
        console.log('登陸失敗的返回:', error);
        alert('登陸失敗的返回:'+error);
        
      }
    }
  
 
  </script>
  
  <style scoped>
  </style>

4、攜帶access令牌請求

axios.get('http://127.0.0.1:8000/api/ImgsGet/', {
      headers: {
        'Authorization': 'Bearer ' + localStorage.getItem('access')#攜帶access令牌資訊
      }
    })

在access令牌裡, 是包含了使用者資訊的, 比如使用者id, 在攜帶令牌請求時, django會自動解析請求中的JWT令牌,並將使用者資訊新增到request物件中。因此我們可以透過request.user來訪問這個使用者資訊, 比如

from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from django.http import JsonResponse

from django.contrib.auth.models import User
# from icecream import ic

class BandEmailView(APIView):
    permission_classes = ([IsAuthenticated])

    #這裡是post請求,函式名必須為post
    def post(self, request):
        if request.method == 'POST':
            email = request.POST.get('email')
            
            #django會自動解析請求中的JWT令牌,並將使用者資訊新增到request物件中。因此我們可以透過request.user來訪問這個使用者資訊
            user_id = request.user.id#獲取使用者id
            print('user_id',user_id)

            user = User.objects.filter(id=user_id).first()
            user.email = email
            user.save()
            return JsonResponse({'result': 'success'})
        
        return JsonResponse({'result': 'error'})

小技巧

如果覺得每次請求都要寫令牌資訊太麻煩,可以將請求封裝一下,如我在vue-project/public/js/myjs.js裡封裝了應該axios請求

const instance = axios.create({
    baseURL: 'http://127.0.0.1:8000',
    timeout: 1000,
    headers: {
        'Authorization': `Bearer ${localStorage.getItem('access')}`
    }
 })

以後請求時直接使用 instance

instance.get('http://127.0.0.1:8000/api/ImgsGet/')

相關文章