Vue3 + Django 前后端分离项目实现密码认证登录

1、功能需求

通常中小型前后端项目,对安全要求不高,也可以采用密码认证方案。如果只用django来实现非常简单。采用 Vue3 前后端分离架构,实现起来稍繁琐一点,好处是可以利用各种前端技术栈,如element-plus UI库来渲染页面。

演示项目需求为:

  • Vue3 前端提供登录页面
  • 输入用户名与密码后,发送POST登录请求至服务器,后者验证通过后,用json格式返回认证结果.
  • 前端收到响应后,如果认证通过,更新用户登录状态,保存响应消息中传来的 cookie
  • 后续请求中,携带cookie,服务端根据请求消息中的cookie验证,通过后,以json格式返回数据。

2、前后端技术栈环境

前端技术栈:

  • vue3
  • element-plus UI 库
  • pinia 状态管理库
  • axios 库

准备Vue3环境

进入保存项目的目录,如d:/workplace/projects/, 运行命令:

npm create vue@latest

这个命令会安装create-vue 工具,并执行创建项目,其过程会显示许多配置选项

新项目的路径为项目名称,即vue02/ , 生成的项目结构如下。
项目默认采用组合式API

D:\workplace\web\vue02>tree /A /F
卷 软件 的文件夹 PATH 列表
卷序列号为 0DC5-179B
D:.
|   .gitignore
|   index.html
|   package.json
|   README.md
|   vite.config.js
+---.vscode
|       extensions.json
+---public
|       favicon.ico
\---src|   App.vue|   main.js|+---assets|       base.css|       logo.svg|       main.css|+---components|   |   HelloWorld.vue|   |   TheWelcome.vue|   |   WelcomeItem.vue|+---router|       index.js|\---viewsAboutView.vueHomeView.vue

修改App.vue,清空项目。

导入依赖库

安装element-plus, axios, pinia

npm install element-plus --save-dev
npm install @element-plus/icons-vue --save-dev  
npm install axios --save-dev
npm install pinia --save-dev

在main.js 全局导入依赖库

import { createApp } from 'vue'
import "./assets/main.css"
import App from './App.vue'
import { createPinia } from 'pinia'
import router from './router'
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import formCreate from '@form-create/element-ui'const app = createApp(App)
app.use(createPinia())     // 导入pinia 库
app.use(router)
app.use(ElementPlus)
app.use(formCreate)
//导入所有elementplus 图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)}
app.mount('#app')

创建路由文件 src/router/index.js

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [{path: '/',name: 'home',component: HomeView},{path: '/about',name: 'about',component: () => import('../views/AboutView.vue')},{path: '/order',name: 'order',component: () => import("../views/FormOrder.vue")},{path: '/login',name: 'login',component: () => import("../views/Login.vue")},]
})export default router

修改 App.vue, 添加布局与导航菜单

<template><div class="common-layout"><el-container><el-header class="el-header">Vue3 测试项目</el-header><el-container><el-aside id="demo-aside" :width="isCollapse ? '64px':'180px'"><div class="toggle-button" @click="toggleCollapse" style="color: #ffffff;"><el-icon size="15" color="#fff" style="margin-top: 5px;"><Menu /></el-icon></div><el-menu background-color="#222222" active-text-color="#8ef" text-color="#fff" default-active="2"class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" :router="true" :collapse="isCollapse" :collapse-transition="false"><el-menu-item index="/logina"><el-icon size="15" color="#fff"><User /></el-icon><span>密码登录</span></el-menu-item><el-menu-item index="/loginjwt"><el-icon size="15" color="#fff"><User /></el-icon><span>JWT登录</span></el-menu-item><el-menu-item index="/"><el-icon :size="15" color="#fff"> <Flag /></el-icon><span>演示</span></el-menu-item><el-menu-item index="/listdata"><el-icon :size='15' color='#fff'><Collection /></el-icon><span>显示Blog</span></el-menu-item><el-menu-item index="/about">               <el-icon :size="15" color="#fff"> <Plus /></el-icon><span>新建Blog</span></el-menu-item><el-menu-item index="/order"><el-icon size="15" color="#fff"><Sell /></el-icon><span>订单管理</span></el-menu-item><el-sub-menu ><template #title ><el-icon :size="15" color="#fff"> <Setting /></el-icon><span>选项</span></template><el-menu-item index="3-1">item one</el-menu-item><el-menu-item index="3-2">item two</el-menu-item></el-sub-menu></el-menu></el-aside><el-container><el-main class="el-main"><router-view></router-view></el-main><el-footer>Footer</el-footer></el-container></el-container></el-container></div>
</template><script>
import { RouterLink, RouterView } from 'vue-router'
export default {data(){return {isCollapse: false }},components: {},methods: {handleOpen(key, keyPath){console.log(key, keyPath)},handleClose(key, keyPath){console.log(key, keyPath)},toggleCollapse(){this.isCollapse = !this.isCollapse}},
}
</script><style lang="scss" scoped>
html, body, .common-layout {margin: 0;padding: 0;width: 100vw; height: 100vh; 
}
.el-container {height: 100%; 
}
.el-header {  margin: 0px;padding-top: 5px;padding-bottom: 5px;height: 30px;text-align: center;background-color: darkblue;color: #ffffff;
}.el-aside {background-color: #222;text-align: center;
}.el-main {height: 600px;color: black;
}
.el-footer {background-color: rgb(6, 15, 103);color: #fff;height: 25px; 
}
.sub-hide * {color: #222;
}
.sub-show {color: #ffffff; 
}
</style>

