Django Rest Framework(第四天)-认证和权限分析

一、认证和权限

当前我们的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,)


评论(0 ) 点赞(16)


暂未登录,请登录之后发表评论。 QQ