Django 提供了一个强大且灵活的认证和权限系统,可以轻松处理用户认证、授权和权限管理。
1、认证系统架构
启用配置
python
# settings.py
INSTALLED_APPS = [
# ...
'django.contrib.auth', # 核心认证框架
'django.contrib.contenttypes', # 权限系统依赖
# ...
]
MIDDLEWARE = [
# ...
'django.contrib.auth.middleware.AuthenticationMiddleware', # 关联用户与请求
# ...
]
1.1 User 模型
存储用户信息的核心模型
1.1.1 模型源码
python
class PermissionsMixin(models.Model):
# 是否为超级用户
is_superuser = models.BooleanField(
_("superuser status"),
default=False,
help_text=_(
"Designates that this user has all permissions without "
"explicitly assigning them."
),
)
groups = models.ManyToManyField(
Group,
verbose_name=_("groups"),
blank=True,
help_text=_(
"The groups this user belongs to. A user will get all permissions "
"granted to each of their groups."
),
related_name="user_set",
related_query_name="user",
)
user_permissions = models.ManyToManyField(
Permission,
verbose_name=_("user permissions"),
blank=True,
help_text=_("Specific permissions for this user."),
related_name="user_set",
related_query_name="user",
)
class Meta:
abstract = True
class AbstractBaseUser(models.Model):
# 哈希后的密码
password = models.CharField(_("password"), max_length=128)
last_login = models.DateTimeField(_("last login"), blank=True, null=True)
is_active = True
REQUIRED_FIELDS = []
_password = None
class Meta:
abstract = True
def __str__(self):
return self.get_username()
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
抽象用户模型,提供了完整的用户功能
"""
username_validator = UnicodeUsernameValidator()
# 用户名(唯一标识)
username = models.CharField(
_("username"),
max_length=150,
unique=True,
help_text=_(
"Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
),
validators=[username_validator],
error_messages={
"unique": _("A user with that username already exists."),
},
)
first_name = models.CharField(_("first name"), max_length=150, blank=True)
last_name = models.CharField(_("last name"), max_length=150, blank=True)
email = models.EmailField(_("email address"), blank=True)
# 是否可以登录管理后台
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
# 账号是否激活
is_active = models.BooleanField(
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
)
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
objects = UserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"]
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
class User(AbstractUser):
# 默认用户模型,继承自 AbstractUser
class Meta(AbstractUser.Meta):
swappable = "AUTH_USER_MODEL"
1.1.2 自定义用户模型
最佳实践是项目开始时创建自定义用户模型
python
# models.py
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)
# 使用邮箱作为用户名
REQUIRED_FIELDS = ['username'] # 创建超级用户时需要的字段
在 settings.py 中指定:
python
AUTH_USER_MODEL = 'myapp.CustomUser'
1.2 权限模型
1.2.1 模型源码
python
class Permission(models.Model):
"""
权限模型,存储所有权限信息
"""
name = models.CharField(_("name"), max_length=255)
content_type = models.ForeignKey(
ContentType,
models.CASCADE,
verbose_name=_("content type"),
)
codename = models.CharField(_("codename"), max_length=100)
objects = PermissionManager()
class Meta:
verbose_name = _("permission")
verbose_name_plural = _("permissions")
unique_together = [["content_type", "codename"]]
ordering = ["content_type__app_label", "content_type__model", "codename"]
def __str__(self):
return "%s | %s" % (self.content_type, self.name)
def natural_key(self):
return (self.codename,) + self.content_type.natural_key()
natural_key.dependencies = ["contenttypes.contenttype"]
Django 自动为每个模型创建四个基本权限,在模型迁移时自动创建
- add_ - 添加权限
- change_ - 修改权限
- delete_ - 删除权限
- view_ - 查看权限
1.2.2 自定义权限
可以在模型的 Meta 类中定义自定义权限:
python
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
class Meta:
permissions = [
("can_publish", "Can publish book"),
("can_edit_cover", "Can edit book cover"),
]
1.2.2 权限检查
python
# django\contrib\auth\models.py
def _user_has_perm(user, perm, obj):
"""
检查用户是否有特定权限
"""
for backend in auth.get_backends():
if not hasattr(backend, "has_perm"):
continue
try:
if backend.has_perm(user, perm, obj):
return True
except PermissionDenied:
return False
return False
1.3 组
用户组,用于批量权限分配
1.3.1 模型源码
python
class Group(models.Model):
"""
组模型,用于批量分配权限
"""
name = models.CharField(_("name"), max_length=150, unique=True)
permissions = models.ManyToManyField(
Permission,
verbose_name=_("permissions"),
blank=True,
)
objects = GroupManager()
class Meta:
verbose_name = _("group")
verbose_name_plural = _("groups")
def __str__(self):
return self.name
def natural_key(self):
return (self.name,)
1.4 认证后端
Django 认证系统支持多个认证后端,按顺序尝试认证,authenticate() 函数会遍历 AUTHENTICATION_BACKENDS设置中指定的所有后端。默认使用的是 ModelBackend,其核心逻辑是根据 username查询用户,并验证其密码和 is_active状态
python
# settings.py
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend', # 默认后端
'myapp.backends.EmailBackend', # 自定义后端
]
1.4.1 ModelBackend源码
python
class ModelBackend(BaseBackend):
"""
基于模型的认证后端
"""
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
if username is None or password is None:
return
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
# 模拟密码哈希以防止时序攻击
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
def user_can_authenticate(self, user):
"""
检查用户是否可以认证(是否激活)
"""
return getattr(user, "is_active", True)
def _get_user_permissions(self, user_obj):
# 获取用户权限的实现
return user_obj.user_permissions.all()
def _get_group_permissions(self, user_obj):
return Permission.objects.filter(group__in=user_obj.groups.all())
def _get_permissions(self, user_obj, obj, from_name):
# 获取权限
if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
return set()
perm_cache_name = "_%s_perm_cache" % from_name
if not hasattr(user_obj, perm_cache_name):
if user_obj.is_superuser:
perms = Permission.objects.all()
else:
perms = getattr(self, "_get_%s_permissions" % from_name)(user_obj)
perms = perms.values_list("content_type__app_label", "codename").order_by()
setattr(
user_obj, perm_cache_name, {"%s.%s" % (ct, name) for ct, name in perms}
)
return getattr(user_obj, perm_cache_name)
def get_user_permissions(self, user_obj, obj=None):
# 获取用户权限
return self._get_permissions(user_obj, obj, "user")
def get_group_permissions(self, user_obj, obj=None):
# 获取组权限
return self._get_permissions(user_obj, obj, "group")
def get_all_permissions(self, user_obj, obj=None):
# 获取所有权限
if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
return set()
if not hasattr(user_obj, "_perm_cache"):
user_obj._perm_cache = super().get_all_permissions(user_obj)
return user_obj._perm_cache
def has_perm(self, user_obj, perm, obj=None):
# 检查用户是否有特定权限
return user_obj.is_active and super().has_perm(user_obj, perm, obj=obj)
def get_user(self, user_id):
try:
user = UserModel._default_manager.get(pk=user_id)
except UserModel.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
1.4.2 自定义认证后端
python
# backends.py
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth import get_user_model
from django.db.models import Q
class EmailBackend(BaseBackend):
"""
使用邮箱认证的后端
"""
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
# 使用邮箱或用户名认证
user = UserModel.objects.get(
Q(username__iexact=username) | Q(email__iexact=username)
)
except UserModel.DoesNotExist:
# 运行默认密码哈希器以防计时攻击
UserModel().set_password(password)
return None
except UserModel.MultipleObjectsReturned:
# 如果找到多个用户,返回第一个
user = UserModel.objects.filter(
Q(username__iexact=username) | Q(email__iexact=username)
).first()
if user and 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
1.4.3 authenticate和login方法
python
@sensitive_variables("credentials")
def authenticate(request=None, **credentials):
# 遍历AUTHENTICATION_BACKENDS设置中指定的所有后端
for backend, backend_path in _get_compatible_backends(request, **credentials):
try:
# 调用认证后端的authenticate核心方法
user = backend.authenticate(request, **credentials)
except PermissionDenied:
break
if user is None:
continue
user.backend = backend_path
return user
# 发送认证失败信号
user_login_failed.send(
sender=__name__, credentials=_clean_credentials(credentials), request=request
)
def login(request, user, backend=None):
# 主要是session的处理,保存是在session中间件完成
# ...
2、基本使用
2.1 用户认证
由 authenticate 和 login函数处理
python
from django.contrib.auth import authenticate, login
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
def login_view(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)
return redirect('home')
else:
return render(request, 'login.html', {'error': 'Invalid credentials'})
return render(request, 'login.html')
def register_view(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
# 创建完成后登录(建立会话)
login(request, user)
return redirect('home')
else:
form = UserCreationForm()
return render(request, 'register.html', {'form': form})
2.2 权限检查
使用 user.has_perm、装饰器或模板变量 perms
2.2.1 视图中的权限检查
python
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import View
from django.contrib.auth.decorators import login_required, permission_required
@login_required # 要求用户登录
def my_view(request):
pass
@permission_required('polls.can_vote', raise_exception=True) # 要求特定权限
def my_vote_view(request):
pass
# 使用Mixin(类视图)
class PublishView(LoginRequiredMixin, PermissionRequiredMixin, View):
permission_required = 'myapp.can_publish'
login_url = '/login/'
def get(self, request):
return HttpResponse("Book published")
2.2.2 模板中的权限检查
html
{% if perms.myapp.can_publish %}
<button>Publish Book</button>
{% endif %}
{% if user.has_perm('myapp.can_edit_cover') %}
<button>Edit Cover</button>
{% endif %}
2.3 用户和权限管理
2.3.1 创建用户并分配权限
python
from django.contrib.auth.models import User, Permission
from django.contrib.contenttypes.models import ContentType
from myapp.models import Book
# 创建用户
user = User.objects.create_user('john', 'john@example.com', 'password')
# 获取权限
content_type = ContentType.objects.get_for_model(Book)
permission = Permission.objects.get(
codename='can_publish',
content_type=content_type,
)
# 分配权限给用户
user.user_permissions.add(permission)
# 检查权限
if user.has_perm('myapp.can_publish'):
print("User can publish books")
2.3.2 使用组管理权限
python
from django.contrib.auth.models import Group, Permission, User
# 1. 创建组并分配权限
editors_group, created = Group.objects.get_or_create(name='Editors')
change_article_perm = Permission.objects.get(codename='change_article')
editors_group.permissions.add(change_article_perm)
# 2. 将用户加入组
user = User.objects.get(username='alice')
user.groups.add(editors_group)
# 现在 user 拥有 editors_group 的所有权限