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

news2024/12/23 3:05:15

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
    |
    \---views
            AboutView.vue
            HomeView.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 = true
    	const 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 = userName
    				loginStatus.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-form
    	    ref="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 author
                serializer.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.coloradmin.cn/o/1538773.html

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

相关文章

蓝桥杯 2022 省B 李白打酒加强版

这题用递归暴力的方法如下&#xff1a; #include<iostream> #include<bits/stdc.h> using namespace std; int num; int N,M; void dfs(int now,int n,int m) {if(now<0 || n>N ||m>M)return ;if(nN && mM){if(now1)num1;return;}dfs(now-1,n,m1…

DDR4总结最全纯干货分享

DDR存储器发展的主要方向一言以蔽之&#xff0c;是更高速率&#xff0c;更低电压&#xff0c;更密的存储密度&#xff0c;从而实现更好的性能。 DDR4 SDRAM&#xff08;Double Data Rate Fourth SDRAM&#xff09;&#xff1a;DDR4提供比DDR3/ DDR2更低的供电电压1.2V以及更高的…

如果搭建axb回拨

AXB回拨技术是一种先进的电话通讯技术&#xff0c;它通过在A&#xff08;主叫方&#xff09;与B&#xff08;被叫方&#xff09;之间引入一个中间号码X&#xff0c;实现了双方的通话连接。这种技术可以有效避免直接拨打被叫方的电话号码&#xff0c;从而保护了用户的隐私。 具体…

GPT2从放弃到入门(三)

引言 上篇文章我们看到了如何从零训练一个聊天机器人&#xff0c;本文在此基础上介绍各种生成策略的原理和实现。最后通过Gradio构建一个聊天机器人应用。 定义生成框架 def generate(model,tokenizer,prompt,max_length255,temperature1.0,top_k50,top_p1.0,repetition_pen…

【WEEK4】 【DAY4】AJAX第一部分【中文版】

【WEEK4】 【DAY4】AJAX第一部分【中文版】 2024.3.21 Thursday 目录 8.AJAX8.1.简介8.2.伪造ajax8.2.1.新建module&#xff1a;springmvc-06-ajax8.2.2.添加web支持&#xff0c;导入pom依赖8.2.2.1.修改web.xml8.2.2.2.新建jsp文件夹 8.2.3.新建applicationContext.xml8.2.4.…

tftp使用

下载 sudo apt-get install tftpd-hpa 创建文件夹 mkdir /home/ljl/work/tftpd mkdir /home/ljl/tftpd chmod 777 tftpd/编辑 sudo vim /etc/default/tftpd-hpa //服务器端 sudo apt-get install tftp-hpa //客户端编辑权限 sudo vi /etc/default/tftpd-hpa 内容&#xff1…

智能风扇的新篇章:唯创知音WTK6900G语音识别芯片引领行业革新

随着科技浪潮的推进&#xff0c;智能化技术逐渐渗透到生活的每一个角落&#xff0c;家电领域尤为明显。风扇&#xff0c;这一夏日清凉神器&#xff0c;也通过智能化改造&#xff0c;焕发出前所未有的光彩。其中&#xff0c;智能语音控制功能的加入&#xff0c;为风扇的使用带来…

算法系列--递归(2)

&#x1f495;"什么样的灵魂就要什么样的养料&#xff0c;越悲怆的时候我越想嬉皮。"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;算法系列–递归(2) 前言:今天带来的是算法系列--递归(2)的讲解,包含六个和二叉树相关的题目哦 1.计算布尔⼆叉树的…

Redis中文乱码问题

最近排查问题&#xff0c;发现之前的开发将日志写在redis缓存中&#xff08;不建议这样做&#xff09;&#xff0c;我在查看日志的时候发现没办法阅读&#xff0c;详细是这样的&#xff1a; 查阅资料后发现是进制问题&#xff0c;解决方法是启动客户端的时候将redis-cli改为red…

【RPG Maker MV 仿新仙剑 战斗场景UI (八)】

RPG Maker MV 仿新仙剑 战斗场景UI 八 状态及装备场景代码效果 状态及装备场景 本计划在战斗场景中直接制作的&#xff0c;但考虑到在战斗场景中加入太多的窗口这不太合适&#xff0c;操作也繁琐&#xff0c;因此直接使用其他场景。 代码 Pal_Window_EquipStatus.prototype.…

