一、认证和权限
当前我们的API没有任何限制,谁都可以编辑或者删除代码片段。我们希望有一些更多高级行为用于实现以下这些:
代码片段是总是关联一个创建者 只有有权限的用户才能够创建代码片段对象 只有代码片段实例对象的创建者才能够更新或者删除它。 没有权限的请求只能够有只读权限。
1.1 添加信息到我们的模型
我们准备创建一组改变在我们的Snippet模型类。首先,让我们添加一组字段。
1、其中一个字段将用于表示创建code snippet的创建者 2、另一个字段将用于表示存储代码的高亮显示的HTML内容。
添加以下两个字段到quickstart/models.py下Snippet类型中:
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE) highlighted = models.TextField()
我们也是需要确认当模型被保存的时候,使用pygments代码高亮显示库填充要高亮显示的字段。
我们需要更多额外的导入:quickstart/models.py下Snippet类型中:
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)
所有的事情做完之后,我们需要更新我们的数据库表。正常情况下,我们将创建一个数据库migration,但是只是个教程示例,所以我们选择直接删除数据库并重新开始。
rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
# 创建一些不同的用户
python manage.py createsuperuser
1.2 添加路径到我们的用户模型
现在我们已经创建了一些用户,我们最好在API中添加这些用户的表示,在quickstart/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 类时它默认不会被包含,所以我们需要为它添加一个显式字段。
我们还会在quickstart/views.py中添加几个视图。只想将用户展示位只读视图,因此我们将使用ListAPIView和RetrieveAPIView通用的基于类的视图。
from django.contrib.auth.models import User from snippets.serializers import UserSerializer class UserList(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer class UserDetail(generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer
最后,我们需要通过在URL conf中引用它们来将这些视图添加到API中。将以下内容添加到quickstart/urls.py文件的patterns中。
url(r'^users/$', views.UserList.as_view()), url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
1.3 将Snippet实例和用户关联
现在,如果我们创建了一个代码片段,并不能将创建该代码片段的用户与代码段实例相关联。用户不是作为序列化表示的一部分发送的,而是作为传入请求的属性。(译者注:user不在传过来的数据中,而是通过request.user获得)
我们处理的方式是在我们的代码片段视图中重写一个.perform_create()方法,这样我们可以修改实例保存的方法,并处理传入请求或请求URL中隐含的任何信息。
在quickstart/views.py,SnippetList视图类中,添加以下方法:
def perform_create(self, serializer): serializer.save(owner=self.request.user)
我们的序列化器的create()方法现在将被传递一个附加的'owner'字段以及来自请求的验证数据。
1.4 更新我们的序列化器
现在,这些代码片段和创建它们的用户相关联,让我们更新我们的SnippetSerializer来体现这个关联关系。将以下字段添加到quickstart/serializers.py中的序列化器SnippetSerializer定义:
class SnippetSerializer(serializers.ModelSerializer): """ 使用ModerSerializer类 """ owner = serializers.ReadOnlyField(source='owner.username') class Meta: model = Snippet fields = ('id', 'title', 'code', 'linenos', 'language', 'style', 'owner')
这个字段非常有趣。source参数控制哪个属性用于填充字段,并且可以指向序列化实例上的任何属性。它也可以采用如上所示点加下划线的方式,在这种情况下,它将以与Django模板语言一起使用的相似方式遍历给定的属性。
我们添加的字段是无类型的ReadOnlyField类,区别于其他类型的字段(如CharField,BooleanField等)。无类型的ReadOnlyField始终是只读的,只能用于序列化表示,不能用于在反序列化时更新模型实例。我们也可以在这里使用CharField(read_only=True)。
1.5 给Browsable API添加登陆
通过http访问接口 启用rest_framework的登入登出功能 在tutorial/urls.py中,新增代码如下:
from django.conf.urls import url, include # from quickstart import views urlpatterns = [ path('admin/', admin.site.urls), # url(r'^user/', views.TestView.as_view()), url(r'^', include('quickstart.urls', namespace='qk')), ] urlpatterns += [ url(r'^api-auth/', include('rest_framework.urls')), ]
这样,在页面中就可以看到Login,Logout按钮了.现在,如果你再次打开浏览器并刷新页面,你将在页面右上角看到一个“登录”链接。如果你用早期创建的用户登录,就可以再次创建代码片段。
一旦你创建了一些代码片段后,在'/users/'路径下你会注意到每个用户的'snippets'字段都包含与每个用户相关联的代码片段的列表。
1.6 添加视图所需的权限
现在,代码片段与用户是相关联的,我们希望确保只有经过身份验证的用户才能创建,更新和删除代码片段。
REST框架包括许多权限类,我们可以使用这些权限类来限制谁可以访问给定的视图。 在这种情况下,我们需要的是IsAuthenticatedOrReadOnly类,这将确保经过身份验证的请求获得读写访问权限,未经身份验证的请求将获得只读访问权限.
首先要在quickstart/views.py视图模块中导入以下内容:
from rest_framework import permissions # 然后,将以下属性添加到SnippetList和SnippetDetail视图类中。 permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
1.7 添加对象级别的权限
我们希望所有的代码片段都可以被任何人看到,但也要确保只有创建代码片段的用户才能更新或删除它。 为此,我们将需要创建一个自定义权限。 在quickstart中添加permissions.py文件:
from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): """ 自定义权限只允许对象的所有者编辑它 """ def has_object_permission(self, request, view, obj): """ # 读取权限允许任何请求 # 所以我们总是允许GET,HEAD或OPTIONS请求 :param request: :param view: :param obj: :return: """ if request.method in permissions.SAFE_METHODS: return True # 只有该snippet的所有者才允许写权限 return obj.owner == request.user
现在,我们可以通过在quickstart/views.py中SnippetDetail视图类中编辑permission_classes属性将该自定义权限添加到我们的代码片段实例路径:
from snippets.permissions import IsOwnerOrReadOnly permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
1.8 使用API进行身份验证
现在因为我们在API上有一组权限,如果我们要编辑任何代码片段,我们都需要验证我们的请求。我们还没有设置任何身份验证类,所以应用的是默认的SessionAuthentication和BasicAuthentication。
当我们通过Web浏览器与API进行交互时,我们可以登录,然后浏览器会话将为请求提供所需的身份验证。
如果我们在代码中与API交互,我们需要在每次请求上显式提供身份验证凭据。
如果我们通过没有验证就尝试创建一个代码片段,我们会像下面展示的那样收到报错:
http POST http://127.0.0.1:8018/qk/snippets/ code="print 123" { "detail": "Authentication credentials were not provided." }
我们可以通过加上我们之前创建的一个用户的用户名和密码来创建:
http -a tom:password123 POST http://127.0.0.1:8018/qk/snippets/ code="print 789" { "id": 5, "owner": "tom", "title": "foo", "code": "print 789", "linenos": false, "language": "python", "style": "friendly" }
二、总结
我们现在已经在我们的Web API上获得了相当精细的一组权限控制,并为系统的用户和他们创建的代码片段提供了API路径。
最终效果图:
![]()
2.1 认证Authentication
1、可以在配置文件中配置全局默认的认证方案:rest_framework/settings.py
'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ),
2、也可以在每个视图中通过设置authentication_classess属性来设置
from rest_framework.authentication import SessionAuthentication, BasicAuthentication from rest_framework.views import APIView class ExampleView(APIView): authentication_classes = (SessionAuthentication, BasicAuthentication)
3、认证失败会有两种可能的返回值:
401 Unauthorized 未认证 403 Permission Denied 权限被禁止
2.2 权限Permissions
2.2.1 权限控制可以限制用户对于视图的访问和对于具体数据对象的访问。
1、在执行视图的dispatch()方法前,会先进行视图访问权限的判断
2、在通过get_object()获取具体对象时,会进行对象访问权限的判断
2.2.2 使用
1、可以在配置文件中设置默认的权限管理类,如
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ) } # 如果未指明,则采用如下默认配置 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.AllowAny', )
2、可以在具体的视图中通过permission_classes属性来设置,如
from rest_framework.permissions import IsAuthenticated from rest_framework.views import APIView class ExampleView(APIView): permission_classes = (IsAuthenticated,) ...
2.2.2 提供的权限
1、AllowAny 允许所有用户 2、IsAuthenticated 仅通过认证的用户 3、IsAdminUser 仅管理员用户 4、IsAuthenticatedOrReadOnly 认证的用户可以完全操作,否则只能get读取
# 举例 from rest_framework.authentication import SessionAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.generics import RetrieveAPIView class BookDetailView(RetrieveAPIView): queryset = BookInfo.objects.all() serializer_class = BookInfoSerializer authentication_classes = [SessionAuthentication] permission_classes = [IsAuthenticated]
2.2.3 自定义权限
如需自定义权限,需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部
1、.has_permission(self, request, view) 是否可以访问视图, view表示当前视图对象
2、.has_object_permission(self, request, view, obj) 是否可以访问数据对象, view表示当前视图, obj为数据对象
例如:
# quickstart/permissions.py from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): """ 自定义权限只允许对象的所有者编辑它 """ def has_object_permission(self, request, view, obj): """ # 读取权限允许任何请求 # 所以我们总是允许GET,HEAD或OPTIONS请求 :param request: :param view: :param obj: :return: """ if request.method in permissions.SAFE_METHODS: return True # 只有该snippet的所有者才允许写权限 return obj.owner == request.user # quickstart/views.py class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
2.3 限流Throttling
可以对接口访问的频次进行限制,以减轻服务器压力。
2.3.1 使用
可以在配置文件中,使用DEFAULT_THROTTLE_CLASSES 和 DEFAULT_THROTTLE_RATES进行全局配置,
# rest_framework/settings.py 'DEFAULT_THROTTLE_CLASSES': ( 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ), 'DEFAULT_THROTTLE_RATES': { 'anon': '100/day', 'user': '1000/day' } # DEFAULT_THROTTLE_RATES 可以使用 second, minute, hour 或day来指明周期。 # 也可以在具体视图中通过throttle_classess属性来配置,如: from rest_framework.throttling import UserRateThrottle from rest_framework.views import APIView class ExampleView(APIView): throttle_classes = (UserRateThrottle,) ...
2.3.2 可选限流类
1、AnonRateThrottle
限制所有匿名未认证用户,使用IP区分用户。 使用:DEFAULT_THROTTLE_RATES['anon'] 来设置频次
2、UserRateThrottle
限制认证用户,使用User id 来区分。 使用:DEFAULT_THROTTLE_RATES['user'] 来设置频次
3、ScopedRateThrottle 限制用户对于每个视图的访问频次,使用ip或user id。
demo:
class ContactListView(APIView): throttle_scope = 'contacts' ... class ContactDetailView(APIView): throttle_scope = 'contacts' ... class UploadView(APIView): throttle_scope = 'uploads' ... REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': ( 'rest_framework.throttling.ScopedRateThrottle', ), 'DEFAULT_THROTTLE_RATES': { 'contacts': '1000/day', 'uploads': '20/day' } } from rest_framework.authentication import SessionAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.generics import RetrieveAPIView from rest_framework.throttling import UserRateThrottle class BookDetailView(RetrieveAPIView): queryset = BookInfo.objects.all() serializer_class = BookInfoSerializer authentication_classes = [SessionAuthentication] permission_classes = [IsAuthenticated] throttle_classes = (UserRateThrottle,)