Django后端环境准备

请参考作者另一篇 [博文] (https://blog.csdn.net/captain5339/article/details/131572762) 准备django环境

3、实现流程分析

Login登录的时序图如下

在这里插入图片描述
说明:

  • response 消息的header:中,django服务器通过set-cookie发送sessionid 以及csrftoken。
    Browser会自动保存set-cookie的值,对于后续请求,自动将cookie添加到头部,通常无须处理。
Set-Cookie: csrftoken=stUBZaZO26cKbf6RidHmmgiwHAFmY31jFpUbFuMqa8gJycz8WB4DNc6jmNexsqn6; expires=Wed, 19 Mar 2025 10:45:44 GMT; Max-Age=31449600; Path=/; SameSite=Lax 
Set-Cookie: sessionid=anv6tzhtws4mzdl5hprjcucre1feynyk; expires=Wed, 03 Apr 2024 10:45:44 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
  • api 登录接口与网页登录页面是有区别的,server端应该分别实现页面登陆与api login 视图, api login 应该用json格式发送登录结果。
  • Vue3 + Pinia 实现技术要点

    思路:

    • 通过 pinia 的store 来保存用户信息及登录状态,userinfo, 通过axios 发送login 请求,登陆成功后,将用户全局状态改为loginStatus=true,

    技术要点:

    • 使用pinia 保存username, loginStatus,并且将登录 api 方法也放在pinia store中。 可以采用base64或des对密码进行必要的加密后再发送。
    • 在store api方法中axios发送请求时使用 async await 语法, 组件的事件处理方法也采用async await 方式调用api, 这样可以避免不同步现象。
    • 对于响应返回的cookie,浏览器可以自行处理( 问题:读 set-cookie失败)

    4、具体步骤

    (1) 创建userStore

    主要包含
    state:

    • username, password,loginStatus等数据。

    actions:

    • login() ,通过axios 发送登录请求。
    • logout()

    创建 store 文件: src/stores/userStore.js

    import { ref, computed } from 'vue'
    import { defineStore } from 'pinia'
    import axios from 'axios';export const useUserStore = defineStore('user', () => {const username = ref('')const password = ref('')const loginStatus = ref(false)const ax = axios.create({baseURL: 'http://localhost:8000', //请求后端数据的基本地址,自定义timeout: 2000 //请求超时设置,单位ms})ax.defaults.withCredentials = trueconst Login = async (userName, pass) => {try {const res = await ax({url: '/v1/api-auth/login/',method: 'post',headers: {'Content-Type': 'multipart/form-data',},data: {username: userName,password: pass,},})console.log(res.data)console.log(res.headers)if (res.data.result == 'success') {username.value = userNameloginStatus.value = true} else {loginStatus.value = false}} catch (error) {console.log(error)}}//清空state const clearUserStore = () => {username.value = ''password.value = ''loginStatus.value = false}return { username, password, loginStatus, Login, clearUserStore	}
    })
    

    (2)创建登陆组件

    a) 提供username, password 输入表单
    b) 将login表单数据传入 userStore的login()方法。
    c) 处理response数据
    - 登陆成功:更新loginStatus, 重定向至下一页
    - 登陆失败,显示失败信息,继续重试。

    组件名称 src/views/Login.vue

    <template><el-formref="form"style="max-width: 500px":model="userinfo"label-width="80px"label-position="left"><el-form-item label="登陆名"><el-input v-model="userinfo.username" /></el-form-item><el-form-item label="密码"><el-input v-model="userinfo.password" /></el-form-item><el-form-item><el-button type="primary" @click="onLogin">登录</el-button></el-form-item></el-form>	  
    </template>
    <script setup>import { ref,reactive } from 'vue'import axios from 'axios'import { useUserStore } from "../stores/userStore.js"import { ElMessage } from 'element-plus'const userinfo = reactive({username: '',password: '',})	const store = useUserStore()	const onLogin = async ()=> {await store.Login(userinfo.username, userinfo.password)console.log(store.loginStatus)if(store.loginStatus == true ){console.log("登录成功")ElMessage({message: '登录成功',type: 'success',})} else {ElMessage({message: '登录失败',type: 'warning',})}		}
    </script>
    <style>
    </style>
    

    (3) 修改路由数据以及父组件

    a) 修改src/router/router.js

    import { createRouter, createWebHistory } from 'vue-router'
    import HomeView from '../views/HomeView.vue'const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [{path: '/login',name: 'login',component: () => import("../views/Login.vue")},	]
    })
    export default router
    

    b) 添加菜单项,指向新建路由
    src/app.vue

    <el-menu-item index="/logina"><el-icon size="15" color="#fff"><User /></el-icon><span>密码登录</span>
    </el-menu-item>
    

    (4) Django 实现登录API

    注意,django应提供基于api的view ,而非基于页面视图的login view.

    from rest_framework import status
    from rest_framework.decorators import api_view, authentication_classes
    from rest_framework.response import Response
    from rest_framework.authentication import (SessionAuthentication, BasicAuthentication
    )
    from django.contrib.auth import authenticate, login, logout
    from django.http import JsonResponse
    from django.views.decorators.csrf import csrf_exempt
    from .models import *
    from .serializers import ArticleSerializer, UserSerializer@csrf_exempt
    def api_login(request):if request.method == "POST":        print(list(request.POST.items()))username = request.POST['username']password = request.POST['password']        user = authenticate(request, username=username, password=password)if user is not None:login(request, user)# Redirect to a success page.return JsonResponse({"result": "success"})    else:# Return an 'invalid login' error message.return JsonResponse({'result': 'failed' ,'reason': "用户名与密码不正确"})else:return JsonResponse({"result": "rejected", "reason": "request method must be post"}, status=403)@csrf_exempt
    def api_logout(request):logout(request)return JsonResponse({"result": "success"})@api_view(['GET','POST'])
    @authentication_classes([SessionAuthentication, BasicAuthentication])
    def article_list(request, format=None):"""List all articles, or create a new article."""if request.method == 'GET':qs = Article.objects.all()qs = qs.select_related('author')serializer = ArticleSerializer(qs, many=True)return Response(serializer.data)elif request.method == 'POST':serializer = ArticleSerializer(data=request.data)if serializer.is_valid():# Very important. Associate request.user with authorserializer.save(author=request.user)return Response(serializer.data, status=status.HTTP_201_CREATED)return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)@api_view(['GET', 'PUT', 'DELETE'])
    def article_detail(request, pk,format=None):"""Retrieve,update or delete an article instance。"""try:qs = Article.objects.select_related('author')article = qs.get(pk=pk)except Article.DoesNotExist:return Response(status=status.HTTP_404_NOT_FOUND)if request.method == 'GET':serializer = ArticleSerializer(article)return Response(serializer.data)elif request.method == 'PUT':serializer = ArticleSerializer(article, data=request.data)if serializer.is_valid():serializer.save()return Response(serializer.data)return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)elif request.method == 'DELETE':article.delete()return Response(status=status.HTTP_204_NO_CONTENT)

    修改app.urls , 添加path

    urlpatterns = [...path('api-auth/login/', api_login, name='login'),path('api-auth/logout/',api_logout,name='logout'),path('articles/', article_list),...
    ]
    

    5、运行与测试

    进入django 文件夹,启动server

    python manage.py runserver  0.0.0.0:8000
    

    默认服务器端口为 http://127.0.0.1:8000
    登录 api url: http://127.0.0.1:8000/v1/api-auth/login/

    进入vue3项目文件夹,启动项目

    npm run dev 
    

    默认前端访问地址:
    http://localhost:5173/
    通过菜单进入登录表单页,打开浏览器的开发者工具,点击网络选项
    输入用户名与密码后,点击提交按钮,axio发送请求至服务器,
    在这里插入图片描述
    服务器端发送响应,vue3组件收到后,弹出登录成功的 message。接口消息可以从开发者工具的网络视图中查看。
    在这里插入图片描述
    后续请求消息处理
    如访问 http://127.0.0.1:8000/v1/articles/ 时,可以看到vue3在自动将 sessionid, csrftoken 放进request 的cookie中了。 django服务器根据sessionid 确定该user是否已通过登录验证。如果通过允许访问 /v1/articles/ 接口。否则将拒绝。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/766577.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git Commit 提交规范,变更日志、版本发布自动化和 Emoji 提交标准

