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 的所有权限
复制代码 来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除 |