Django 提供了一个强大且灵活的认证和权限系统,可以轻松处理用户认证、授权和权限管理。
1、认证系统架构
启用配置
# settings.py INSTALLED_APPS = [ # ... 'django.contrib.auth', # 核心认证框架 'django.contrib.contenttypes', # 权限系统依赖 # ... ] MIDDLEWARE = [ # ... 'django.contrib.auth.middleware.AuthenticationMiddleware', # 关联用户与请求 # ... ]
1.1 User 模型
存储用户信息的核心模型
1.1.1 模型源码
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 自定义用户模型
最佳实践是项目开始时创建自定义用户模型
# 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 中指定:
AUTH_USER_MODEL = 'myapp.CustomUser'
1.2 权限模型
1.2.1 模型源码
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 类中定义自定义权限:
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 权限检查
# 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 模型源码
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状态 # settings.py AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', # 默认后端 'myapp.backends.EmailBackend', # 自定义后端 ]
1.4.1 ModelBackend源码
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 自定义认证后端
# 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方法
@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
函数处理 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 视图中的权限检查
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 模板中的权限检查
{% 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 创建用户并分配权限
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 使用组管理权限
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 的所有权限
这一切,似未曾拥有