前言 Git Commit 是开发的日常操作, 一个优秀的 Commit Message 不仅有助于他人 Review, 还可以有效的输出 CHANGELOG, 对项目的管理实际至关重要, 但是实际工作中却常常被大家忽略&#xff0c;希望通过本文&#xff0c;能够帮助大家规范 Git Commit&#xff0c;并且展示相关 …

Mongodb入门到入土,安装到实战,外包半年学习的成果

这是我参与「第四届青训营 」笔记创作活动的的第27天&#xff0c;今天主要记录前端进阶必须掌握内容Mongodb数据库,从搭建环境到运行数据库,然后使用MongodB; 一、文章内容 数据库基础知识关系型数据库和非关系型数据库为什么学习Mongodb数据库环境搭建及运行MongodbMongodb命…

【进程概念】启动进程 | 查看进程 | 创建进程

目录 启动进程 查看进程 方法1&#xff1a;/proc 方法2&#xff1a;查看脚本 ​方法3&#xff1a;系统调用获取进程标示符❗❗ 终止进程 创建进程&#xff08;主fork) &#x1f642;查看父子进程的pid &#x1f642;进程创建/执行/终止 &#x1f642;多次重新启动进…

STM32 CAN的工作模式

STM32 CAN的工作模式 正常模式 正常模式下就是一个正常的CAN节点&#xff0c;可以向总线发送数据和接收数据。 静默模式 静默模式下&#xff0c;它自己的输出端的逻辑0数据会直接传输到它自己的输入端&#xff0c;逻辑1可以被发送到总线&#xff0c;所以它不能向总线发送显性…

