跳转至

第7章:权限

安全是任何网站的重要部分,但对于 Web API 来说,它的重要性加倍。目前,我们的博客 API 允许任何人完全访问。没有限制;任何用户都可以做任何事情,这是非常危险的。例如,匿名用户可以创建、读取、更新或删除任何博客文章。即使是他们没有创建的!显然我们不希望这样。

Django REST Framework 附带了几个开箱即用的权限设置,我们可以使用它们来保护我们的 API。这些可以应用于项目级别、视图级别或任何单独的模型级别。

在本章中,我们将探索所有这三个级别,并最终获得自定义权限,以便只有博客文章的作者才能更新或删除它。

项目级权限

Django REST Framework 有许多配置,这些配置命名在空间内,位于一个名为 REST_FRAMEWORK 的单个 Django 设置中。我们已经在 django_project/settings.py 文件中明确设置了其中之一 AllowAny

Code

# django_project/settings.py
REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.AllowAny",  # new
    ],
}

实际上,我们可以使用四个内置的项目级权限设置: - AllowAny - 任何用户,无论是否认证,都具有完全访问权限 - IsAuthenticated - 只有认证的、注册的用户才有权限 - IsAdminUser - 只有管理员/超级用户才有权限 - IsAuthenticatedOrReadOnly - 未授权的用户可以查看任何页面,但只有认证的用户才具有写入、编辑或删除权限

实现这四个设置中的任何一个都需要更新 DEFAULT_PERMISSION_CLASSES 设置并刷新我们的 Web 浏览器。就是这样!

让我们将 IsAuthenticated 切换为只有认证的或登录的用户才能查看 API。

更新 django_project/settings.py 文件如下:

Code

# django_project/settings.py
REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticated",  # new
    ],
}

如果您刷新 Web 浏览器,什么都不会改变,因为我们已经使用超级用户帐户登录了。它应该出现在可浏览 API 的右上角。要注销,请在 [[http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/`)) 输入管理员,然后单击右上角的"Log Out"链接。

如果您返回 [[http://127.0.0.1:8000/api/v1/,它会显示](http://127.0.0.1:8000/api/v1/,它会显示](http://127.0.0.1:8000/api/v1/,它会显示](http://127.0.0.1:8000/api/v1/`,它会显示)) HTTP 403 Forbidden 错误,因为未提供认证凭据。这就是我们想要的。

创建新用户

我们需要创建一个新用户来测试普通用户(不仅仅是管理员超级用户)是否可以访问 API。有两种方法可以做到这一点:使用 python manage.py createsuperuser 在命令 Shell 中创建用户,或者我们可以登录管理员并以这种方式添加用户。让我们选择管理员路由。

