vue+django的JWT验证
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 扩展中的一个中间件
]
from datetime import timedelta
#允许跨域访问之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里给这个类指定一个访问链接即可
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
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/')