Django系列(八)之用户和权限系统

一、概述
Django 自带一个用户验证系统。它负责处理用户账号、组、权限和基于cookie的用户会话,它存在于 django.contrib.auth包中。 认证系统由以下部分组成:
  • 用户

  • 权限:指定用户是否可以执行特定任务。

  • 组:将标签和权限应用于多个用户的一般方法。

  • 可配置的密码哈希化系统

  • 为登录用户或限制内容提供表单和视图工具

  • 可插拔的后端系统

Django 里的验证系统旨在通用化,不提供一些常见的 web 验证系统的特性。其中一些常见问题的解决方案已在第三方包中实现,例如:

  • 密码强度检查
  • 限制登录尝试
  • 针对第三方的身份验证(例如OAuth)
  • 对象级权限

django自带的用户和权限系统,使得你不用自己再去设计一套表和代码去实现登录,退出,权限控制等,完全使用django自己的就可以,django已经帮你写好了,你直接用就行。

二、验证系统在默认配置下的使用方法
默认配置满足最常见的项目需求,可以处理相当多的任务,还有一个安全的密码和权限实现。对于验证需求和django自带的默认配置不同的项目,Django 支持对身份验证进行扩展和定制。
2.1、用户(User对象)

user对象源码

点击查看代码

class AbstractUser(AbstractBaseUser, PermissionsMixin):
    """
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.

    Username and password are required. Other fields are optional.
    """

    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)

    def get_full_name(self):
        """
        Return the first_name plus the last_name, with a space in between.
        """
        full_name = "%s %s" % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        """Return the short name for the user."""
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user."""
        send_mail(subject, message, from_email, [self.email], **kwargs)


class User(AbstractUser):
    """
    Users within the Django authentication system are represented by this
    model.

    Username and password are required. Other fields are optional.
    """

    class Meta(AbstractUser.Meta):
        swappable = "AUTH_USER_MODEL"

使用方式

点击查看代码
# 默认用户的主要属性是:
username
password
email
first_name
last_name

# 1、创建用户
创建用户的最直接方法是使用包含的 create_user() 辅助方法:
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user("john", "lennon@thebeatles.com", "johnpassword")

# 2、创建超级用户
使用 createsuperuser 命令创建超级用户:
python manage.py createsuperuser --username=joe --email=joe@example.com
你将被要求输入密码。输入密码后,用户将立即创建。如果你不使用 --username 或 --email 选项,它将提示你输入这些值。

使用create_superuser()辅助方法:
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_superuser("john", "lennon@thebeatles.com", "johnpassword")

# 3、更改密码
Django 不会在用户模型上存储明文密码,而只会存储散列值。因此,不要直接操作用户的密码属性。这就是在创建用户时使用辅助函数的原因。

更改一个用户的密码,你有几个选择:
manage.py changepassword username  提供了一种从命令行更改用户密码的方法。它会提示您更改指定用户的密码,你必须输入两次密码。如果它们都匹配,新密码将立即更改。如果你不提供用户名,该命令将尝试更改与当前系统用户匹配的用户名的密码。

你也可以通过编程方式更改密码,使用 set_password():
>>> from django.contrib.auth.models import User
>>> u = User.objects.get(username="john")
>>> u.set_password("new password")
>>> u.save()

# 注意:
这里一定不能用upate()方法直接更新密码,只能用set_password()方法,因为当你创建一个用户或修改密码时,Django 会使用一种哈希算法对密码进行加密处理,然后只存储加密后的哈希值,如果直接update,那就会存储明文密码,导致登录的时候验证失败

# 4、验证用户
authenticate(request=None, **credentials)

使用 authenticate() 来验证用户是否存在。它以关键字参数的形式接受用户参数,在使用django自带的认证后端(ModelBackend)情况下,参数是 username 和 password(request是可选参数),
然后检查它们与每个认证后端(如果你没有自定义认证后端,那就只有一个)进行匹配,如果凭据对于某个后端是有效的,则停止查找,返回一个 User 对象。
如果凭据对于任何后端都无效,或者如果后端引发 PermissionDenied,则返回 None。例如:

from django.contrib.auth import authenticate
user = authenticate(username="john", password="secret")
if user is not None:
    # A backend authenticated the credentials
    ...
else:
    # No backend authenticated the credentials
    ...

# 注意:
authenticate 不等于 login:
authenticate 只是验证凭据,它不会创建用户会话,也不会让用户登录。它的任务在返回 User 对象(或 None)时就完成了。login 才是真正执行登录操作的函数。

# 5、登录
如果你有一个已经经过认证的用户,想要将其附加到当前会话中,可以使用 login() 函数来实现。

login(request, user, backend=None)
要从视图中登录用户,请使用 login()。它接受一个 HttpRequest 对象和一个 User 对象。login() 会使用 Django 的会话框架将用户的ID保存在会话中。

请注意,在用户登录后,匿名会话期间设置的任何数据都将保留在会话中。

登录示例:
from django.contrib.auth import authenticate, login
def my_view(request):
    username = request.POST["username"]
    password = request.POST["password"]
    user = authenticate(request, username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

注意:这个login方法实际是帮助你自行保存了会话信息,这个会话信息默认是保存在数据库中的(保存在哪可以通过配置文件中
SESSION_ENGINE = 'django.contrib.sessions.backends.db' 这个配置项来配置,默认是db,会在数据库中创建一张django_session 表),你也可以设置保存在redis等缓存里
例如:
# session配置,存储在缓存中
SESSION_ENGINE = "django.contrib.sessions.backends.cache" 表示使用缓存

# session 使用的缓存别名,对应下面CACHES里面配置的各种缓存,选哪个就会用到哪个
SESSION_CACHE_ALIAS = "session-redis"

# 缓存配置
CACHES = {
    "session-redis": {
        "BACKEND": 'django_redis.cache.RedisCache',      # 表示使用redis来当会话缓存
        "LOCATION": 'redis://8.153.110.179:2379/1',  # Redis 服务器地址(1 是数据库编号)
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": '111111',  # 若 Redis 有密码,添加此行
            # "PARSER_CLASS": "redis.connection._HiredisParser",  # 可选,提升性能
        },
        # "TIMEOUT": 3600,  # 缓存过期时间(1 小时)
    }
}

# 6、退出登录
logout(request)
要退出通过 django.contrib.auth.login() 登录的用户,请在你的视图中使用 django.contrib.auth.logout()。它接受一个 HttpRequest 对象,没有返回值。示例:

from django.contrib.auth import logout
def logout_view(request):
    logout(request)
    # Redirect to a success page.
请注意,如果用户未登录,logout() 不会引发任何错误。

当你调用 logout() 时,当前请求的会话数据将被完全清除。所有现有的数据都会被移除。
2.2、权限
Django 自带了一个内置的权限系统。它提供了一种将权限分配给特定用户和用户组的方式。
点击查看代码
# 1、默认权限
django自带的权限系统会为你已安装的每个应用程序中定义的 Django 模型创建四个默认权限:添加、更改、删除和查看。这些权限将在运行 manage.py migrate 时创建并保存到数据库表中(auth_permission)

比如你创建了个名为goods的app,并在app的models.py中创建了一个名为inventory的模型,你在执行了python3 manage.py makemigrations和 python3 manage.py migrate 后,django的权限系统会自动为你创建这个模型的添加、更改、删除和查看权限,并把权限保存到auth_permission表中。

# 2、用户权限和组权限
可以为单个用户授权某些权限,也可以为某个组授权某些权限,然后把用户加入组中,用户就会获得该组的所有权限,用户具有的全部权限=用户自身权限+用户所在组的权限

# 3、自定义创建权限
django会默认为你自己创建的和django自带的所有已经在配置文件中通过INSTALLED_APPS配置好的app中的模型创建默认的四个权限,
但是如果你有别的权限需求,比如你有一个article的模型,django自动创建了增删改查四个模型权限,但是你还想要个审核文章的权限,此时你就需要自己创建这个权限。

方法一,以编程方式创建权限:
from myapp.models import BlogPost
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType

content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.create(
    codename="can_publish",
    name="Can Publish Posts",
    content_type=content_type,
)

方法二,通过模型的Meta类来定义:
from django.db import models
class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

    class Meta:
        permissions = [
            ("publish_article", "Can publish article"),
            ("review_article", "Can review article"),
        ]

每个权限是一个 二元元组:(permission_code, human-readable_description)
permission_code:权限的唯一标识符(字符串),以后代码判断权限啥的就用它;
description:给人看的描述。

之后生成并应用迁移,此时你自己创建的权限也就会保存到数据库auth_permission表中了,就可以供后续使用了
python manage.py makemigrations  # 可选,通常不需要,因为 Meta 不影响字段
python manage.py migrate


# 4、怎么为用户授权呢?
方法一:通过 Admin 管理界面(推荐,简单直观)
这是最常用和最推荐的方式,适合日常管理。django自带了一个admin(后台管理系统),授权,创建用户,创建组等操作都可以在此系统中完成。
1. 为单个用户分配权限
登录到你的 Django Admin 后台(通常是 /admin/)。
在 "认证和授权"(Authentication and Authorization)部分,点击 "Users"。
找到你想要编辑的用户,点击他的用户名进入编辑页面。
向下滚动页面,你会看到两个重要的部分:
用户权限 (User permissions):
这里列出了系统中所有可用的权限。
你可以直接勾选你想要赋予给这个用户的权限。
这些权限是叠加在他所属组的权限之上的。
所属组 (Groups):
这里列出了系统中所有的用户组。
你可以将用户添加到一个或多个组中。
用户会自动继承他所在组的所有权限。
选择好权限和 / 或组之后,点击页面底部的 "SAVE" 按钮保存。

方法二:通过代码分配权限
这种方式适用于在程序运行时,根据某些业务逻辑动态地为用户分配权限。
自行研究,这里不做展开,不光是分配权限,创建权限也可以通过代码方式。
admin后台管理系统能做的事通过代码都能做。


# 5、限制用户访问的方式
权限创建好了,用户也有权限了,还有个问题,就是怎么控制某个页面是否需要具有权限或者必须登录以后的用户才能访问,还是所有用户(未登录或者不具有权限)都能访问呢,这就需要限制用户访问了

1、原始方式
限制访问页面的原始方法是检查用户是否已登录(request.user.is_authenticated) ,让登录的用户才能访问该页面
from django.conf import settings
from django.shortcuts import redirect
def my_view(request):
    if not request.user.is_authenticated:
        return redirect(f"{settings.LOGIN_URL}?next={request.path}")
    # ...

2、login_required 装饰器
该方式要求必须是登录的用户才能访问该页面
login_required(redirect_field_name='next', login_url=None)
作为一种快捷方式,你可以使用 login_required() 装饰器来修饰视图函数:
from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    ...
参数解释:
login_url:
这是最常用的参数,可选。
作用:当用户未登录时,login_required 会将其重定向到你指定的这个 URL(登录页面的 URL)。
为什么需要它?
Django 有一个默认的登录 URL,即 /accounts/login/。如果你的登录页面不在这个地址上,你就需要用 login_url 参数来明确告诉 Django 登录页面在哪里。
假设你的登录页面 URL 是 /login/,不是djangom默认的/accounts/login/,那就要像下面这样指定login_url
# views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render

# 使用 login_url 参数指定登录页面的 URL
@login_required(login_url='/login/')
def profile(request):
    return render(request, 'profile.html')

为了避免在每个 @login_required 装饰器中都写一遍 login_url,你可以在项目的 settings.py 文件中全局配置 LOGIN_URL。
# 配置全局的登录 URL
LOGIN_URL = '/login/'
这样,你就可以直接使用 @login_required 而无需指定 login_url 参数,它会自动使用你在 settings.py 中配置的值。

redirect_field_name:
作用:当 login_required 将用户重定向到登录页面时,它会在登录 URL 的末尾附加一个查询参数,用来告诉登录页面:“用户最初想访问的是哪个页面”。
这个查询参数的名称就是由 redirect_field_name 决定的。当用户在登录页面成功登录后,网站可以读取这个 next 参数的值,然后自动将用户重定向回他最初想要访问
的/profile/ 页面,提升用户体验,这个参数基本不用动,保持默认即可

3、LoginRequiredMixin
login_required是用来修饰视图函数的,当使用基于类的视图时,你可以通过使用 LoginRequiredMixin 来实现与 login_required 相同的行为(限制必须登录的用户才能访问页面)。这个混合类必须在继承列表的最左边位置。
例如:
class MyView(LoginRequiredMixin, View):
    login_url = "/login/"  # 如果配置文件里设置了LOGIN_URL,这里也可以不指定
    redirect_field_name = "redirect_to"。# 和login_require一样,这个参数可以不指定,使用默认值即可

4、permission_required 装饰器
上面的几个限制方式都是限制登录的用户才能访问页面,但是没有限制用户是否拥有权限。该方式限制要求必须拥有权限的用户才能操作该页面上的一些增删改查动作
permission_required(perm, login_url=None, raise_exception=False)
例如:
from django.contrib.auth.decorators import permission_required
@permission_required("polls.add_choice")
def my_view(request):
    ...

参数解释:
perm:权限,例如polls.add_choice,polls是应用名称(即app名),add_choice是你创建的权限,会存储在auth_permission表里,如果不记得了,去查就行(codename字段)
login_url:如果权限不足,就会重定向到登录页面,这个和login_require里一样

5、PermissionRequiredMixin
也是一样,permission_required装饰器是用来修饰视图函数的,要想在视图类里限制权限,你可以使用 PermissionRequiredMixin,它也需要在最左边的位置
from django.contrib.auth.mixins import PermissionRequiredMixin
class MyView(PermissionRequiredMixin, View):
    permission_required = "polls.add_choice"
    # Or multiple of permissions:
    permission_required = ["polls.view_choice", "polls.change_choice"]

# 6、总结
1、django自带的权限系统只支持模型级别,不支持对象级别,比如你创建了个article模型,你在页面上根据这个模型来做展示,然后某个用户就可以查看这个页面,
相当于查找了这个模型的数据,这就是模型级别的查看权限,你在页面上删除了一篇文章,相当于删除了这个模型里的这条数据,这是模型级别的删除权限
但是如果想控制这个用户只能查看和删除自己的文章,不能查看和删除别人的文章,这就做不到了,这个就是需要控制对模型里的某条数据的权限,这就是对象级别权限
也就是说默认的权限可以控制你是否能操作这个模型,但是如果能操作,那就是操作这个模型里的所有数据,而无法控制是否能操作这个表里的某条具体的数据
如果想实现对象级权限,需要在代码中自行判断,例如:
# views.py
from django.contrib.auth.decorators import permission_required
from django.shortcuts import get_object_or_404, render

@permission_required('blog.change_article') # 先检查通用权限
def edit_article(request, article_id):
    article = get_object_or_404(Article, id=article_id)

    # 手动添加对象级权限判断
    if article.author != request.user:
        # 如果文章的作者不是当前登录用户,拒绝访问
        return HttpResponse("你没有权限修改这篇文章!", status=403)

    # 如果是作者,才继续处理修改逻辑
    if request.method == 'POST':
        # ... 处理表单提交 ...
        pass
    
    return render(request, 'edit_article.html', {'article': article})

或者是使用django-guardian 这个第三方对象级别权限库

2、权限和用户是多对多的关系,权限相关的几张表,auth_permission,auth_group,auth_user,auth_user_user_permissions,auth_group_permissions,auth_user_groups

3、权限分配只能分配给已登录用户,匿名用户(AnonymousUser)无法为其分配权限

4、django权限系统默认是模型级别,这里要明白,你操作页面的时候,就是增删改查,控制了你操作模型的权限,实际也就控制了你操作页面的权限,所以django是
通过控制模型来控制页面的,对象级权限也是同理。不要以为Django的权限系统无法控制页面。
login_required装饰器和permission_required装饰器也是这样,控制了视图函数,你访问页面点击某个操作的时候是访问这个视图函数的,所以控制了视图函数也就控制了页面。
2.3、Web 请求的认证
点击查看代码
Django 使用 会话 和中间件将身份验证系统与 请求对象 钩连在一起。

这些机制为每个请求提供了一个 request.user 属性,该属性表示当前用户。如果当前用户未登录,此属性将设置为 AnonymousUser 的实例,否则将是 User 的实例。

你可以使用 is_authenticated 来判断当前用户是否登录,如下所示:

if request.user.is_authenticated:
    # Do something for authenticated users.
    ...
else:
    # Do something for anonymous users.
    ...



参考文档:https://docs.djangoproject.com/zh-hans/4.2/topics/auth/default/

posted @ 2025-11-13 14:49  有形无形  阅读(16)  评论(0)    收藏  举报