跳到主要内容从零搭建 Django + Vue 全栈应用:用户认证篇 | 极客日志Python大前端
从零搭建 Django + Vue 全栈应用:用户认证篇
综述由AI生成基于 Django 和 Vue 3 构建全栈应用的流程,重点实现了用户注册与登录功能。通过集成 Django REST Framework 和 SimpleJWT 完成后端认证逻辑,配置 Vue 3 前端组件进行交互,并详细解决了跨域(CORS)等常见问题。
不羁31 浏览 引言
在现代 Web 开发中,前后端分离已成为主流架构。后端负责数据处理和业务逻辑(如 Django),前端则专注于用户界面和交互(如 Vue)。本文将带你一步步构建一个基础的全栈应用,实现用户注册和登录功能,并详细记录过程中可能遇到的典型问题及其解决方案。
我们将使用:
一、后端搭建 (Django)
1. 环境准备
首先,创建并激活 Python 虚拟环境(推荐使用 venv 或 conda),然后安装所需包:
pip install django djangorestframework djangorestframework-simplejwt python-decouple
接着,创建 Django 项目和应用:
django-admin startproject myproject
cd myproject
python manage.py startapp accounts_api
2. Django 配置 (myproject/settings.py)
这部分是整个后端的核心配置。我们需要集成 DRF、SimpleJWT、以及处理跨域问题的 django-cors-headers。
import os
from pathlib import Path
from datetime import timedelta
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'your-secret-key-here'
DEBUG = True
ALLOWED_HOSTS = ['*']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
,
,
,
,
,
,
,
]
MIDDLEWARE = [
,
,
,
,
,
,
,
,
]
CORS_ALLOWED_ORIGINS = [
,
]
ROOT_URLCONF =
TEMPLATES = [
{
: ,
: [],
: ,
: {
: [
,
,
,
,
],
},
},
]
WSGI_APPLICATION =
DATABASES = {
: {
: ,
: BASE_DIR / ,
}
}
AUTH_PASSWORD_VALIDATORS = [
{: },
{: },
{: },
{: },
]
LANGUAGE_CODE =
TIME_ZONE =
USE_I18N =
USE_TZ =
STATIC_URL =
DEFAULT_AUTO_FIELD =
REST_FRAMEWORK = {
: (,),
: [],
}
SIMPLE_JWT = {
: timedelta(minutes=),
: timedelta(days=),
: ,
: ,
: ,
: SECRET_KEY,
: ,
: (,),
: ,
: ,
}
'django.contrib.sessions'
'django.contrib.messages'
'django.contrib.staticfiles'
'corsheaders'
'rest_framework'
'rest_framework_simplejwt'
'accounts_api'
'corsheaders.middleware.CorsMiddleware'
'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'
"http://localhost:5173"
'myproject.urls'
'BACKEND'
'django.template.backends.django.DjangoTemplates'
'DIRS'
'APP_DIRS'
True
'OPTIONS'
'context_processors'
'django.template.context_processors.debug'
'django.template.context_processors.request'
'django.contrib.auth.context_processors.auth'
'django.contrib.messages.context_processors.messages'
'myproject.wsgi.application'
'default'
'ENGINE'
'django.db.backends.sqlite3'
'NAME'
'db.sqlite3'
'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.NumericValidator'
'en-us'
'UTC'
True
True
'/static/'
'django.db.models.BigAutoField'
'DEFAULT_AUTHENTICATION_CLASSES'
'rest_framework_simplejwt.authentication.JWTAuthentication'
'DEFAULT_PERMISSION_CLASSES'
'rest_framework.permissions.AllowAny'
'ACCESS_TOKEN_LIFETIME'
60
'REFRESH_TOKEN_LIFETIME'
1
'ROTATE_REFRESH_TOKENS'
True
'BLACKLIST_AFTER_ROTATION'
True
'ALGORITHM'
'HS256'
'SIGNING_KEY'
'VERIFYING_KEY'
None
'AUTH_HEADER_TYPES'
'Bearer'
'USER_ID_FIELD'
'id'
'USER_ID_CLAIM'
'user_id'
重要提醒: corsheaders.middleware.CorsMiddleware 必须 放在 MIDDLEWARE 列表的最前面,否则它可能无法拦截到某些请求。
3. 序列化器 (accounts_api/serializers.py)
from rest_framework import serializers
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ('username', 'password')
def create(self, validated_data):
user = User.objects.create_user(
username=validated_data['username'],
password=validated_data['password']
)
return user
class LoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, data):
username = data.get('username')
password = data.get('password')
if username and password:
user = authenticate(username=username, password=password)
if user:
if not user.is_active:
raise serializers.ValidationError("用户账户已被禁用。")
data['user'] = user
else:
raise serializers.ValidationError("无法使用提供的凭据登录。")
else:
raise serializers.ValidationError("必须包含 'username' 和 'password'。")
return data
4. 视图 (accounts_api/views.py)
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework import status
from .serializers import RegisterSerializer, LoginSerializer
from rest_framework_simplejwt.tokens import RefreshToken
@api_view(['POST'])
@permission_classes([AllowAny])
def register_view(request):
serializer = RegisterSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
refresh = RefreshToken.for_user(user)
return Response({
'message': '用户注册成功!',
'refresh': str(refresh),
'access': str(refresh.access_token),
}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])
@permission_classes([AllowAny])
def login_view(request):
serializer = LoginSerializer(data=request.data)
if serializer.is_valid():
user = serializer.validated_data["user"]
refresh = RefreshToken.for_user(user)
return Response({
'message': '登录成功!',
'refresh': str(refresh),
'access': str(refresh.access_token),
}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
5. URLs (accounts_api/urls.py & myproject/urls.py)
from django.urls import path
from . import views
urlpatterns = [
path('register/', views.register_view, name='register'),
path('login/', views.login_view, name='login'),
]
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/accounts/', include('accounts_api.urls')),
]
6. 数据库迁移与启动
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
现在,Django 服务应在 http://127.0.0.1:8000/ 上运行,并提供 http://127.0.0.1:8000/api/accounts/register/ 和 http://127.0.0.1:8000/api/accounts/login/ 两个 API 端点。
二、前端搭建 (Vue 3)
1. 环境准备
npm create vue@latest my-vue-app
cd my-vue-app
npm install axios
2. 主要组件 (src/App.vue)
<template>
<div>
<h1>My App</h1>
<div v-if="!isLoggedIn">
<h2>Register</h2>
<form @submit.prevent="handleRegister">
<div>
<label for="reg_username">Username:</label>
<input type="text" v-model="registerForm.username" required />
</div>
<div>
<label for="reg_password">Password:</label>
<input type="password" v-model="registerForm.password" required />
</div>
<button type="submit">Register</button>
</form>
</div>
<div v-if="!isLoggedIn">
<h2>Login</h2>
<form @submit.prevent="handleLogin">
<div>
<label for="login_username">Username:</label>
<input type="text" v-model="loginForm.username" required />
</div>
<div>
<label for="login_password">Password:</label>
<input type="password" v-model="loginForm.password" required />
</div>
<button type="submit">Login</button>
</form>
</div>
<div v-if="isLoggedIn">
<p>Welcome, {{ currentUser }}!</p>
<button @click="handleLogout">Logout</button>
</div>
<div v-if="message" :class="messageType">{{ message }}</div>
</div>
</template>
<script>
import axios from 'axios';
// 将后端 API 地址提取为常量,方便维护
const API_BASE_URL = 'http://127.0.0.1:8000/api/accounts';
export default {
name: 'App',
data() {
return {
registerForm: { username: '', password: '' },
loginForm: { username: '', password: '' },
isLoggedIn: false,
currentUser: null,
message: '',
messageType: ''
};
},
methods: {
async handleRegister() {
try {
const response = await axios.post(`${API_BASE_URL}/register/`, this.registerForm);
console.log(response.data);
this.setMessage(response.data.message, 'success');
} catch (error) {
console.error('Registration error:', error.response?.data || error.message);
this.setMessage(
error.response?.data?.username?.[0] ||
error.response?.data?.password?.[0] ||
error.response?.data?.non_field_errors?.[0] ||
'Registration failed.',
'error'
);
}
},
async handleLogin() {
try {
const response = await axios.post(`${API_BASE_URL}/login/`, this.loginForm);
console.log(response.data);
localStorage.setItem('access_token', response.data.access);
localStorage.setItem('refresh_token', response.data.refresh);
this.currentUser = this.loginForm.username;
this.isLoggedIn = true;
this.setMessage(response.data.message, 'success');
this.loginForm.username = '';
this.loginForm.password = '';
} catch (error) {
console.error('Login error:', error.response?.data || error.message);
this.setMessage(
error.response?.data?.non_field_errors?.[0] || 'Login failed.',
'error'
);
}
},
handleLogout() {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
this.currentUser = null;
this.isLoggedIn = false;
this.setMessage('Logged out successfully.', 'success');
},
setMessage(text, type) {
this.message = text;
this.messageType = type;
setTimeout(() => {
this.message = '';
}, 3000);
},
checkAuthStatus() {
const token = localStorage.getItem('access_token');
if (token) {
this.isLoggedIn = true;
// 可以通过解析 token 获取用户名或调用 API 获取
// this.currentUser = ...
}
}
},
mounted() {
this.checkAuthStatus();
}
};
</script>
<style scoped>
#app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
form div {
margin-bottom: 10px;
}
label {
display: block;
margin-bottom: 5px;
}
input[type="text"], input[type="password"] {
width: 90%;
padding: 8px;
}
button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
.success {
color: green;
}
.error {
color: red;
}
</style>
3. 启动 Vue 应用
Vue 应用将在 http://localhost:5173 上启动。
三、常见问题与解决方案
在开发过程中,我遇到了一个非常典型且令人头疼的问题,值得单独拿出来记录一下。
问题现象:CORS 错误
当我尝试从前端发起注册请求时,浏览器控制台 Network 标签页显示 register/ 请求失败,状态码不明,响应数据也无法加载。Django 后端却打印出了 OPTIONS /api/accounts/register/ HTTP/1.1" 200 0 的日志。
问题分析
这是一个经典的 跨域资源共享 (CORS) 问题。
- 预检请求 (Preflight Request): 当前端发起一个复杂的请求(如 POST,带有自定义头)到另一个域名时,浏览器会先自动发送一个
OPTIONS 请求,询问服务器是否允许此次跨域请求。Django 成功响应了这个 OPTIONS 请求(返回 200),表示预检通过。
- 实际请求 (Actual Request): 预检通过后,浏览器才会发送实际的
POST 请求。然而,如果服务器没有为这个 POST 请求的响应添加正确的 CORS 头部(如 Access-Control-Allow-Origin),浏览器就会将响应拦截,并报告错误给前端代码。
解决方案
这个问题的根本原因是 django-cors-headers 中间件没有被正确加载或配置。
- 确保安装:
pip install django-cors-headers
- 正确配置
settings.py:
- 将
'corsheaders' 添加到 INSTALLED_APPS。
- 最重要的一点: 将
'corsheaders.middleware.CorsMiddleware' 放在 MIDDLEWARE 列表的最前面。
- 正确配置
CORS_ALLOWED_ORIGINS。
- 重启服务器: 修改配置后必须重启 Django 服务器 (
python manage.py runserver)。
完成以上配置后,POST 请求也能顺利收到带有正确 CORS 头部的响应,问题得以解决。
四、总结
通过本文,我们完成了 Django + Vue 全栈应用的基础用户认证功能。这个过程涉及到了前后端的通信、数据处理、安全性(密码哈希、JWT)、以及重要的跨域问题解决。这是一个很好的起点,后续可以在此基础上扩展更多功能,如用户资料管理、权限控制等。
相关免费在线工具
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online