移国拱 发表于 5 天前

Django用户认证权限

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=,
      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 False1.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 None1.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 None1.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 的所有权限
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Django用户认证权限