鸿蒙开发实战:网络请求库【axios】

简介 [Axios] &#xff0c;是一个基于 promise 的网络请求库&#xff0c;可以运行 node.js 和浏览器中。本库基于[Axios]原库v1.3.4版本进行适配&#xff0c;使其可以运行在 OpenHarmony&#xff0c;并沿用其现有用法和特性。 http 请求Promise APIrequest 和 response 拦截器…

Stable Diffusion实现光影字效果

昨天下午有人在群里发光影图片&#xff0c;大家都觉得很酷&#xff0c;我没怎么在意。直到早上我在小红书看到有人发同款图片&#xff0c;只是一晚上的时间点赞就超过了8000&#xff0c;而且评论数也很高&#xff0c;也可以做文字定制变现。研究了一下发现这个效果不难实现&…

数据结构/C++:哈希表

数据结构/C&#xff1a;哈希表 哈希表概念哈希函数直接定址法除留余数法 哈希冲突闭散列 - 开放定址法基本结构查找插入删除总代码展示 开散列 - 哈希桶基本结构查找插入删除代码展示 哈希表概念 在顺序表中&#xff0c;查找一个数据的时间复杂度为O(N)&#xff1b;在平衡树这…

宋仕强论道之华强北科技创新说

宋仕强论道之华强北科技创新说&#xff0c;“创新”是深圳市和华强北灵魂&#xff0c;创新再加上敢想敢干永不言败&#xff0c;造就了深圳市经济奇迹和华强北财富神话&#xff01;首次在深圳市落槌的“土地拍卖”&#xff0c;华强北“一米柜台”赋予独立经营权&#xff0c;把最…

