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
    |
    \---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/ 接口。否则将拒绝。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android Jetpack Compose基础之组件的帧渲染

Android Jetpack Compose基础之组件的帧渲染 组合布局LayoutModifier示例 LayoutCompsable示例 绘制CanvasDrawModifierDrawModifier-drawWithContent示例 DrawModifier-drawBehind源码示例 DrawModifier-drawWithCache源码示例 拓展Modifier.graphicsLayer Android View 系统&…

6-191 拓扑排序

一项工程由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其他子任务后才能执行。例如,下图表示了一项工程若干子任务之间的先后关系。 编写函数输出所有子任务的拓扑序列。 函数接口定义: Status Push_SeqStack(SeqStack &s, ElemType x)//入栈,x入到…

Polar 2024春季个人挑战赛 Jay17 WP

Polar 2024春季个人挑战赛 Rank&#xff1a;7 【WEB】机器人 开题 起手敏感文件robots.txt 【WEB】PHP反序列化初试 最简单的php反序列化 POC&#xff1a; <?php class Easy{public $name;public function __wakeup(){echo $this->name;} } class Evil{public $evi…

2、Jenkins持续集成-gitlab安装和源码上传

文章目录 1、Gitlab代码托管服务器安装2、源代码上传托管 环境&资源准备 统一采用VMware中安装CentOS7&#xff0c;安装教程&#xff0c;统一设置静态IP资源包都存在于我的资源里面 资源版本&位置 名称机器IP软件代码托管服务器192.168.2.100Gitlab-12.4.2持续集成服…

idm下载器 idm网络加速器 Internet Download Manager(IDM)免激活不弹窗版

idm下载器官方版英文名为Internet Download Manager。是一款界面简单、体积小巧下载速度飞快的下载工具&#xff0c;支持断点续传、多个任务同时下载&#xff0c;同时还支持限速下载&#xff0c;在自义文件类型里还可以自由设置下载的文件类型。 一、IDM软件安装 Internet Dow…

Linux安装Nginx及配置TCP负载均衡

目录 1、安装编译工具及库文件2、下载解压Nginx压缩包3、Ngnix配置Tcp负载均衡4、配置Ngnix的文件5、Nginx启动 1、安装编译工具及库文件 yum -y install make zlib zlib-devel gcc-c libtool openssl openssl-devel pcre-devel2、下载解压Nginx压缩包 wget https://nginx.o…

Docker 安装 Tomcat

目录 1. 总体步骤 2.安装tomcat 2.1 搜索 2.2 拉取 2.3 查看是否拉取到镜像 2.4 运行镜像 2.5 访问tomcat首页 1. 总体步骤 搜索镜像拉取镜像查看镜像启动镜像停止容器移除容器 2.安装tomcat 2.1 搜索 2.2 拉取 docker pull tomcat 2.3 查看是否拉取到镜像 docker …

连接医患的桥梁:深入了解互联网医院APP的开发与优化

当下&#xff0c;互联网技术的不断发展&#xff0c;越来越多的医院和医疗机构开始关注并投入到互联网医院APP的开发与优化中。接下来&#xff0c;小编将与大家共同探讨互联网医院APP的开发与优化。 一、互联网医院APP的开发原则 &#xff08;1&#xff09;用户体验至上 界面设…

卷积篇 | YOLOv8改进之主干网络中引入可变形卷积DConv

前言:Hello大家好,我是小哥谈。可变形卷积模块是一种改进的卷积操作,它可以更好地适应物体的形状和尺寸,提高模型的鲁棒性。可变形卷积模块的实现方式是在标准卷积操作中增加一个偏移量offset,使卷积核能够在训练过程中扩展到更大的范围,从而实现对尺度、长宽比和旋转等各…

git基础-查看提交历史

