来源引用网络知识与某站曹老师视频相互结合学习记录,仅供参考!
架构搭建
三、引入 JWT 前后端交互
Json Web Token (JWT) 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519);JWT就是一段字符串,主要是用来进行用户身份认证的凭证,如前后端不分离,用户会话时会临时生成一个 Session 存在服务端,再结合前端 Cookie;现在前后端分离的话, 也需要一个标识,就引用的 Json web token (JWT),它的组成字符串由"头部(Header)、载荷(Payload)和签名(Signature)"三部分组成;
头部(Header):
JWT的头部通常由两部分组成:令牌的类型(即JWT)和使用的签名算法;
例如:一个简单的头部可以是: { "alg": "HS256", "typ": "JWT" }
该头部的意思是使用HS256算法进行签名,并且类型是JWT;
载荷(Payload):
载荷是JWT的第二个部分,也被称为声明(Claims);它包含了一些被称为声明的实体;
(例如用户、用户角色等)的信息。载荷可以包含一组标准声明,
例如: { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
还可以包含自定义的声明,例如: { "role": "admin" };
签名(Signature):
要创建JWT的签名部分,需要使用头部和载荷以及密钥进行签名;
签名用于验证消息的完整性和验证发件人的身份;
例如:用于创建签名的算法可以是HMAC SHA256(使用字符串和密钥并加密生成签名),或者是RSA或ECDSA(使用公钥加密和私钥解密)等;服务端需要保存加密用的密钥,以便后续的签名验证;
总结起来,JWT由三部分组成:头部(Header)、载荷(Payload )和签名(Signature);头部用于描述签名的算法和类型,载荷用于存储信息的声明,签名用于验证消息的完整性和验证发件人的身份。

具体流程如下(烦请自行参考):
- 前端用户认证发起"提交用户名、密码",后端进行认证;
- 认证通过之后会通过 JWT 生成 Token,就是会话标识返回给前端,前端会进行浏览器保存;
- 其他后端请求在 headers 请求头中携带 Token,然后后端拦截器进行拦截、判断 Token;
- 如果是认证成功的,没有过期且是正确的话,就执行业务逻辑,返回数据,前端进行展示;
- 如果 Token 失效了或者 Token 不存在或者错误,则返回错误信息给前端进行下一步处理;
这是一种非常好的设计,前后端分离开发均是这么做的;
在实际开发过程中是基于 DRF 框架使用 JWT(推荐),DRF(Django Rest Framework)是用于构建 API 的成熟且广泛使用的 Python 库;它是基于流行的 Python Web 框架 Django 构建的一个轻量级 REST 框架,主要是为了方便 Web 开发而引入的,旨在简化复杂的数据访问API的开发过程;如果已经熟悉 Django 框架,那么学习 DRF 将会非常直接,因为它使用了很多 Django 中已有的概念,并添加了额外的功能来支持 RESTful API 的设计;DRF(Django Rest Framework)官方网站(烦请自行探索):https://www.django-rest-framework.org/

JWT 基于 DRF 的一个扩展(自行探索了解,只有几个接口):https://github.com/jpadilla/django-rest-framework-jwt

DRF(Django Rest Framework)主要特点:
- 简单易用:DRF提供了许多开箱即用的功能;例如:认证策略、内容协商、限流等,使得开发者能够快速地搭建出 RESTful 风格的服务端接口;
- 自动化的URL路由:它允许定义清晰的 URL 模式,并自动生成相应的视图,从而减少了编写常规代码的时间;
- 序列化支持:DRF内建了序列化功能,可以帮忙将模型实例转换为JSON或其他格式的数据,以及反序列化请求数据到模型对象;比较实用,自己整序列化比较麻烦;
- 灵活的配置:虽然提供了很多默认设置,但是 DRF 也非常容易根据项目需求进行定制;
- 丰富的文档支持:可以自动生成 API 文档,帮助前端开发者或第三方更好地理解和使用 API;
安装第三方库
drf 和 drf-jwt 安装、使用 PyCharm 工具 Python 解释器安装或者命令安装,自行选择;
# drf安装
pip install djangorestframework -i https://pypi.tuna.tsinghua.edu.cn/simple
# drf-jwt 安装
pip install djangorestframework-jwt -i https://pypi.tuna.tsinghua.edu.cn/simple


1、安装完成之后去配置文件中设置添加,修改项目"python_admin"应用下的"settings.py"配置文件,在"INSTALLED_APPS"配置项中添加相对应的"rest_framework"和"rest_framework_jwt";设置完成后就可以在实际项目里面使用到了;
"""
Django settings for python_admin project.
Generated by 'django-admin startproject' using Django 6.0.7.
For more information on this file, see
https://docs.djangoproject.com/en/6.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/6.0/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-imdr37+f#1u-6spy0ch)49cr1=gkg8gr#x3dzx@)&f0)1qg_=!'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_jwt',
"user.apps.UserConfig",
"role.apps.RoleConfig",
"menu.apps.MenuConfig"
]
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',
]
ROOT_URLCONF = 'python_admin.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'python_admin.wsgi.application'
# Database
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db_admin',
'USER': 'root',
'PASSWORD': '123',
'HOST': '192.168.88.145',
'PORT': '3306'
}
}
# Password validation
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/6.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/6.0/howto/static-files/
STATIC_URL = 'static/'

2、修改项目"user"子模块应用下的"views.py"文件内容,新增定义一个 JwtTestView 测试类,模拟登录的一个测试类,相当于是模拟登录认证成功,然后给用户前端返回一个 Token;
from django.http import JsonResponse
from django.shortcuts import render
from django.views import View
from rest_framework_jwt.settings import api_settings
from user.models import SysUser
# Create your views here.
# 定义TestView类(导包引入from django.views import View)
class TestView(View):
def get(self, request):
# 导包引入from user.models import SysUser,通过objects.all()查询所有用户信息
userList_obj = SysUser.objects.all()
print(f'打印查看userList_obj类型:{type(userList_obj)},查看: {userList_obj},')
# 转存字典,里面的对象转换为了字典类型
userList_dict = userList_obj.values()
print(f'打印查看userList_dict2类型: {type(userList_dict)},查看:{userList_dict}')
# 把外层的容器转为List
userList = list(userList_dict)
print(f'打印查看userList类型: {type(userList)},查看:{userList}')
# 导包引入from django.http import JsonResponse
return JsonResponse({'code': 200, 'info': '测试', 'data': userList})
# 定义一个 JwtTestView 测试类,模拟登录
class JwtTestView(View):
# get请求方式
def get(self, request):
# 这里模拟登录是直接从之前添加测试数据的表中获取,返回的是一个用户user
user = SysUser.objects.get(username='jack', password='123456')
# 导包引入from rest_framework_jwt.settings import api_settings模块
# 获取 JWT_PAYLOAD_HANDLER 荷载方法,其实就是内容,返回的是一个函数对象
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
# 编码处理 JWT_ENCODE_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
# 上面两个方法获取到之后就可以进行操作了,第一步:将用户对象传递进去,获取到该对象的属性值
payload = jwt_payload_handler(user)
# 将属性编码成jwt格式的字符串,其实就是生成token
token = jwt_encode_handler(payload)
return JsonResponse({'code': 200, 'token': token})

3、修改项目"user"子模块应用下的"urls.py"文件内容,配置映射关系;
from django.urls import path
from user.views import TestView, JwtTestView
urlpatterns = [
path('test', TestView.as_view(), name='test'), # test测试
path('jwt_test', JwtTestView.as_view(), name='jwt_test'), # JWT测试
]

4、运行启动项目,浏览器输入"http://127.0.0.1:8000/user/jwt_test"测试验证,能够生成对应的 token 串且每次生成的都是不一样;建议这里可以保存一个 token 值,方便下面接口测试,若未保存,到时候使用时再生成一个即可;

5、修改项目"user"子模块应用下的"views.py"文件内容,更改查询用户信息的 TestView 测试类,要求有 token 才能够访问到;
from django.http import JsonResponse
from django.shortcuts import render
from django.views import View
from rest_framework_jwt.settings import api_settings
from user.models import SysUser
# Create your views here.
# 定义TestView类(导包引入from django.views import View)
class TestView(View):
def get(self, request):
# 获取token,需注意在前端是叫"AUTHORIZATION"这个名字,而到Django中则会自动添加"HTTP_"变为"HTTP_AUTHORIZATION"
token = request.META.get('HTTP_AUTHORIZATION')
print(f'打印查看token值: {token}')
# 判断token不等于None值且不为空值,就表示通过了
if token != None and token != '':
# 导包引入from user.models import SysUser,通过objects.all()查询所有用户信息
userList_obj = SysUser.objects.all()
print(f'打印查看userList_obj类型:{type(userList_obj)},查看: {userList_obj},')
# 转存字典,里面的对象转换为了字典类型
userList_dict = userList_obj.values()
print(f'打印查看userList_dict2类型: {type(userList_dict)},查看:{userList_dict}')
# 把外层的容器转为List
userList = list(userList_dict)
print(f'打印查看userList类型: {type(userList)},查看:{userList}')
# 导包引入from django.http import JsonResponse
return JsonResponse({'code': 200, 'info': '测试', 'data': userList})
else:
# 否则的话就给前端返回401
return JsonResponse({'code': 401, 'info': '没有测试访问权限!'})
# 定义一个 JwtTestView 测试类,模拟登录
class JwtTestView(View):
# get请求方式
def get(self, request):
# 这里模拟登录是直接从之前添加测试数据的表中获取,返回的是一个用户user
user = SysUser.objects.get(username='jack', password='123456')
# 导包引入from rest_framework_jwt.settings import api_settings模块
# 获取 JWT_PAYLOAD_HANDLER 荷载方法,其实就是内容,返回的是一个函数对象
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
# 编码处理 JWT_ENCODE_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
# 上面两个方法获取到之后就可以进行操作了,第一步:将用户对象传递进去,获取到该对象的属性值
payload = jwt_payload_handler(user)
# 将属性编码成jwt格式的字符串,其实就是生成token
token = jwt_encode_handler(payload)
return JsonResponse({'code': 200, 'token': token})

6、浏览器输入"http://127.0.0.1:8000/user/test"直接访问,会提示没有访问权限,因为现在需要带有 token 才可以访问到;

7、使用接口测试工具,烦请自行选择,例如:Postman、Apifox 等等,分别测试验证"http://127.0.0.1:8000/user/test"接口在不携带请求头参数"AUTHORIZATION(这里不需要加'HTTP',因为后端默认会添加)"和携带请求头参数的返回结果;



8、打开前端 vue 项目"python_vue5_admin",在"src"目录下新建一个名称为"util"的目录文件,在"util"目录文件路径下新建一个名称为"request.js"的 JS 文件,输入以下内容;大致就是封装了的请求工具类,通过axios请求后端接口;
// 引入axios
import axios from 'axios';
let baseUrl="http://localhost:8000/";
// 创建axios实例
const httpService = axios.create({
// url前缀-'http:xxx.xxx'
// baseURL: process.env.BASE_API, // 需自定义
baseURL:baseUrl,
// 请求超时时间
timeout: 3000 // 需自定义
});
//添加请求和响应拦截器
// 添加请求拦截器
httpService.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
config.headers.AUTHORIZATION=window.sessionStorage.getItem('token');
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
httpService.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
/*网络请求部分*/
/*
* get请求
* url:请求地址
* params:参数
* */
export function get(url, params = {}) {
return new Promise((resolve, reject) => {
httpService({
url: url,
method: 'get',
params: params
}).then(response => {
resolve(response);
}).catch(error => {
reject(error);
});
});
}
/*
* post请求
* url:请求地址
* params:参数
* */
export function post(url, params = {}) {
return new Promise((resolve, reject) => {
httpService({
url: url,
method: 'post',
data: params
}).then(response => {
console.log(response)
resolve(response);
}).catch(error => {
console.log(error)
reject(error);
});
});
}
/*
* delete请求
* url:请求地址
* params:参数
* */
export function del(url, params = {}) {
return new Promise((resolve, reject) => {
httpService({
url: url,
method: 'delete',
data: params
}).then(response => {
console.log(response)
resolve(response);
}).catch(error => {
console.log(error)
reject(error);
});
});
}
/*
* 文件上传
* url:请求地址
* params:参数
* */
export function fileUpload(url, params = {}) {
return new Promise((resolve, reject) => {
httpService({
url: url,
method: 'post',
data: params,
headers: { 'Content-Type': 'multipart/form-data' }
}).then(response => {
resolve(response);
}).catch(error => {
reject(error);
});
});
}
export function getServerUrl(){
return baseUrl;
}
export default {
get,
post,
del,
fileUpload,
getServerUrl
}

9、继续修改"src"目录"views"目录文件下的"HomeView.vue"文件内容,写一个模拟登录的获取token的请求,再写一个业务接口请求;
<template>
<el-button type="primary" @click="handleLogin">测试登录</el-button>
<el-button type="success" @click="handleUserList">测试获取用户信息列表</el-button>
</template>
<script setup>
import requestUtil from '@/util/request'
const handleLogin=async()=>{
let result=await requestUtil.get("user/jwt_test")
let data=result.data;
if(data.code==200){
const token=data.token
console.log("登录成功,token="+token)
window.sessionStorage.setItem("token",token)
}else{
console.log("登录出错")
}
}
const handleUserList=async ()=>{
let result=await requestUtil.get("user/test")
let data=result.data;
if(data.code==200){
const userList=data.data;
console.log("用户信息列表,userList="+userList)
}else{
console.log("登录出错")
}
}
</script>

10、浏览器输入"http://127.0.0.1:8080/"测试验证,记得启动前端项目,在终端命令窗口输入"npm run serve"命令启动项目,启动成功后自行打开测试查看;点击任意按钮,可以看到后端报错信息端口号是"8000",而前端是"8080",它们不属于同一个域,为了安全着想,所以默认是不允许进行跨域访问的;

四、跨域问题解决
CORS(跨域资源共享,Cross-Origin Resource Sharing)是一种跨域访问的机制,可让Ajax实现跨域访问;在服务器端可以设置允许,在服务器的response header中,加入"Access-Control-Allow-Origin: *"便可支持CORS;但是一个个添加会非常麻烦,所以有人开发了一个库叫"CORS-header"可以直接支持,只需在settings.py中做一些简单的配置即可,其他不用作任何修改,也不用自己手动的创建中间件对response处理了,直接用以下配置即可, 现在用起来服务器端完全开放,开启CORS,没有任何跨域烦恼;
安装第三方库 django-cors-headers
11、安装django-cors-headers库,烦请自行安装;
# 安装django-cors-headers库命令
pip install django-cors-headers -i https://pypi.tuna.tsinghua.edu.cn/simple


配置添加到应用程序中
12、修改项目"python_admin"应用下的"settings.py"配置文件,在"INSTALLED_APPS"配置项中添加相对应的"corsheaders",可自行放置,无顺序要求;
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_jwt',
'corsheaders',
"user.apps.UserConfig",
"role.apps.RoleConfig",
"menu.apps.MenuConfig"
]

13、修改项目"python_admin"应用下的"settings.py"配置文件,在"MIDDLEWARE"配置项中添加相对应的"corsheaders.middleware.CorsMiddleware"设置项,注意需要必须放在"django.middleware.common.CommonMiddleware"设置项之前,建议可以直接把"corsheaders.middleware.CorsMiddleware"设置项直接放在第一位,不然可能会造成某些请求没有添加CORS;
MIDDLEWARE = [
'django.middleware.common.CommonMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

设置访问白名单
14、修改项目"python_admin"应用下的"settings.py"配置文件内容,添加"CORS_ORIGIN_ALLOW_ALL = True"设置项;
# CORS_ORIGIN_ALLOW_ALL为True, 指定所有域名(ip)都可以访问后端接口, 默认为False
CORS_ORIGIN_ALLOW_ALL = True

设置允许携带cookie
15、修改项目"python_admin"应用下的"settings.py"配置文件内容,添加"CORS_ALLOW_CREDENTIALS = True"设置项,一般情况下都需要设置此项;
CORS_ALLOW_CREDENTIALS = True

设置默认允许请求头方法
16、修改项目"python_admin"应用下的"settings.py"配置文件内容,添加"CORS_ALLOW_METHODS"设置项,默认请求方法,建议可以把所有的请求方法都写上就可以了;
"""
Django settings for python_admin project.
Generated by 'django-admin startproject' using Django 6.0.7.
For more information on this file, see
https://docs.djangoproject.com/en/6.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/6.0/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-imdr37+f#1u-6spy0ch)49cr1=gkg8gr#x3dzx@)&f0)1qg_=!'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_jwt',
'corsheaders',
"user.apps.UserConfig",
"role.apps.RoleConfig",
"menu.apps.MenuConfig"
]
MIDDLEWARE = [
'django.middleware.common.CommonMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_METHODS = [
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
]
ROOT_URLCONF = 'python_admin.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'python_admin.wsgi.application'
# Database
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db_admin',
'USER': 'root',
'PASSWORD': '123',
'HOST': '192.168.88.145',
'PORT': '3306'
}
}
# Password validation
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/6.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/6.0/howto/static-files/
STATIC_URL = 'static/'

17、以上的配置信息都设置完成之后就可以在实际项目里面使用了,浏览器输入"http://127.0.0.1:8080/"测试验证,点击任意按钮,可以看到已经能够请求到数据信息了,能够说明跨域请求访问配置已经成功设置完成。

未完待续......