Django API 开发:博客系统的权限管理

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 22 天,点击查看活动详情

引言

安全性是任何网站的重要组成部分,但对于 Web API 而言则至关重要。 目前,我们的 Blog API 允许任何人进行完全访问。 没有任何限制; 任何用户都可以做任何极其危险的事情。 例如,匿名用户可以创建,阅读,更新或删除任何博客文章。 他们甚至没有创造一个! 显然,我们不希望这样做。

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

在本文中,我们将添加一个新用户并尝试多种权限设置。 然后,我们将创建自己的自定义权限,以便只有博客文章的作者才可以更新或删除它。

新增一个用户

首先创建第二个用户。 这样,我们可以在两个用户帐户之间切换以测试我们的权限设置。

浏览至 http://127.0.0.1:8000/admin/ 的管理员。 然后单击“用户”旁边的“ +添加”。

输入新用户的用户名和密码,然后单击“保存”按钮。 我在这里选择了用户名 testuser

下一个屏幕是“管理员用户更改”页面。 我已经将我的用户称为 testuser,在这里我可以添加默认用户模型中包含的其他信息,例如名字,姓氏,电子邮件地址等。但是对于我们而言,这些都不是必需的:我们只需要用户名密码用于检测。

向下滚动到此页面的底部,然后单击“保存”按钮。 它将重定向回位于 http://127.0.0.1:8000/admin/auth/user/ 的主用户页面。

我们可以看到列表中有两个用户。

在 browsable API 增加登录功能

今后,无论何时要在用户帐户之间切换,我们都需要跳到 Django 管理员,退出一个帐户,然后登录另一个帐户。 每次。 然后切换回我们的 API 端点。

这是一种常见的情况,Django REST Framework 具有单行设置以添加登录并直接注销到可浏览的 API 本身。 我们将立即实施。

在项目级别的 urls.py 文件中,添加一个包含 rest_framework.urls的新 URL 路由。 令人困惑的是,指定的实际路线可以是我们想要的任何东西; 重要的是 rest_framework.urls 包含在某处。 我们将使用 api-auth 路由,因为它与官方文档匹配,但我们可以轻松使用任何我们想要的东西,并且所有功能都将保持相同。

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('posts.urls')), 
    path('api-auth/', include('rest_framework.urls')), # new
]
复制代码

现在,访问 http://127.0.0.1:8000/api/v1/ 上的可浏览 API。 有一个细微的变化:右上角的用户名旁边是一个向下的箭头。

由于此时我们已使用超级用户帐户登录(对我而言是 wsv ),因此将显示该名称。 单击链接,然后显示带有“注销”的下拉菜单。 点击它。

右上角的链接现在更改为“登录”。 因此,请点击该按钮。

我们被重定向到 Django REST Framework 登录页面。 在此处使用您的测试用户帐户。

最后,它将重定向到主 API 页面,在右上角有testuser。

最后,注销我们的 testuser 帐户。

您应该再次在右上角看到“Log in”链接。

AllowAny

当前,任何匿名非授权用户都可以访问我们的 PostList 端点。 之所以知道这一点,是因为即使我们尚未登录,也可以看到我们的单个博客文章。 更糟糕的是,任何人都有权创建,编辑,更新或删除帖子!

在详细信息页面 http://127.0.0.1:8000/api/v1/1/ 上,该信息也是可见的,任何随机用户都可以更新或删除现有博客文章。

之所以仍然可以看到“发布列表”终结点和“详细列表”终结点,是因为我们之前在 settings.py 文件中将项目的项目级别权限设置为 AllowAny。 简要提醒一下,它看起来像这样:

# blog_project/settings.py

REST_FRAMEWORK = { 
    'DEFAULT_PERMISSION_CLASSES': [
    'rest_framework.permissions.AllowAny', 
    ]
}
复制代码

视图层权限

我们现在想要的是将 API 访问限制为经过身份验证的用户。 我们可以在多个地方执行此操作-项目级,视图级或对象级-但是由于目前我们只有两个视图,因此我们从那里开始,并为每个视图添加权限。

在您的 posts/views.py文件中,从 Django REST 框架的顶部导入权限,然后向每个视图添加一个 permission_classes字段。

# posts/views.py
from rest_framework import generics, permissions # new

from .models import Post
from .serializers import PostSerializer

class PostList(generics.ListCreateAPIView): 
    permission_classes = (permissions.IsAuthenticated,) # new 
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    
class PostDetail(generics.RetrieveUpdateDestroyAPIView): 

    permission_classes = (permissions.IsAuthenticated,) # new 
    queryset = Post.objects.all()
    serializer_class = PostSerializer
复制代码

这就是我们所需要的。 通过 http://127.0.0.1:8000/api/v1/ 刷新可浏览的 API。 看看发生了什么!

我们不再看到我们的“帖子列表”页面。 取而代之的是,由于我们尚未登录,因此会收到不友好的 HTTP 403禁止状态代码。由于我们没有权限,因此可浏览的 API 中没有表格可以编辑数据。

因此,目前只有登录的用户可以查看我们的 API。 如果您使用超级用户或 testuser 帐户重新登录,则可以访问 API 端点。

