作者: IT策士?
10余年一線大廠經(jīng)驗,專注 IT 思維、架構(gòu)、職場進階。持續(xù)發(fā)布最新文章,助你少走彎路。
前言
上一篇我們完成了用戶注冊功能,數(shù)據(jù)庫里已經(jīng)積累了一批注冊用戶。但一個完整的身份認證體系,光有注冊是不夠的——用戶還需要能夠登錄和登出。
今天這篇文章,我們將基于 Django 內(nèi)置的認證系統(tǒng)(django.contrib.auth),通過自定義認證后端實現(xiàn)多方式登錄(手機號/郵箱/用戶名),同時集成"記住我"、登錄后跳轉(zhuǎn)、導航欄狀態(tài)切換等實用功能。
跟著我的步驟,半小時內(nèi)讓你的項目真正"認人"。
一、需求分析
我們要實現(xiàn)的登錄功能清單:
| 功能點 | 說明 |
|---|---|
| 多方式登錄 | 支持手機號、郵箱、用戶名任一方式登錄 |
| 錯誤提示 | 密碼錯誤或用戶不存在時給出明確提示 |
| 記住我 | 支持延長 session 有效期 |
| 導航欄切換 | 登錄后顯示"歡迎,XXX"及下拉菜單 |
| 智能跳轉(zhuǎn) | 登錄后返回之前想訪問的頁面,否則回首頁 |
| 安全登出 | 清除 session,回到首頁 |
二、自定義認證后端
2.1 為什么要自定義?
Django 默認的認證后端只能通過 username 和 password 登錄。但我們的注冊邏輯允許用戶用手機號或郵箱注冊,username 字段存儲的可能是手機號或郵箱前綴,這會導致用戶記不住自己的用戶名。
解決方案: 自定義認證后端,讓系統(tǒng)同時匹配手機號、郵箱和用戶名。
2.2 實現(xiàn)代碼
在 apps/users/ 目錄下創(chuàng)建 backends.py:
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 None2.3 工作原理
Django 調(diào)用 authenticate() 時會遍歷所有配置的認證后端。我們的自定義后端優(yōu)先嘗試匹配手機號、郵箱或用戶名,只要用戶輸入的是三者之一,都能找到對應賬戶。
2.4 注冊后端
在 django_ecommerce/settings.py 中聲明:
AUTHENTICATION_BACKENDS = [
'users.backends.MultiFieldAuthBackend', # 自定義后端(優(yōu)先)
'django.contrib.auth.backends.ModelBackend', # 保留默認后端
]三、登錄表單
在 apps/users/forms.py 中追加登錄表單:
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'})
)設(shè)計思路: 表單只負責收集和基礎(chǔ)校驗,真正的認證邏輯交給視圖處理,保持關(guān)注點分離。
四、登錄視圖
編輯 apps/users/views.py,添加登錄邏輯:
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')
# 調(diào)用自定義后端進行認證
user = authenticate(request, username=username, password=password)
if user is not None:
if user.is_active:
# 執(zhí)行登錄,將用戶寫入 session
login(request, user)
# 處理"記住我"邏輯
if remember:
# Session 有效期 30 天
request.session.set_expiry(60 * 60 * 24 * 30)
else:
# 瀏覽器關(guān)閉即失效
request.session.set_expiry(0)
# 跳轉(zhuǎn)到 next 參數(shù)指定的頁面,否則回首頁
next_url = request.GET.get('next', 'home')
messages.success(request, f'歡迎回來,{user.username}!')
return redirect(next_url)
else:
messages.error(request, '該賬號已被禁用,請聯(lián)系管理員。')
else:
messages.error(request, '賬號或密碼錯誤,請重試。')
else:
form = LoginForm()
return render(request, 'users/login.html', {'form': form})核心要點解析
| 要點 | 說明 |
|---|---|
request.user.is_authenticated |
檢查用戶是否已登錄,避免重復登錄 |
authenticate() |
自動調(diào)用我們自定義的 MultiFieldAuthBackend
|
login(request, user) |
將認證信息寫入 session,完成登錄 |
set_expiry() |
控制 session 有效期,實現(xiàn)"記住我" |
next 參數(shù) |
Django 內(nèi)置跳轉(zhuǎn)機制,登錄后自動返回原頁面 |
五、登出視圖
繼續(xù)在 views.py 末尾添加,邏輯非常簡單:
def user_logout(request):
logout(request) # 清除當前 session 中的所有認證數(shù)據(jù)
messages.success(request, '您已成功退出登錄。')
return redirect('home')六、配置 URL 路由
編輯 apps/users/urls.py,追加登錄和登出路由:
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'),
]七、登錄頁面模板
創(chuàng)建 apps/users/templates/users/login.html:
{% 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 %}八、更新導航欄(動態(tài)切換)
打開 templates/base.html,替換導航欄的用戶相關(guān)部分:
<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>效果: 未登錄時顯示"登錄"和"注冊",登錄后顯示用戶名下拉菜單,包含個人中心、訂單、后臺管理、退出登錄等入口。
九、完整流程測試
啟動開發(fā)服務(wù)器:
python manage.py runserver9.1 手機號登錄測試
訪問
http://127.0.0.1:8000/users/login/輸入手機號
13800138000(上一篇注冊的手機號)和密碼勾選"記住我",點擊登錄
預期結(jié)果:
終端輸出
302重定向狀態(tài)碼瀏覽器跳轉(zhuǎn)到首頁
導航欄顯示
?? 13800138000下拉菜單頁面頂部顯示綠色消息:"歡迎回來,13800138000!"
9.2 郵箱登錄測試
點擊導航欄"退出登錄",確認看到成功提示
重新進入登錄頁,輸入郵箱
test@example.com和密碼點擊登錄
預期結(jié)果: 同樣登錄成功,驗證自定義后端對郵箱的匹配支持。
9.3 錯誤情況測試
輸入錯誤的密碼,頁面應顯示紅色提示:
賬號或密碼錯誤,請重試。
終端返回 200 狀態(tài)碼,表示停留在登錄頁并展示錯誤信息。
9.4 登出流程測試
點擊"退出登錄":
終端輸出
302重定向回到首頁,導航欄恢復為"登錄"和"注冊"
頁面顯示綠色消息:"您已成功退出登錄。"
十、未登錄訪問保護(預告)
某些頁面(如個人中心、購物車)必須登錄才能訪問。Django 提供了 login_required 裝飾器,后續(xù)我們會這樣使用:
from django.contrib.auth.decorators import login_required
@login_required(login_url='users:login')
def personal_center(request):
...當未登錄用戶訪問被保護頁面時,系統(tǒng)會自動跳轉(zhuǎn)到登錄頁,并帶上 ?next=/原路徑 參數(shù)。這正是我們在登錄視圖中已經(jīng)支持的 next 跳轉(zhuǎn)機制,登錄后自動返回原頁面。
十一、總結(jié)與下集預告
本篇完成清單
? 自定義認證后端:支持手機號/郵箱/用戶名三種方式登錄
? 登錄視圖:集成"記住我"功能和
next智能跳轉(zhuǎn)? 登出功能:一行代碼清除 session
? 導航欄動態(tài)切換:根據(jù)登錄狀態(tài)顯示不同菜單
? 完整測試:覆蓋登錄、登出、錯誤提示等全部流程
身份認證的基礎(chǔ)已經(jīng)打牢。從下一篇開始,我們將進入用戶體系的深化部分。
下集預告
第 8 篇:個人中心頁面
用戶信息展示
修改密碼
更換手機號/郵箱
讓用戶真正擁有自己的"小窩"。
?? 想了解更多? 搜索公眾號、今日頭條「IT策士」,一起升級 IT 思維!