第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 的权限。将新权限添加到 PostDetail 和 PostList 的 permission_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 端点上更易访问。