通过jsDelivr实现Github的图床CDN加速

最近小伙伴们是否发现访问我的个人博客http://xiejava.ishareread.com/图片显示特别快了&#xff1f; 我的博客的图片是放在github上的&#xff0c;众所周知的原因&#xff0c;github访问不是很快&#xff0c;尤其是hexo博客用github做图床经常图片刷不出来。一直想换图床&…

提面 | 面试抽题

学习到更新日期面试抽题-1.2案例分析的思维本质2024-3-23 1提面抽屉论述问题的分类 1.1案例分析占总论 1.2案例分析的思维本质

rabbitmq 3.9.29 docker mac 管理员页面无法打开

SyntaxError: Unexpected token ‘catch’ SyntaxError: Unexpected token ‘catch’ at EJS.Compiler.compile (http://127.0.0.1:15672/js/ejs-1.0.min.js:1:6659) at new EJS (http://127.0.0.1:15672/js/ejs-1.0.min.js:1:1625) at format (http://127.0.0.1:15672/js/main…

【docker系列】深入理解 Docker 容器管理与清理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

llvm后端

SelectionDAGBuilder是LLVM&#xff08;Low Level Virtual Machine&#xff09;编译器中的一个重要组件&#xff0c;它负责将LLVM中间表示&#xff08;Intermediate Representation&#xff0c;IR&#xff09;转换为SelectionDAG&#xff08;选择有向无环图&#xff09;的形式。…

java selenium 元素点击不了

最近做了一个页面爬取&#xff0c;很有意思被机缘巧合下解决了。 这个元素很奇怪&#xff0c;用xpath可以定位元素&#xff0c;但是就是click()不了。 试过了网上搜的一些办法&#xff1a; //尝试一 WebElement a_tag driver.findElement(By.xpath("xxx")); a_tag…

适合马犬吃的狗粮有哪些?

亲爱的朋友们&#xff0c;你们是不是也在为家里的马犬挑选合适的狗粮而犯愁呢&#xff1f;&#x1f436;&#x1f35a; 今天&#xff0c;我就来和大家分享一下适合马犬吃的狗粮有哪些&#xff0c;以及为什么我要特别推荐福派斯鲜肉无谷狗粮。 首先&#xff0c;我们得了解马犬的…

ArmSoM-Sige RK3588开发板产品简介

让我们在 5 分钟内了解 Sige7。 简介​ ArmSoM-Sige7采用Rockchip RK3588新一代旗舰级八核64位处理器&#xff0c;主频高达2.4GHz&#xff0c;6 TOPS算力NPU&#xff0c;最大可配32GB大内存。支持8K视频编解码&#xff0c;拥有丰富的接口&#xff0c;支持双2.5G网口、WiFi6 &…

【leetcode热题】 二叉树的右视图

给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4]示例 2: 输入: [1,null,3] 输出: [1,3]示例 3: 输入: [] 输出: []解法一 题…

InstructGPT的流程介绍

1. Step1&#xff1a;SFT&#xff0c;Supervised Fine-Tuning&#xff0c;有监督微调。顾名思义&#xff0c;它是在有监督&#xff08;有标注&#xff09;数据上微调训练得到的。这里的监督数据其实就是输入Prompt&#xff0c;输出相应的回复&#xff0c;只不过这里的回复是人工…

element-ui checkbox 组件源码分享

简单分享 checkbox 组件&#xff0c;主要从以下三个方面来分享&#xff1a; 1、组件的页面结构 2、组件的属性 3、组件的方法 一、组件的页面结构 二、组件的属性 2.1 value / v-model 属性&#xff0c;绑定的值&#xff0c;类型 string / number / boolean&#xff0c;无…

Spring单元测试+Mockito

一&#xff0c;背景 单元测试基本上是开发逃不过的一个工作内容&#xff0c;虽然往往因为过于无聊&#xff0c;或者过于麻烦&#xff0c;而停止于项目的迭代之中&#xff0c;不了了之了。其实不是开发们懒&#xff0c;而是上头要求的测试覆盖率高&#xff0c;但是又没有好用的…