Tutorial 4: Authentication & Permissions
目前,我们的API对谁可以编辑或删除代码段没有任何限制。我们希望有一些更高级的行为,以确保:
- 代码段始终与创建者相关联。
- 只有经过身份验证的用户才能创建摘要。
- 只有代码段的创建者可以更新或删除它。
- 未经身份验证的请求应具有完全只读访问权限。
Adding information to our model
我们将对我们的Snippet
模型类进行一些更改。首先,让我们添加几个字段。其中一个字段将用于表示创建代码段的用户。另一个字段将用于存储代码的突出显示的HTML表示。
将以下两个字段添加到models.py
中的Snippet
模型。
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
我们还需要确保在保存模型时,使用pygments
代码突出显示库填充突出显示的字段。
我们需要一些额外的导入:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
现在我们可以在模型类中添加.save()
方法:
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = 'table' if self.linenos else False
options = {'title': self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
完成所有操作后,我们需要更新数据库表。通常我们会创建一个数据库迁移,但为了本教程的目的,让我们删除数据库并重新开始。
rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
您可能还想创建一些不同的用户,用于测试API。执行此操作的最快方法是使用createsuperuser
命令。
python manage.py createsuperuser
Adding endpoints for our User models
既然我们已经有一些用户可以使用,我们最好将这些用户的表示添加到我们的API中。创建新的序列化器很容易。在serializers.py
中添加:
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
由于snippets
在User模型上是反向关系,因此在使用ModelSerializer
类时默认情况下不会包含它,因此我们需要为其添加显式字段。
我们还将为views.py
添加一些视图。我们只想对用户表示使用只读视图,因此我们将使用ListAPIView
和RetrieveAPIView
基于类的通用视图。
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
确保还导入UserSerializer
类
from snippets.serializers import UserSerializer
最后,我们需要将这些视图添加到API中,方法是从URL conf中引用它们。将以下内容添加到urls.py
中的模式中。\
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
Associating Snippets with Users
现在,如果我们创建了代码段,则无法将创建代码段的用户与代码段实例相关联。用户不是作为序列化表示的一部分发送的,而是传入请求的属性。
我们处理的方法是在我们的代码段视图上覆盖.perform_create()
方法,这允许我们修改实例保存的管理方式,并处理传入请求或请求的URL中隐含的任何信息。
在SnippetList
视图类上,添加以下方法:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
现在,我们的序列化器的create()
方法将传递一个额外的owner
字段,以及来自请求的验证数据。
Updating our serializer
既然片段与创建它们的用户相关联,那么让我们更新我们的SnippetSerializer
来反映它。将以下字段添加到serializers.py
中的序列化程序定义:
owner = serializers.ReadOnlyField(source='owner.username')
注意:确保您还将owner
添加到内部Meta
类的字段列表中。
这个领域做得非常有趣。 source
参数控制用于填充字段的属性,并且可以指向序列化实例上的任何属性。它也可以采用上面显示的虚线表示法,在这种情况下,它将以与Django模板语言一样的方式遍历给定的属性。
我们添加的字段是无类型的ReadOnlyField
类,与其他类型字段(如CharField
,BooleanField
等)形成对比... 无类型ReadOnlyField
始终是只读的,将用于序列化表示,但在反序列化时不会用于更新模型实例。我们也可以在这里使用CharField(read_only = True)
。
Adding required permissions to views
既然代码片段与用户相关联,我们希望确保只有经过身份验证的用户才能创建,更新和删除代码段。
REST框架包含许多权限类,我们可以使用这些权限来限制谁可以访问给定视图。在这种情况下,我们正在寻找的是 IsAuthenticatedOrReadOnly
,它将确保经过身份验证的请求获得读写访问权限,未经身份验证的请求获得只读访问权限。
首先在views模块中添加以下导入
from rest_framework import permissions
然后,将以下属性添加到SnippetList
和SnippetDetail
视图类。
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Adding login to the Browsable API
如果您此时打开浏览器并导航到可浏览的API,您将发现您无法再创建新的代码段。为此,我们需要能够以用户身份登录。
我们可以通过在项目级urls.py
文件中编辑URLconf来添加登录视图以与可浏览API一起使用。
在文件顶部添加以下导入:
from django.conf.urls import include
并且,在文件末尾添加模式以包含可浏览API的登录和注销视图。
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls')),
]
模式的r'^ api-auth /'
部分实际上可以是您想要使用的任何URL。
现在,如果您再次打开浏览器并刷新页面,您将在页面右上角看到“登录”链接。如果您以之前创建的某个用户身份登录,则可以再次创建代码段。
创建一些代码段后,导航到'/ users /'端点,并注意该表示包含每个用户的“代码段”字段中与每个用户关联的代码段ID列表。
Object level permissions
实际上,我们希望所有人都可以看到所有代码段,但也要确保只有创建代码段的用户才能更新或删除它。
为此,我们需要创建自定义权限。
在代码段应用中,创建一个新文件permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
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 owner of the snippet.
return obj.owner == request.user
现在,我们可以通过编辑SnippetDetail
视图类上的permission_classes
属性,将该自定义权限添加到我们的代码段实例端点:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
确保还导入IsOwnerOrReadOnly类。
from snippets.permissions import IsOwnerOrReadOnly
现在,如果再次打开浏览器,如果您以创建代码片段的同一用户身份登录,则会发现“DELETE”和“PUT”操作仅出现在代码段实例端点上。
Authenticating with the API
因为我们现在拥有API的一组权限,所以如果我们要编辑任何代码段,我们需要验证我们对它的请求。我们还没有设置任何身份验证类,因此当前应用了默认值,即SessionAuthentication
和BasicAuthentication
。
当我们通过Web浏览器与API交互时,我们可以登录,然后浏览器会话将为请求提供所需的身份验证。
如果我们以编程方式与API交互,我们需要在每个请求上明确提供身份验证凭据。
如果我们尝试在不进行身份验证的情况下创建代码段,则会收到错误消息:
http POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"detail": "Authentication credentials were not provided."
}
我们可以通过包含我们之前创建的用户之一的用户名和密码来成功提出请求。
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
{
"id": 1,
"owner": "admin",
"title": "foo",
"code": "print 789",
"linenos": false,
"language": "python",
"style": "friendly"
}
Summary
我们现在已经在Web API上获得了相当细粒度的权限集,以及系统用户和他们创建的代码片段的端点。
在本教程的第5部分中part 5,我们将介绍如何通过为突出显示的片段创建HTML端点来将所有内容组合在一起,并通过对系统内的关系使用超链接来提高API的内聚力。
网友评论