Django 提供了一个强大且灵活的认证系统,用于处理用户账户、组、权限和基于会话的认证。它提供了完整的用户管理功能,包括注册、登录、注销、密码重置等。
| 功能 | 描述 | URL |
|---|---|---|
| 登录 | 用户认证 | /accounts/login/ |
| 注销 | 结束会话 | /accounts/logout/ |
| 密码修改 | 更改密码 | /accounts/password_change/ |
| 密码重置 | 忘记密码恢复 | /accounts/password_reset/ |
Django 使用内置的 User 模型管理用户,也支持自定义用户模型。
from django.contrib.auth.models import User
# 创建用户
user = User.objects.create_user(
username='john',
email='john@example.com',
password='password123'
)
# 创建超级用户
admin_user = User.objects.create_superuser(
username='admin',
email='admin@example.com',
password='admin123'
)
# 验证用户
from django.contrib.auth import authenticate
user = authenticate(username='john', password='password123')
if user is not None:
# 用户认证成功
print(f"用户 {user.username} 认证成功")
else:
# 认证失败
print("认证失败")
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
# 添加额外字段
phone_number = models.CharField(max_length=15, blank=True)
date_of_birth = models.DateField(null=True, blank=True)
profile_picture = models.ImageField(
upload_to='profile_pics/',
null=True,
blank=True
)
# 自定义方法
def get_full_name(self):
return f"{self.first_name} {self.last_name}".strip()
def __str__(self):
return self.email # 使用邮箱作为标识
class Meta:
# 按邮箱排序
ordering = ['email']
# 指定自定义用户模型
AUTH_USER_MODEL = 'myapp.CustomUser'
# 认证后端
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
# 可以添加其他认证后端
]
# 登录URL
LOGIN_URL = '/accounts/login/'
# 登录后重定向URL
LOGIN_REDIRECT_URL = '/dashboard/'
# 注销后重定向URL
LOGOUT_REDIRECT_URL = '/'
Django 提供了内置的视图来处理常见的认证操作,也可以创建自定义视图。
from django.urls import path, include
from django.contrib.auth import views as auth_views
urlpatterns = [
# 内置认证URL
path('accounts/', include('django.contrib.auth.urls')),
# 或者单独配置
path('accounts/login/',
auth_views.LoginView.as_view(template_name='registration/login.html'),
name='login'),
path('accounts/logout/',
auth_views.LogoutView.as_view(),
name='logout'),
path('accounts/password_change/',
auth_views.PasswordChangeView.as_view(),
name='password_change'),
path('accounts/password_change/done/',
auth_views.PasswordChangeDoneView.as_view(),
name='password_change_done'),
path('accounts/password_reset/',
auth_views.PasswordResetView.as_view(),
name='password_reset'),
path('accounts/password_reset/done/',
auth_views.PasswordResetDoneView.as_view(),
name='password_reset_done'),
path('accounts/reset/<uidb64>/<token>/',
auth_views.PasswordResetConfirmView.as_view(),
name='password_reset_confirm'),
path('accounts/reset/done/',
auth_views.PasswordResetCompleteView.as_view(),
name='password_reset_complete'),
]
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib import messages
from django.contrib.auth.decorators import login_required
def custom_login(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
# 重定向到next参数或默认页面
next_url = request.GET.get('next', '/dashboard/')
return redirect(next_url)
else:
messages.error(request, '用户名或密码错误')
return render(request, 'accounts/login.html')
def custom_logout(request):
logout(request)
messages.success(request, '您已成功退出登录')
return redirect('home')
@login_required
def dashboard(request):
return render(request, 'accounts/dashboard.html')
{% extends 'base.html' %}
{% block content %}
<div class="login-form">
<h2 class="text-center mb-4">用户登录</h2>
{% if form.errors %}
<div class="alert alert-danger">
用户名或密码错误,请重试。
</div>
{% endif %}
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="{{ form.username.id_for_label }}">用户名</label>
{{ form.username }}
</div>
<div class="form-group">
<label for="{{ form.password.id_for_label }}">密码</label>
{{ form.password }}
</div>
<button type="submit" class="btn btn-django btn-block">登录</button>
<div class="text-center mt-3">
<a href="{% url 'password_reset' %}">忘记密码?</a>
</div>
</form>
</div>
{% endblock %}
创建用户注册功能,允许新用户创建账户。
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from .models import CustomUser
class UserRegisterForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
# 或者使用自定义用户模型
# model = CustomUser
fields = ['username', 'email', 'password1', 'password2']
def clean_email(self):
email = self.cleaned_data.get('email')
if User.objects.filter(email=email).exists():
raise forms.ValidationError("该邮箱已被注册")
return email
class CustomUserRegisterForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ['username', 'email', 'phone_number', 'password1', 'password2']
widgets = {
'username': forms.TextInput(attrs={'class': 'form-control'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
'phone_number': forms.TextInput(attrs={'class': 'form-control'}),
}
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib import messages
from .forms import UserRegisterForm
def register(request):
if request.method == 'POST':
form = UserRegisterForm(request.POST)
if form.is_valid():
# 保存用户
user = form.save()
# 可选:立即登录用户
login(request, user)
# 发送欢迎消息
messages.success(
request,
f'账户创建成功!欢迎 {user.username}'
)
return redirect('home')
else:
form = UserRegisterForm()
return render(request, 'accounts/register.html', {'form': form})
# 基于类的注册视图
from django.views.generic import CreateView
from django.urls import reverse_lazy
class RegisterView(CreateView):
form_class = UserRegisterForm
template_name = 'accounts/register.html'
success_url = reverse_lazy('login')
def form_valid(self, form):
response = super().form_valid(form)
messages.success(
self.request,
'注册成功!请登录您的账户。'
)
return response
Django 提供了细粒度的权限控制系统,可以控制用户对特定资源的访问。
from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.models import ContentType
from myapp.models import Article
# 获取用户权限
user = User.objects.get(username='john')
user_permissions = user.user_permissions.all()
user_has_perm = user.has_perm('myapp.add_article')
# 获取用户组权限
group, created = Group.objects.get_or_create(name='Editors')
user.groups.add(group)
# 添加权限到组
content_type = ContentType.objects.get_for_model(Article)
permission = Permission.objects.get(
codename='change_article',
content_type=content_type
)
group.permissions.add(permission)
# 直接为用户添加权限
user.user_permissions.add(permission)
# 检查权限
if user.has_perm('myapp.change_article'):
print("用户有编辑文章的权限")
# 程序化权限检查
if user.has_perm('myapp.delete_article'):
article.delete()
from django.db import models
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE
)
is_published = models.BooleanField(default=False)
class Meta:
permissions = [
("can_publish", "可以发布文章"),
("can_feature", "可以推荐文章"),
("advanced_edit", "高级编辑权限"),
]
# 对象级权限
def user_can_edit(self, user):
return user == self.author or user.has_perm('myapp.change_article')
def user_can_delete(self, user):
return (user == self.author or
user.has_perm('myapp.delete_article') or
user.is_staff)
| 用户类型 | 查看 | 添加 | 修改 | 删除 |
|---|---|---|---|---|
| 匿名用户 | ✓ | ✗ | ✗ | ✗ |
| 普通用户 | ✓ | ✓ | ✓ (自己的) | ✓ (自己的) |
| 编辑 | ✓ | ✓ | ✓ (所有) | ✗ |
| 管理员 | ✓ | ✓ | ✓ | ✓ |
Django 提供了多种方式控制对视图和模板的访问。
from django.contrib.auth.decorators import (
login_required,
permission_required,
user_passes_test
)
from django.http import HttpResponseForbidden
# 要求登录
@login_required
def profile(request):
return render(request, 'accounts/profile.html')
# 要求特定权限
@permission_required('myapp.change_article', raise_exception=True)
def edit_article(request, article_id):
# 编辑文章逻辑
pass
# 要求多个权限
@permission_required(['myapp.add_article', 'myapp.change_article'])
def manage_articles(request):
pass
# 自定义测试函数
def is_staff_user(user):
return user.is_staff
@user_passes_test(is_staff_user)
def staff_dashboard(request):
return render(request, 'staff/dashboard.html')
# 组合装饰器
@login_required
@permission_required('myapp.can_publish')
def publish_article(request, article_id):
pass
from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin,
UserPassesTestMixin
)
from django.views.generic import ListView, CreateView, UpdateView
from django.urls import reverse_lazy
# 要求登录
class UserProfileView(LoginRequiredMixin, ListView):
model = User
template_name = 'accounts/profile.html'
# 要求权限
class ArticleCreateView(PermissionRequiredMixin, CreateView):
model = Article
fields = ['title', 'content']
template_name = 'articles/create.html'
permission_required = 'myapp.add_article'
success_url = reverse_lazy('article_list')
# 自定义测试
class StaffOnlyView(UserPassesTestMixin, ListView):
model = User
template_name = 'staff/users.html'
def test_func(self):
return self.request.user.is_staff
# 组合混入类
class ArticleUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = Article
fields = ['title', 'content', 'is_published']
template_name = 'articles/update.html'
permission_required = 'myapp.change_article'
success_url = reverse_lazy('article_list')
# 对象级权限检查
def get_queryset(self):
queryset = super().get_queryset()
if not self.request.user.is_staff:
queryset = queryset.filter(author=self.request.user)
return queryset
{% if user.is_authenticated %}
<p>欢迎, {{ user.username }}!</p>
<a href="{% url 'logout' %}">退出登录</a>
{% else %}
<a href="{% url 'login' %}">登录</a>
<a href="{% url 'register' %}">注册</a>
{% endif %}
{# 检查特定权限 #}
{% if perms.myapp.add_article %}
<a href="{% url 'article_create' %}" class="btn btn-primary">
写文章
</a>
{% endif %}
{# 检查多个权限 #}
{% if perms.myapp.change_article or perms.myapp.delete_article %}
<div class="admin-actions">
{% if perms.myapp.change_article %}
<a href="{% url 'article_edit' article.id %}">编辑</a>
{% endif %}
{% if perms.myapp.delete_article %}
<a href="{% url 'article_delete' article.id %}">删除</a>
{% endif %}
</div>
{% endif %}
{# 检查用户组 #}
{% if user.groups.all.0.name == "Editors" %}
<div class="editor-tools">
<!-- 编辑工具 -->
</div>
{% endif %}
| 变量 | 描述 |
|---|---|
user |
当前用户对象 |
user.is_authenticated |
用户是否已认证 |
user.is_anonymous |
用户是否匿名 |
user.is_staff |
用户是否是员工 |
user.is_superuser |
用户是否是超级用户 |
perms |
权限对象 |
Django 使用会话来维护用户的登录状态和其他临时数据。
# 设置会话数据
request.session['user_preference'] = 'dark_mode'
request.session['cart_items'] = [1, 2, 3]
request.session.set_expiry(3600) # 1小时后过期
# 获取会话数据
preference = request.session.get('user_preference', 'light_mode')
cart_items = request.session.get('cart_items', [])
# 检查会话键
if 'user_id' in request.session:
user_id = request.session['user_id']
# 删除会话数据
if 'temp_data' in request.session:
del request.session['temp_data']
# 清空会话
request.session.flush()
# 会话设置
from django.conf import settings
# settings.py 中的会话配置
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 默认
SESSION_COOKIE_AGE = 1209600 # 2周,默认
SESSION_COOKIE_SECURE = True # 仅HTTPS
SESSION_COOKIE_HTTPONLY = True # 防止XSS
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 持久会话
# settings.py
# 数据库后端(默认)
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
# 缓存后端
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
# 文件后端
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
SESSION_FILE_PATH = '/tmp/django_sessions'
# 缓存+数据库后端(持久化)
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
# 自定义会话后端
SESSION_ENGINE = 'myapp.session_backends.CustomSessionBackend'
# 安全配置
SESSION_COOKIE_SECURE = True # 仅HTTPS传输
SESSION_COOKIE_HTTPONLY = True # 防止JavaScript访问
SESSION_COOKIE_SAMESITE = 'Lax' # CSRF保护
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
# settings.py
INSTALLED_APPS = [
# ...
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'allauth.socialaccount.providers.github',
# ...
]
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
]
SITE_ID = 1
# allauth 配置
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_AUTHENTICATION_METHOD = 'username_email'
LOGIN_REDIRECT_URL = '/'
# 社交账号配置
SOCIALACCOUNT_PROVIDERS = {
'google': {
'SCOPE': ['profile', 'email'],
'AUTH_PARAMS': {'access_type': 'online'},
},
'github': {
'SCOPE': ['user:email'],
}
}
from django.urls import path, include
urlpatterns = [
path('accounts/', include('allauth.urls')),
# ...
]
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
}
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
class ProtectedView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
return Response({"message": "这是受保护的数据"})
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth import get_user_model
class EmailBackend(BaseBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
# 尝试通过邮箱查找用户
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
try:
# 回退到用户名
user = UserModel.objects.get(username=username)
except UserModel.DoesNotExist:
return None
# 验证密码
if user.check_password(password):
return user
return None
def get_user(self, user_id):
UserModel = get_user_model()
try:
return UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
AUTHENTICATION_BACKENDS = [
'myapp.backends.EmailBackend', # 自定义后端
'django.contrib.auth.backends.ModelBackend', # 默认后端
]
# settings.py
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
]
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'validate_password_length'},
{'NAME': 'validate_password_complexity'},
]
# 安全会话配置
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_AGE = 3600 # 1小时
# 登录审计
from django.contrib.auth.signals import (
user_logged_in,
user_login_failed
)
def log_login(sender, request, user, **kwargs):
log_security_event(
f"用户 {user} 登录成功"
)