Tutorial 4: 授权(Authentication)与权限(Permissions)
当前,我们的API没有限制谁能编辑或删除snippets代码。我们想要一些更高级的行为以确保:
- snippets数据总是与创建者联系在一起。
- 只有授权用户才能创建snippets。
- 只有snippet的创建者才能更新或者删除它。
- 没有授权的请求应该只有只读权限。
在我们的模型中添加信息
我们打算对我们的Snippet
模型类做些改变。首先,让我们添加几个字段。其中一个字段将显示出哪个用户创建里snippet数据。另一个字段将用于HTML代码高亮。
owner = models.ForeignKey('auth.User', related_name='snippets')
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 = self.linenos and 'table' or False
options = self.title and {'title': self.title} or {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
然后,我们需要更细我们的数据库表。为此,正常情况下,我们会创建数据库迁移(database migration),但是就本教程来说,我们只需要删除原来的数据库,然后重新创建即可。
rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
你可能也想要创建不同的用户来测试API。最快的方式就是用createsuperuser
命令。
python manage.py createsuperuser
为我们的用户模型添加端点
既然我们已经创建了多个用户,那么我们最好将用户添加到我们的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'
在用户模型中是一个相反的关系,默认情况下在使用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
最后,我们需要通过修改URL配置,将这些视图添加进API。添加以下urls.py
中。
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
将用户和Snippets连接起来
现在,如果我们创建snippet数据,我们没办法将用户和snippet实例联系起来。虽然用户不是序列表示的部分,但是它是请求的一个属性。
我们通过重写snippet视图的.perform_create()
方法来做到,这个方法允许我们修改如何保存实例,修改任何请求对象或者请求连接里的信息。
在SnippetList
视图类中添加以下方法;
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
现在,我们序列的create()
方法将会另外传入一个来自有效的请求数据的'owner'
字段。
更新我们的序列
既然已经将snippets和创建它们的用户联系在一起了,那么我们需要更新对应的SnippetSerializer
。在serializers.py
的序列定义(serializer definition)中添加以下字段:
owner = serializers.ReadOnlyField(source='owner.username')
注意;去报你也添加'owner',
到内部类Meta
的字段列表里。
这个字段很有趣。source
参数控制哪个属性被用于构成一个字段,并且能够指出序列实例的任何属性。它也能想上面一样使用点标记(.),这中情况下他会横贯给定的属性,就是我们使用Django模板语言一样。
我们添加的字段是隐式ReadOnly
类,与其他类相反,如CharField
,BooleanField
,隐式ReadOnlyField
总是只读的,用于序列化表示,但在数据非序列化时不能用于更新实例。这里我们也可以用CharField(read_only=True)
。
为视图添加需要的权限
snippets数据已经和用户联系在一起,我们想确保只有授权的用户可以创建、更新和删除snippet数据。
REST框架包括许多权限类(permission classes),我们可以使用这些权限类来现在视图的访问权限。这种情况下,其中我们需要IsAuthenticatedOrReadOnly
,这个类确保授权请求有读写权限,而没有授权的用户只有只读权限。
首先,在视图模块中引入以下代码
from rest_framework import permissions
然后,在SnippetList
和SnippetDetail
视图类中添加以下属性。
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
在浏览器API中添加登录
如果你现在用浏览器打开API,你会发现你已经不能创建新的snippets数据。为此,我们需要以用户身份登录。
为了使用浏览器打开API,我们需要添加一个登录视图,编辑URL配置(URLconf)文件urls.py
文件。
在urls.py
顶部添加下面import。
from django.conf.urls import include
并且,在urls.py
底部为API添加一个包括登录和退出视图的url样式。
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
url样式的r'^api-auth/'
部分实际上可以是任何你想要的URL。唯一的限制就是include的链接必须使用'rest_framework'
名字空间。在Django 1.9+,REST框架会设置名字空间,所以你必须写。
现在如果你刷新浏览器页面,你会看到右上角的'Login'链接。如果你用之前创建的用户登录,你就可以再次写snippets数据了。
一旦你创建snippets数据,浏览'/users/',然后你会发现在每个用户的'snippets'字段,显示的内容包括与每个用户相关的snippets主键。
对象等级权限
虽然我们真的想任何人都和一看见snippets数据,但也要确保只有创建snippet的用户可以修改或删除他的snippet。
为此,我们需要创建自定义权限。
在snippets app中,创建一个新文件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)属于你的snippet数据。
授权API
因为我们的API有一系列权限,所以如果我们想编辑任何snippets,我们需要授权我们的请求。我们现在还没有任何授权类(authenticaions classes)
,所以默认情况下只有SessionAuthentication
和BasicAuthentication
。
当我们通过Web浏览器与API交互时,我们可以登录,然后浏览器会话(session)将会提供必须的请求授权。
如果我们通过程序与API交互,我们需要为每个请求提供明确的授权证明。
如果我们在没有授权的情况下创建一个snippet,那么我们会得到下面的错误:
http POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"detail": "Authentication credentials were not provided."
}
为了请求成功,我们需要包含用户名和密码。
http -a tom:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
{
"id": 5,
"owner": "tom",
"title": "foo",
"code": "print 789",
"linenos": false,
"language": "python",
"style": "friendly"
}
总结
现在我们已经在我们的Web API上,为我们的系统用户和snippet的创建者,添加了很多权限和端点。 在第五部分,我们将会看怎么我们可以通过为我们的高亮snippets创建HTML端点来将所有东西联系在一起,然后在系统内用超链接将我们的API联系起来。