Django从零开始(六):用户认证与权限系统
一、Django认证系统概述
Django自带了一套完善的用户认证系统(Authentication System),包含用户注册、登录、登出、权限管理、分组等功能。它位于 django.contrib.auth 模块中。
认证系统核心组件
- User模型:用户模型,包含用户名、密码、邮箱等字段
- Authentication:验证用户身份(用户名+密码)
- Authorization:权限和分组管理
- Session:基于Session的状态管理
- 视图和表单:内置的登录/登出/密码重置视图
二、User模型
2.1 默认User模型字段
# django.contrib.auth.models.User 的默认字段
username # 用户名(必填,唯一)
password # 密码(自动哈希存储)
email # 邮箱(可选)
first_name # 名(可选,max_length=150)
last_name # 姓(可选,max_length=150)
is_active # 是否激活(默认True)
is_staff # 是否可登录Admin后台(默认False)
is_superuser # 是否超级管理员(默认False)
date_joined # 注册时间
last_login # 最后登录时间
2.2 用户基本操作
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout
# ===== 创建用户 =====
# 方法1:create_user(自动处理密码哈希)
user = User.objects.create_user(
username='zhangsan',
email='zhangsan@example.com',
password='secure_password123', # 明文密码,会自动哈希
first_name='三',
last_name='张',
)
# 方法2:先创建再设置密码
user = User(username='lisi', email='lisi@example.com')
user.set_password('secure_password123') # 手动哈希密码
user.save()
# ===== 修改用户信息 =====
user.email = 'new_email@example.com'
user.save()
# 修改密码
user.set_password('new_password456')
user.save()
# 检查密码是否正确
if user.check_password('new_password456'):
print('密码正确')
# ===== 删除用户 =====
user.delete()
2.3 自定义User模型(推荐做法)
Django官方强烈建议在项目开始时就自定义User模型,即使它与默认模型完全相同,这样未来需要扩展时会非常方便:
# users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
"""自定义用户模型"""
avatar = models.ImageField('头像', upload_to='avatars/', null=True, blank=True)
phone = models.CharField('手机号', max_length=11, blank=True, default='')
bio = models.TextField('个人简介', blank=True, default='')
birthday = models.DateField('生日', null=True, blank=True)
website = models.URLField('个人网站', blank=True, default='')
github = models.CharField('GitHub', max_length=100, blank=True, default='')
class Meta:
verbose_name = '用户'
verbose_name_plural = '用户'
def __str__(self):
return self.username
def get_full_name_cn(self):
"""中文名"""
return f'{self.last_name}{self.first_name}' or self.username
# settings.py
AUTH_USER_MODEL = 'users.User' # 指定自定义用户模型
三、注册功能实现
3.1 注册表单
# users/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import User
class RegisterForm(UserCreationForm):
"""用户注册表单"""
email = forms.EmailField(
label='邮箱',
required=True,
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': '请输入邮箱',
})
)
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
widgets = {
'username': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '请输入用户名',
}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['password1'].widget.attrs.update({
'class': 'form-control',
'placeholder': '请输入密码',
})
self.fields['password2'].widget.attrs.update({
'class': 'form-control',
'placeholder': '请确认密码',
})
def clean_email(self):
email = self.cleaned_data.get('email')
if User.objects.filter(email=email).exists():
raise forms.ValidationError('该邮箱已被注册')
return email
def save(self, commit=True):
user = super().save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
3.2 注册视图
# users/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login
from .forms import RegisterForm
def register(request):
"""用户注册"""
if request.user.is_authenticated:
return redirect('blog:article_list')
if request.method == 'POST':
form = RegisterForm(request.POST)
if form.is_valid():
user = form.save()
# 注册成功后自动登录
login(request, user)
return redirect('blog:article_list')
else:
form = RegisterForm()
return render(request, 'users/register.html', {'form': form})
四、登录与登出
4.1 手动实现登录
# users/views.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import AuthenticationForm
def login_view(request):
"""用户登录"""
if request.user.is_authenticated:
return redirect('blog:article_list')
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
# 记住我功能
if not form.cleaned_data.get('remember_me'):
request.session.set_expiry(0) # 浏览器关闭后过期
else:
request.session.set_expiry(60 * 60 * 24 * 30) # 30天
next_url = request.GET.get('next', '/')
return redirect(next_url)
else:
form = AuthenticationForm()
return render(request, 'users/login.html', {'form': form})
def logout_view(request):
"""用户登出"""
logout(request)
return redirect('blog:article_list')
4.2 使用Django内置认证视图
# urls.py
from django.contrib.auth import views as auth_views
urlpatterns = [
# 登录
path('login/', auth_views.LoginView.as_view(
template_name='users/login.html',
redirect_authenticated_user=True,
), name='login'),
# 登出
path('logout/', auth_views.LogoutView.as_view(
next_page='/',
), name='logout'),
# 密码修改
path('password-change/', auth_views.PasswordChangeView.as_view(
template_name='users/password_change.html',
success_url='/password-change/done/',
), name='password_change'),
path('password-change/done/', auth_views.PasswordChangeDoneView.as_view(
template_name='users/password_change_done.html',
), name='password_change_done'),
# 密码重置(忘记密码)
path('password-reset/', auth_views.PasswordResetView.as_view(
template_name='users/password_reset.html',
email_template_name='users/password_reset_email.html',
subject_template_name='users/password_reset_subject.txt',
success_url='/password-reset/done/',
), name='password_reset'),
path('password-reset/done/', auth_views.PasswordResetDoneView.as_view(
template_name='users/password_reset_done.html',
), name='password_reset_done'),
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(
template_name='users/password_reset_confirm.html',
success_url='/reset/done/',
), name='password_reset_confirm'),
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(
template_name='users/password_reset_complete.html',
), name='password_reset_complete'),
]
五、登录保护(访问控制)
5.1 装饰器方式
from django.contrib.auth.decorators import login_required, permission_required
# 要求登录才能访问
@login_required(login_url='/accounts/login/')
def dashboard(request):
return render(request, 'dashboard.html')
# 要求特定权限
@permission_required('blog.can_publish', login_url='/accounts/login/')
def publish_article(request):
pass
# 多个权限(满足任一)
@permission_required(['blog.change_article', 'blog.delete_article'])
def moderate_article(request):
pass
5.2 类视图Mixin
from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin,
UserPassesTestMixin,
)
class DashboardView(LoginRequiredMixin, TemplateView):
"""需要登录的仪表盘"""
template_name = 'dashboard.html'
login_url = '/accounts/login/'
class PublishView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""需要登录+发布权限"""
model = Article
fields = ['title', 'content']
permission_required = 'blog.can_publish'
raise_exception = True # 403而不是重定向到登录
class ArticleUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
"""只有作者本人可以编辑"""
model = Article
fields = ['title', 'content']
def test_func(self):
article = self.get_object()
return self.request.user == article.author or self.request.user.is_superuser
六、权限系统
6.1 权限类型
# Django为每个模型自动创建4种权限
# add: 添加权限 blog.add_article
# change: 修改权限 blog.change_article
# delete: 删除权限 blog.delete_article
# view: 查看权限 blog.view_article
# 检查权限
user.has_perm('blog.add_article')
user.has_perm('blog.change_article')
user.has_perms(['blog.add_article', 'blog.change_article'])
# 在模板中检查权限
# {% if perms.blog.add_article %}
# <a href="{% url 'blog:article_create' %}">写文章</a>
# {% endif %}
6.2 自定义权限
class Article(models.Model):
# ...
class Meta:
permissions = [
('can_publish', '可以发布文章'),
('can_edit_all', '可以编辑所有文章'),
('can_moderate', '可以审核评论'),
]
6.3 分组管理
from django.contrib.auth.models import Group, Permission
# 创建分组
editors, _ = Group.objects.get_or_create(name='编辑')
reviewers, _ = Group.objects.get_or_create(name='审核员')
# 给分组分配权限
publish_perm = Permission.objects.get(codename='can_publish')
edit_perm = Permission.objects.get(codename='can_edit_all')
moderate_perm = Permission.objects.get(codename='can_moderate')
editors.permissions.add(publish_perm, edit_perm)
reviewers.permissions.add(moderate_perm)
# 将用户加入分组
user.groups.add(editors)
# 检查分组
user.groups.filter(name='编辑').exists()
user.has_perm('blog.can_publish') # 通过分组继承的权限也返回True
七、用户资料管理
# users/models.py
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
"""用户资料(一对一关联)"""
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
avatar = models.ImageField('头像', upload_to='avatars/', default='avatars/default.png')
bio = models.TextField('个人简介', blank=True)
phone = models.CharField('手机号', max_length=11, blank=True)
birthday = models.DateField('生日', null=True, blank=True)
github = models.URLField('GitHub', blank=True)
def __str__(self):
return f'{self.user.username}的资料'
# 信号:用户创建时自动创建Profile
@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save()
# users/forms.py
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['avatar', 'bio', 'phone', 'birthday', 'github']
widgets = {
'bio': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
'phone': forms.TextInput(attrs={'class': 'form-control'}),
'birthday': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'github': forms.URLInput(attrs={'class': 'form-control'}),
}
# users/views.py
@login_required
def profile_edit(request):
if request.method == 'POST':
form = ProfileForm(request.POST, request.FILES, instance=request.user.profile)
if form.is_valid():
form.save()
messages.success(request, '资料更新成功!')
return redirect('profile')
else:
form = ProfileForm(instance=request.user.profile)
return render(request, 'users/profile_edit.html', {'form': form})
八、社交登录(django-allauth)
# 安装
pip install django-allauth
# settings.py
INSTALLED_APPS = [
...
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.github', # GitHub登录
'allauth.socialaccount.providers.google', # Google登录
'allauth.socialaccount.providers.weixin', # 微信登录
]
SITE_ID = 1
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
]
# allauth 配置
ACCOUNT_EMAIL_VERIFICATION = 'none' # 邮箱验证策略
ACCOUNT_AUTHENTICATION_METHOD = 'username_email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_LOGOUT_ON_GET = True
LOGIN_REDIRECT_URL = '/'
# urls.py
urlpatterns += [path('accounts/', include('allauth.urls'))]
九、总结
本章我们学习了Django认证系统的核心功能:
- User模型和自定义用户模型
- 用户注册、登录、登出功能实现
- Django内置认证视图的使用
- 登录保护和权限控制
- 权限和分组管理
- 用户资料管理和社交登录
下一章我们将学习 Django REST Framework,开始构建RESTful API接口。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。







