作者: IT策士
10余年一线大厂经验,专注 IT 思维、架构、职场进阶。持续发布最新文章,助你少走弯路。
前言
上一篇我们完成了用户注册功能,数据库里已经积累了一批注册用户。但一个完整的身份认证体系,光有注册是不够的------用户还需要能够登录 和登出。
今天这篇文章,我们将基于 Django 内置的认证系统(django.contrib.auth),通过自定义认证后端实现多方式登录(手机号/邮箱/用户名),同时集成"记住我"、登录后跳转、导航栏状态切换等实用功能。
跟着我的步骤,半小时内让你的项目真正"认人"。
一、需求分析
我们要实现的登录功能清单:
| 功能点 | 说明 |
|---|---|
| 多方式登录 | 支持手机号、邮箱、用户名任一方式登录 |
| 错误提示 | 密码错误或用户不存在时给出明确提示 |
| 记住我 | 支持延长 session 有效期 |
| 导航栏切换 | 登录后显示"欢迎,XXX"及下拉菜单 |
| 智能跳转 | 登录后返回之前想访问的页面,否则回首页 |
| 安全登出 | 清除 session,回到首页 |
二、自定义认证后端
2.1 为什么要自定义?
Django 默认的认证后端只能通过 username 和 password 登录。但我们的注册逻辑允许用户用手机号或邮箱注册,username 字段存储的可能是手机号或邮箱前缀,这会导致用户记不住自己的用户名。
解决方案: 自定义认证后端,让系统同时匹配手机号、邮箱和用户名。
2.2 实现代码
在 apps/users/ 目录下创建 backends.py:
bash
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from .models import User
class MultiFieldAuthBackend(ModelBackend):
"""
支持手机号 / 邮箱 / 用户名 登录的自定义认证后端
"""
def authenticate(self, request, username=None, password=None, **kwargs):
try:
# 使用 Q 对象进行 OR 查询,同时匹配三个字段
user = User.objects.get(
Q(username=username) |
Q(email=username) |
Q(phone=username)
)
except User.DoesNotExist:
return None
# 校验密码(Django 自动处理密码哈希比对)
if user.check_password(password):
return user
return None
2.3 工作原理
Django 调用 authenticate() 时会遍历所有配置的认证后端。我们的自定义后端优先尝试匹配手机号、邮箱或用户名,只要用户输入的是三者之一,都能找到对应账户。
2.4 注册后端
在 django_ecommerce/settings.py 中声明:
bash
AUTHENTICATION_BACKENDS = [
'users.backends.MultiFieldAuthBackend', # 自定义后端(优先)
'django.contrib.auth.backends.ModelBackend', # 保留默认后端
]
三、登录表单
在 apps/users/forms.py 中追加登录表单:
bash
from django import forms
class LoginForm(forms.Form):
username = forms.CharField(
label='账号',
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '手机号 / 邮箱 / 用户名'
})
)
password = forms.CharField(
label='密码',
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'placeholder': '请输入密码'
})
)
remember = forms.BooleanField(
label='记住我',
required=False,
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
)
设计思路: 表单只负责收集和基础校验,真正的认证逻辑交给视图处理,保持关注点分离。
四、登录视图
编辑 apps/users/views.py,添加登录逻辑:
bash
from django.contrib.auth import authenticate, login, logout
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import LoginForm
def user_login(request):
# 已登录用户直接重定向到首页
if request.user.is_authenticated:
return redirect('home')
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
remember = form.cleaned_data.get('remember')
# 调用自定义后端进行认证
user = authenticate(request, username=username, password=password)
if user is not None:
if user.is_active:
# 执行登录,将用户写入 session
login(request, user)
# 处理"记住我"逻辑
if remember:
# Session 有效期 30 天
request.session.set_expiry(60 * 60 * 24 * 30)
else:
# 浏览器关闭即失效
request.session.set_expiry(0)
# 跳转到 next 参数指定的页面,否则回首页
next_url = request.GET.get('next', 'home')
messages.success(request, f'欢迎回来,{user.username}!')
return redirect(next_url)
else:
messages.error(request, '该账号已被禁用,请联系管理员。')
else:
messages.error(request, '账号或密码错误,请重试。')
else:
form = LoginForm()
return render(request, 'users/login.html', {'form': form})
核心要点解析
| 要点 | 说明 |
|---|---|
request.user.is_authenticated |
检查用户是否已登录,避免重复登录 |
authenticate() |
自动调用我们自定义的 MultiFieldAuthBackend |
login(request, user) |
将认证信息写入 session,完成登录 |
set_expiry() |
控制 session 有效期,实现"记住我" |
next 参数 |
Django 内置跳转机制,登录后自动返回原页面 |
五、登出视图
继续在 views.py 末尾添加,逻辑非常简单:
bash
def user_logout(request):
logout(request) # 清除当前 session 中的所有认证数据
messages.success(request, '您已成功退出登录。')
return redirect('home')
六、配置 URL 路由
编辑 apps/users/urls.py,追加登录和登出路由:
bash
from django.urls import path
from . import views
urlpatterns = [
path('register/', views.register, name='register'),
path('send_sms/', views.send_sms_code, name='send_sms'),
path('activate/<int:user_id>/<str:token>/', views.activate_email, name='activate_email'),
path('login/', views.user_login, name='login'),
path('logout/', views.user_logout, name='logout'),
]
七、登录页面模板
创建 apps/users/templates/users/login.html:
bash
{% extends 'base.html' %}
{% block title %}用户登录{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6 col-lg-5">
<div class="card shadow-sm">
<div class="card-body p-4">
<h3 class="text-center mb-4">🔐 用户登录</h3>
<form method="post" novalidate>
{% csrf_token %}
<!-- 全局错误提示 -->
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors.0 }}
</div>
{% endif %}
<!-- 账号输入 -->
<div class="mb-3">
<label class="form-label">{{ form.username.label }}</label>
{{ form.username }}
{% if form.username.errors %}
<div class="text-danger small">{{ form.username.errors.0 }}</div>
{% endif %}
</div>
<!-- 密码输入 -->
<div class="mb-3">
<label class="form-label">{{ form.password.label }}</label>
{{ form.password }}
{% if form.password.errors %}
<div class="text-danger small">{{ form.password.errors.0 }}</div>
{% endif %}
</div>
<!-- 记住我 -->
<div class="mb-3 form-check">
{{ form.remember }}
<label class="form-check-label" for="{{ form.remember.id_for_label }}">
{{ form.remember.label }}
</label>
</div>
<button type="submit" class="btn btn-primary w-100">登录</button>
</form>
<p class="text-center mt-3">
还没有账号?<a href="{% url 'users:register' %}">立即注册</a>
</p>
</div>
</div>
</div>
</div>
{% endblock %}
八、更新导航栏(动态切换)
打开 templates/base.html,替换导航栏的用户相关部分:
bash
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'home' %}">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">商品</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">购物车</a>
</li>
{% if user.is_authenticated %}
<!-- 已登录:显示用户下拉菜单 -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
👤 {{ user.username }}
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="#">个人中心</a></li>
<li><a class="dropdown-item" href="#">我的订单</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="{% url 'admin:index' %}">后台管理</a></li>
<li><a class="dropdown-item" href="{% url 'users:logout' %}">退出登录</a></li>
</ul>
</li>
{% else %}
<!-- 未登录:显示登录/注册入口 -->
<li class="nav-item">
<a class="nav-link" href="{% url 'users:login' %}">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'users:register' %}">注册</a>
</li>
{% endif %}
</ul>
效果: 未登录时显示"登录"和"注册",登录后显示用户名下拉菜单,包含个人中心、订单、后台管理、退出登录等入口。
九、完整流程测试
启动开发服务器:
bash
python manage.py runserver
9.1 手机号登录测试
-
访问
http://127.0.0.1:8000/users/login/ -
输入手机号
13800138000(上一篇注册的手机号)和密码 -
勾选"记住我",点击登录
预期结果:
-
终端输出
302重定向状态码 -
浏览器跳转到首页
-
导航栏显示
👤 13800138000下拉菜单 -
页面顶部显示绿色消息:"欢迎回来,13800138000!"
9.2 邮箱登录测试
-
点击导航栏"退出登录",确认看到成功提示
-
重新进入登录页,输入邮箱
test@example.com和密码 -
点击登录
预期结果: 同样登录成功,验证自定义后端对邮箱的匹配支持。
9.3 错误情况测试
输入错误的密码,页面应显示红色提示:
账号或密码错误,请重试。
终端返回 200 状态码,表示停留在登录页并展示错误信息。
9.4 登出流程测试
点击"退出登录":
-
终端输出
302重定向 -
回到首页,导航栏恢复为"登录"和"注册"
-
页面显示绿色消息:"您已成功退出登录。"
十、未登录访问保护(预告)
某些页面(如个人中心、购物车)必须登录才能访问。Django 提供了 login_required 装饰器,后续我们会这样使用:
bash
from django.contrib.auth.decorators import login_required
@login_required(login_url='users:login')
def personal_center(request):
...
当未登录用户访问被保护页面时,系统会自动跳转到登录页,并带上 ?next=/原路径 参数。这正是我们在登录视图中已经支持的 next 跳转机制,登录后自动返回原页面。
十一、总结与下集预告
本篇完成清单
-
✅ 自定义认证后端:支持手机号/邮箱/用户名三种方式登录
-
✅ 登录视图 :集成"记住我"功能和
next智能跳转 -
✅ 登出功能:一行代码清除 session
-
✅ 导航栏动态切换:根据登录状态显示不同菜单
-
✅ 完整测试:覆盖登录、登出、错误提示等全部流程
身份认证的基础已经打牢。从下一篇开始,我们将进入用户体系的深化部分。
下集预告
第 8 篇:个人中心页面
-
用户信息展示
-
修改密码
-
更换手机号/邮箱
让用户真正拥有自己的"小窝"。
📌 想了解更多? 搜索其它平台「IT策士」,一起升级 IT 思维!