回到 [[http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/`)) 的管理员并使用您的超级用户凭据登录。然后单击用户旁边的"+Add"。输入新用户的用户名和密码,然后单击"Save"按钮。

下一个屏幕是管理员用户更改页面。在这里您可以添加默认用户模型上包含的其他信息,如名字、姓氏、电子邮件地址等。但就我们的目的而言,这些都不是必需的:我们只是需要一个用户名和密码进行测试。

向下滚动到此页面底部,然后单击"Save"按钮。它将重定向回主用户页面。

我们可以看到列出了两个用户。请注意,只有一个帐户显示"Staff Status",这是超级用户。作为最后一步,单击网页右上角的"Log Out"链接以离开管理员。

添加登录和注销

完成设置后,我们的新用户如何登录可浏览 API?我们可以通过更新项目级 URL 配置来实现。使用登录的新路径更新 django_project/urls.py 如下。

Code

# django_project/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/v1/", include("posts.urls")),
    path("api-auth/", include("rest_framework.urls")),  # new
]

现在导航到我们的可浏览 API。有一个微妙的变化:右上角的"Login"链接。单击它登录。

使用新的用户帐户登录。这将重定向回文章列表页面,其中用户名出现在右上角以及一个显示下拉"Logout"链接的箭头。

视图级权限

也可以在视图级别添加权限以获得更精细的控制。让我们更新我们的 PostDetail 视图,以便只有管理员用户可以查看它。

posts/views.py 文件中,从 Django REST Framework 导入权限,然后向 PostDetail 添加一个 permission_classes 字段,将其设置为 IsAdminUser

Code

# posts/views.py
from rest_framework import generics, permissions
from .models import Post
from .serializers import PostSerializer

class PostList(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAdminUser,)
    queryset = Post.objects.all()
    serializer_class = PostSerializer

刷新可浏览 API,文章列表页面仍然可见。但是,如果您导航到文章详细页面,则会显示 HTTP 403 Forbidden 状态码。

如果您注销可浏览管理员,然后使用您的管理员帐户登录,文章详细页面仍然可见。因此,我们有效地应用了视图级权限。

如您所见,设置权限的标准类型是允许任何用户完全访问、限制为认证用户、限制为管理员用户,或允许认证用户执行任何请求但其他用户只读。如何配置权限取决于您的项目需求。

在我们继续之前,请删除 PostDetail 上的 permission_classes 字段。就我们的目的而言,限制对认证用户的访问就足够了,我们已经在 django_project/settings.py 中使用 DEFAULT_PERMISSION_CLASSES 配置完成了这一点。

自定义权限

对于我们的第一个自定义权限,我们希望限制访问,以便只有博客文章的作者才能编辑或删除它。管理员超级用户将有权访问所有内容,但普通用户只能更新/删除自己的内容。

在内部,Django REST Framework 依赖于一个基本权限类,所有其他权限类都从中继承。所有内置权限设置都简单地扩展 BasePermission

对于自定义权限类,您可以重写其中一个或两个方法。has_permission 适用于列表视图,而详细视图执行两者:首先是 has_permission,然后(如果通过)has_object_permission

在我们的例子中,我们只希望博客文章的作者具有编辑或删除它的写入权限。我们还希望将只读列表视图限制为认证用户。为此,我们将创建一个名为 posts/permissions.py 的新文件,并填充以下代码:

Code

# posts/permissions.py
from rest_framework import permissions

class IsAuthorOrReadOnly(permissions.BasePermission):
    def has_permission(self, request, view):
        # Authenticated users only can see list view
        if request.user.is_authenticated:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request so we'll always
        # allow GET, HEAD, or OPTIONS requests
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the author of a post
        return obj.author == request.user

我们在顶部导入权限,然后创建一个自定义类 IsAuthorOrReadOnly,它扩展 BasePermission。第一个方法 has_permission 要求用户已登录或认证才能访问。第二个方法 has_object_permission 允许只读请求,但将写入权限限制为只有博客文章的作者。

回到 views.py 文件,我们可以删除权限导入,因为我们将用导入我们的自定义 IsAuthorOrReadOnly 权限来代替 PostDetail 的权限。将新权限添加到 PostDetailPostListpermission_classes

Code

# posts/views.py
from rest_framework import generics
from .models import Post
from .permissions import IsAuthorOrReadOnly
from .serializers import PostSerializer

class PostList(generics.ListCreateAPIView):
    permission_classes = (IsAuthorOrReadOnly,)
    queryset = Post.objects.all()
    serializer_class = PostSerializer

class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (IsAuthorOrReadOnly,)
    queryset = Post.objects.all()
    serializer_class = PostSerializer

我们完成了。要检查这一点,我们需要创建一个以测试用户作为作者的博客文章条目,并确认测试用户可以访问它。

创建后,前往文章详细端点。使用右上角的下拉菜单从您的管理员帐户"注销",然后以测试用户身份重新登录。

是的!有编辑或删除条目的选项,因为测试用户是作者。但是,如果您导航到第一个博客文章的详细页面,它是只读的,因为测试用户不是作者。

为了确保我们的认证控制正常工作,请在右上角注销。然后导航到文章列表端点和两个文章详细端点,以确认注销的用户没有访问权限。

最后,我们应该将我们的新工作提交到 Git。

Shell

(.venv) > git status
(.venv) > git add -A
(.venv) > git commit -m "add permissions"

结论

设置适当的权限是任何 API 的重要组成部分。作为一般策略,最好设置严格的项目级权限策略,以便只有认证的用户才能查看 API。然后根据需要使视图级或自定义权限在特定 API 端点上更易访问。