教程 4:认证和权限
现在我们的 API 没有任何限制谁可以编辑删除代码片段,我们需要一些更好的功能来满足下列要求:
- 代码片段需要和创建者进行关联
- 只有认证用户可以创建新的代码片段
- 只有创建者才能修改或删除代码片段
- 未认证用户只有只读权限
# 向模型中新增字段
Snippet
模型需要作出一些改变。首先添加 2 个字段,一个字段用于表明谁创建了这个代码片段,另一个用来存储高亮的 HTML 代码。
编辑models.py
中关于Snippet
的模型,新增下列代码:
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
2
也要确保当实例被保存时,使用pygments
库来正确处理 highlighted 字段。
所以需要引入一些额外的包:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
2
3
接下来,添加.save()
到模型中:
def save(self, *args, **kwargs):
"""
使用pygments来创建高亮的HTML代码。
"""
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)
2
3
4
5
6
7
8
9
10
11
完成上面的步骤后,我们需要更新数据库表。正常情况下我们需要创建一个数据库迁移任务来实现这个目标,但在本教程中,我们删除整个数据库重新建表:
rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
2
3
4
你也许还需要创建几个用户来测试 API,最简便的办法还是使用python manage.py 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')
2
3
4
5
6
7
8
因为 'snippets'
和用户是一种反向关联,默认情况下不会包含在ModelSerializer
类中,所以我们需要手动添加。
我们也需要对views.py
进行修改,另外用户页面是只读的,所以使用ListAPIView
以及 RetrieveAPIView
这 2 个通用类视图:
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
2
3
4
5
6
7
8
9
10
11
12
确保引入了UserSerializer
类:
from snippets.serializers import UserSerializer
最后还需要对其进行 URL 的配置,修改urls.py
,添加下列代码:
path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),
2
# 关联 user 和 snippet
现在,如果我们创建一个代码片段,是没法和用户进行关联的。因为用户信息是通过 request 获取而不是以 serialized 数据传递的。
为了解决这个问题。我们需要重写 snippet 视图中.perform_create()
方法,这个方法准许我们修改实例如何被保存、处理任何由 request 或 requested URL 传递进来的隐含数据。
修改SnippetList
,新增下列代码:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
2
现在create()
方法将获得一个新的字段'owner'
,值就是 request 中的用户信息。
# 更新 serializer
现在代码片段和创建者之间建立了联系,现在需要修改SnippetSerializer
来反映这一变化。修改serializers.py
添加下列代码:
owner = serializers.ReadOnlyField(source='owner.username')
提示
注意:确保你也添加了'owner'到内部类Meta
中的 fields 字段里。
这个字段正在做一些非常有趣的事情。source
参数控制哪个属性用于填充字段,并可以指向序列化实例上的任何属性。它也可以采用上面所示的点符号,在这种情况下,它将遍历给定的属性,与 Django 的模板语言使用的方式类似。
这里我们还使用了ReadOnlyField
类型,不同于其他字段类型,比如CharField
, BooleanField
等。这种类型是只读的,用于进行序列化时候的展示,并且反序列化时不会被修改。这里我们也可以使用CharField(read_only=True)
来替代它。
# 添加权限认证
现在我们将代码片段和用户进行了关联,但我们需要确保只有认证用户才能创建、修改、删除代码片段。
REST framework 包含了多种认证类,这里我们使用IsAuthenticatedOrReadOnly
,这个类确保了只有认证用户才有读写权限,未认证用户则只有只读权限。
修改views.py
,添加:
from rest_framework import permissions
然后修改SnippetList
和SnippetDetail
类,添加:
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
# 添加登录功能
如果你现在打开浏览器,你会发现你无法创建新的代码片段了,所以我们需要启用用户登录功能。编辑tutorial/urls.py
新增代码:
from django.conf.urls import include
在最后,添加:
urlpatterns += [
path('api-auth/', include('rest_framework.urls')),
]
2
3
你可以将'api-auth'替换成任何你喜欢的字符串,唯一的限制就是 namespace 必须为'rest_framework'。在 Django1.9+的版本中,REST framework 将自动设置,所以无需理会。
现在再次刷新页面将在右上角看到'Login'按钮,登录后就可以创建新代码片段了。
当你创建了几个代码片段后,访问/users/
页面,就会看到每个用户对应的代码片段的主键列表了。
# 对象级别的权限
现在所有人都可以浏览代码片段了,接下来实现只有创建者才能删除或修改自己的代码片段功能。想实现这点,我们需要自定义权限认证方法。
新建文件permissions.py
:
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
自定义权限,只有创建者才能编辑
"""
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
接下来修改SnippetDetail
视图:
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]
2
并确保引入了所需的类:
from snippets.permissions import IsOwnerOrReadOnly
再次打开浏览器,如果你是这个代码片段的创建者的话,你会看到'DELETE'和'PUT'操作按钮。
# 为 API 调用添加认证信息
因为我们添加了权限认证,所以我们想编辑任何代码片段时都需要提供认证信息。我们并没有设置任何认证相关的类,目前默认的认证方法是SessionAuthentication
和BasicAuthentication
。
当我们使用浏览器访问时,我们可以登录,浏览器会自动处理 session 信息用于认证。当我们使用编程方式调用 API 时,我们需要显式的为每个请求提供认证信息。
如果我们尝试创建一个代码片段而未提供认证消息的话,我们会得到一个错误返回:
http POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"detail": "Authentication credentials were not provided."
}
2
3
4
5
而提供用户名和密码就可以创建了:
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"
}
2
3
4
5
6
7
8
9
10
11
# 总结
现在,我们已经在 Web API 上获得了一组相当细粒度的权限,以及系统用户和他们创建的代码片段的端点。
在本教程的第 5 部分中,我们将了解如何通过为突出显示的代码片段创建 HTML 端点来将所有东西联系在一起,并通过为系统内的关系使用超链接来改进 API 的内聚性。