但是,请考虑一下随着 API 复杂性的增长会发生什么。 将来我们可能会有更多的视图和终点。 如果我们要在整个 API 中设置相同的权限设置,则向每个视图添加专用的 permission_class似乎是重复的。

最好只在项目级别更改一次权限,而不是对每个视图都进行一次更改,这会更好吗?

项目级别权限

此时,您应该点头。 在项目级别设置严格的权限策略并在视图级别根据需要放宽策略是一种更简单,更安全的方法。 这就是我们要做的。

幸运的是,Django REST Framework随 附了许多我们可以使用的内置项目级权限设置,包括:

  • AllowAny-任何经过身份验证的用户都具有完全访问权限
  • IsAuthenticated-只有经过身份验证的注册用户才能访问
  • IsAdminUser-只有管理员/超级用户有权访问
  • IsAuthenticatedOrReadOnly-未经授权的用户可以查看任何页面,但只能查看经过身份验证的用户具有写,编辑或删除权限

要实施这四个设置中的任何一个,都需要更 DEFAULT_PERMISSION_CLASSES 设置和刷新我们的 Web 浏览器。 而已!

让我们切换到 IsAuthenticated ,这样只有经过身份验证或登录的用户才能查看API。

更新 blog_project/settings.py,如下:

# blog_project/settings.py
REST_FRAMEWORK = { 
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated', # new
    ]
}
复制代码

现在返回到 views.py 文件,并删除我们刚刚进行的权限更改。

# posts/views.py
from rest_framework import generics

from .models import Post
from .serializers import PostSerializer


class PostList(generics.ListCreateAPIView): 
    queryset = Post.objects.all() 
    serializer_class = PostSerializer
    
class PostDetail(generics.RetrieveUpdateDestroyAPIView): 
    queryset = Post.objects.all()
    serializer_class = PostSerializer
复制代码

如果刷新“发布列表”和“详细列表” API 页面,您仍将看到相同的 403 状态代码。 现在,我们要求所有用户都必须先进行身份验证,然后才能访问 API,但是我们也始终可以根据需要进行其他视图级更改。

自定义权限

是时候获得我们的第一个自定义权限了。 作为我们现在的简要回顾:我们有两个用户,testuser 和超级用户帐户。 我们的数据库中有一个博客帖子,由超级用户创建。

我们只希望特定博客文章的作者能够对其进行编辑或删除; 否则,博客文章应为只读。 因此,超级用户帐户应具有对单个博客实例的完整 CRUD 访问权限,而常规用户 testuser 应该没有。

使用 Control + c 停止本地服务器,并在我们的帖子应用中创建一个新的 permissions.py文件。

(blogapi) $ touch posts/permissions.py
复制代码

在内部,Django REST Framework 依赖于 BasePermission 类,所有其他权限类都从该 BasePermission 类继承。 这意味着诸如 AllowAny,IsAuthenticated 之类的内置权限设置会对其进行扩展。 这是 Github 上可用的实际源代码

class BasePermission(object): 
    """
    A base class from which all permission classes should inherit. 
    """
    
    def has_permission(self, request, view): 
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True
    
    def has_object_permission(self, request, view, obj): 
        """
        Return `True` if permission is granted, `False` otherwise. 
        """
        return True
复制代码

要创建自己的自定义权限,我们将覆盖 has_object_permission方法。 具体来说,我们希望对所有请求都允许只读,但对于任何写入请求(例如编辑或删除),作者必须与当前登录的用户相同。

这是我们的 posts/permissions.py 文件的内容。

# posts/permissions.py
from rest_framework import permissions


class IsAuthorOrReadOnly(permissions.BasePermission):
  
    def has_object_permission(self, request, view, obj):
        # Read-only permissions are allowed for any request 
        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_object_permission。 如果请求包含 SAFE_METHODS 中包含的 HTTP 动词(包含 GET,OPTIONS 和 HEAD 的元组),则该请求为只读请求,并授予权限。

否则,该请求是针对某种类型的写入的,这意味着需要更新 API 资源,以便创建,删除或编辑功能。 在这种情况下,我们检查所讨论对象的作者(即我们的博客文章 obj.author)是否与发出请求 request.user 的用户匹配。

回到 views.py 文件中,我们应该导入 IsAuthorOrReadOnly,然后我们可以添加PostDetail的permission_classes

# posts/views.py
from rest_framework import generics

from .models import Post
from .permissions import IsAuthorOrReadOnly # new 
from .serializers import PostSerializer


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


class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (IsAuthorOrReadOnly,) # new 
    queryset = Post.objects.all()
    serializer_class = PostSerializer
复制代码

我们完成了。 让我们测试一下。 导航到“帖子详细信息”页面。 确保您使用帖子的作者超级用户帐户登录。 因此它将在页面的右上角可见。

但是,如果您注销然后使用 testuser 帐户登录,则页面会更改。

由于允许只读权限,因此我们可以查看此页面。 但是,由于自定义的 IsAuthorOrReadOnly 权限类,我们无法发出任何 PUT 或 DELETE 请求。

请注意,通用视图将仅检查对象级权限以获取检索单个模型实例的视图。 如果您需要对列表视图进行对象级过滤(对于实例集合),则需要通过覆盖初始查询集进行过滤。

总结

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

猜你喜欢

转载自juejin.im/post/7109858290707726349