Django用户认证权限

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 用户认证

authenticatelogin函数处理

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 的所有权限
相关推荐
思辨共悟3 小时前
python数据分析 与spark、hive数据分析对比
python·数据分析·spark
aiden:)3 小时前
Selenium WebUI 自动化“避坑”指南——从常用 API 到 10 大高频问题
开发语言·前端·javascript·python·selenium
Lenskit3 小时前
使用pyspark对上百亿行的hive表生成稀疏向量
python·spark-ml·spark
站大爷IP3 小时前
Python数据分析实战:Pandas处理结构化数据的核心技巧
python
站大爷IP4 小时前
Python脚本转EXE文件实战指南:从原理到操作全解析
python
码界筑梦坊4 小时前
278-基于Django的协同过滤旅游推荐系统
python·数据分析·django·毕业设计·旅游
BYSJMG4 小时前
计算机大数据毕业设计选题:基于Spark+hadoop的全球香水市场趋势分析系统
大数据·vue.js·hadoop·python·spark·django·课程设计
小白学大数据4 小时前
Scrapy框架实战:大规模爬取华为应用市场应用详情数据
开发语言·爬虫·python·scrapy·华为
大翻哥哥5 小时前
Python 2025:AI代理、Rust与异步编程的新时代
开发语言·人工智能·python