查看提交历史 在创建了多个提交之后&#xff0c;或者如果克隆了一个具有现有提交历史的存储库&#xff0c;可能会想要回顾一下发生了什么。最基本和强大的工具就是 git log 命令。 运行下git log查看下输出状态 默认情况下&#xff0c;不带任何参数运行 git log 命令会以逆时…

拌合楼管理系统(八) c#海康威视摄像头车牌识别

前言: c#调用海康威视SDK实现车牌识别 原本以为海康威视sdk的Demo里面没有车牌识别的实例,后来发现自己肤浅了,官方是有提供的,只是车牌识别是通过安防布警的方式实现的.程序主动监听,触发告警后获取到车牌信息. 一、接口调用的流程&#xff1a; 首先初始化sdk -> 开…

袁志佳:前端全栈工程师精英班

教程介绍 本套课程涵盖大前端的全部领域&#xff0c;并对传统的Web前端全栈深入教学。如利用前端知识开发 AI、VR、AR、iOS、Android、PC、Server、智能硬件。 同时我们将核心打造 JavaScript语言新发展、Vue源码分析、前端持续集成方案汇总、MV*框架深度分析 、前端图形学、N…

亚稳态及其解决办法

异步电路 亚稳态 亚稳态亚稳态的产生原因什么是同步异步信号怎么消除亚稳态 亚稳态 在数字电路中&#xff0c;每一位数据不是1&#xff08;高电平&#xff09;就是0&#xff08;低电平&#xff09;。当然对于具体的电路来说&#xff0c;并非1&#xff08;高电平&#xff09;就是…

elementary OS7 Ubuntu 22.04中硬盘挂载报错

elementary OS7 Ubuntu 22.04中硬盘挂载报错 背景目标思路解决方法 背景 上周末安装elementaryos7的过程中将windows10的引导文件搞丢了&#xff0c;这两天准备修复一下&#xff0c;保险期间将固态硬盘上的文件备份到移动硬盘上&#xff0c;备份过程中出现报错的问题&#xff…

2024常用Web支付开发讲解教程

课程介绍 本教程为web支付开发&#xff0c;讲解了最常用的两钟支付&#xff1a;支付宝支付和微信支付&#xff0c;服务器配置和API对接&#xff0c;学完本课程可以学会微信支付、和支付宝支付开发。 学习资料 链接&#xff1a;https://pan.baidu.com/s/19WarLP2dO4dFvNbIHLU…

C++类的6个默认成员函数(构造)

C类和对象基础-CSDN博客https://blog.csdn.net/lh11223326/article/details/136834917?spm1001.2014.3001.5501 目录 1.构造函数 概念 特性 2.析构函数 概念 特性 3.拷贝构造函数 概念 特征 4.赋值运算符重载&#xff08;构造实现&#xff09; 运算符重载 赋值运算…

【常见BUG系列】Java 编程中的 NoSuchFieldError 异常:原因与解决方法

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

云原生(五)、Docker-Swarm集群

基础环境说明 1、环境准备 1、启动4台服务器&#xff08;在同一个网段内&#xff09;。 2、重命名4台服务器&#xff0c;方便区分。 hostnamectl set-hostname swarm1 reboot安装docker。参考文章&#xff1a;云原生&#xff08;二&#xff09;、Docker基础 2、DockerSwarm…

Autosar Crypto Interface学习笔记

文章目录 前言Functional specificationError classificationError detection API specificationType DefinitionsFunction definitionsGeneral APICryIf_InitCryIf_GetVersionInfo Job Processing InterfaceCryIf_ProcessJobDispatch Key IDs匹配KeyId Job Cancellation Inter…

springcloud-Nacos 更强大的注册中心组件

Nacos 实际上从设计思想来说 Eureka 和 nacos 是一样的。 后者是Alibaba推出的 一款更强大 功能更丰富的注册中心 你可以理解为Eureka的高配版 技多不压身既然了解了 Eureka, nacos也来学习一下吧&#xff01; 安装 首先nacos不像eureka 直接pom里面引个依赖就搞定了&#…