STM32之HAL开发——启动文件详解【精华版】

启动文件介绍 启动文件是使用机器认识的汇编语言&#xff0c;由汇编编写&#xff0c;是系统上电复位后第一个执行的程序&#xff0c;经过一些必要的配置&#xff0c;最终能够调用 main 函数&#xff0c;使得用户程序能够在 MCU上正常运行起来的必备文件。 无论是是何种MCU&…

腾讯三面被问到有没有参加过CTF_我反手就是一套军体拳打得面试官哑口无言!

目录 ​ 前言&#xff1a; 正文&#xff1a; 什么是CTF&#xff1f; 什么是PWN? 为什么要学CTF&#xff1f; CTF竞赛模式&#xff1a; CTF各大题型简介&#xff1a; 学之前的思考&#xff1a;分析赛题情况 常规做法 CTF比赛需要的知识储备 CTF比赛的神器&#xff…

基于甘特图的资源调度优化策略

资源在项目管理中是一个永恒的话题。无论人力、物力还是财力资源,总是捉襟见肘,都希望用最少的资源完成最大的工作。这就要求我们在资源调度方面果断精准,做到最优化。而甘特图作为项目时间规划的重要工具,恰恰能为资源调度提供绝佳帮助。 甘特图能反映出任务之间的制约关系,有…

vulnhub-----pWnOS1.0靶机

文章目录 1.信息收集2.漏洞测试3.爆破hash4.提权 首先拿到一台靶机&#xff0c;就需要知道靶机的各种信息&#xff08;IP地址&#xff0c;开放端口&#xff0c;有哪些目录&#xff0c;什么框架&#xff0c;cms是什么&#xff0c;网页有什么常见的漏洞&#xff0c;如sql注入&…

DC-3靶机

一.环境搭建 下载地址&#xff1a; http://www.five86.com/downloads/DC-3-2.zip 下载不下来的可以用迅雷输入上面的网址进行下载 虚拟机配置&#xff1a; 切换连接桥接模式为nat模式&#xff0c;启动靶机&#xff0c;出现如下报错&#xff0c;进入虚拟机配置 选中CD/DVD&…

C语言:自定义类型(结构体)

目录 一、结构的特殊声明二、结构的自引用三、结构体内存对齐1.对齐规则2.为什么存在内存对齐(1)平台原因 (移植原因)&#xff1a;(2)性能原因&#xff1a; 3.修改默认对齐数 四、结构体传参五、结构体实现位段1.什么是位段2.位段的内存分配3.位段的跨平台问题4.位段使用的注意…

使用Python和OpenFOAM进行流体力学模拟的基础示例

流体力学模拟通常涉及复杂的数学方程和数值方法&#xff0c;例如计算流体动力学(CFD)。OpenFOAM是一个开源的CFD工具箱&#xff0c;它使用C编写&#xff0c;但可以通过Python脚本进行自动化和定制。 以下是一个简单的示例&#xff0c;展示如何使用Python和OpenFOAM进行流体力学…

【嵌入式开发 Linux 常用命令系列 4.3 -- git add 时单独排除某个目录或者文件】

文章目录 git add 时单独排除某个目录或者文件使用 .gitignore 文件使用命令行排除文件或目录 git add 时单独排除某个目录或者文件 在使用 git add 命令时&#xff0c;如果你想要排除特定的目录或文件&#xff0c;可以使用 .gitignore 文件或使用路径规范来指定不想添加的文件…

YOLOv5-Y5周:yolo.py文件解读

本文为&#x1f517;365天深度学习训练营 中的学习记录博客 原作者&#xff1a;K同学啊|接辅导、项目定制 我的环境&#xff1a; 1.语言&#xff1a;python3.7 2.编译器&#xff1a;pycharm 3.深度学习框架Tensorflow/Pytorch 1.8.0cu111 一、代码解读 import argparse i…

pcie dllp FC

关于pcie dllp FC内容&#xff1a; 源地址&#xff1a; PCIe&#xff08;三&#xff09;—— PCIe协议栈&#xff0c;事务层和数据链路层 | Soul Orbit 3.2. 控制消息&#xff1a;DLLP&#xff08;Data Link Layer Packet&#xff09; 除了传输TLP数据包之外&#xff0c;数…