美文网首页
认证和权限

认证和权限

作者: SingleDiego | 来源:发表于2018-04-18 11:16 被阅读24次

    目前,我们的 API 对谁可以编辑或删除代码段没有任何限制。我们希望有更高级的行为,以确保:

    • book 对象与创建者相关联。
    • 只有登录用户可以创建 book。
    • 只有 book 的创建者可以更新或删除它。
    • 未登录用户只有只读访问的权限。




    准备工作

    首先我们需要修改我们的 Models 用 ForeignKey 让 Book 和 User 两个模型关联起来。

    编辑 models.py,添加 owner 字段:

    # models.py
    
    from django.db import models
    
    class Book(models.Model):
        title = models.CharField(null=True, blank=True, max_length=50)
        author = models.CharField(null=True, blank=True, max_length=50)
        amount = models.IntegerField()
        # related_name 是必须要写的
        owner = models.ForeignKey('auth.User', related_name='books')
    
        def __str__(self):
            return self.title
    




    创建 User API

    模型修改完毕,接下来我们修改序列化器,添加一个 User 模型的序列化器,编辑 serializers.py 文件:

    # serializers.py
    
    from django.contrib.auth.models import User
    from rest_framework import serializers
    
    class UserSerializer(serializers.ModelSerializer):
    
        # 这里的 book 和模型中 related_name 定义的名字要相同
        books = serializers.PrimaryKeyRelatedField(many=True, queryset=Book.objects.all())
    
        class Meta:
            model = User
            fields = ('id', 'username', 'books')
    

    因为 book 字段在 User 模型中是一个反向关联关系。在使用 ModelSerializer 类时它默认不会被包含,所以我们需要为它添加一个显式字段(这和 ModelForm 添加新字段很相似)。

    接下来在 apis.py 中添加几个视图,通过 UserSerializer 做出展示 User 模型的 API。

    我们只想将 User 展示为只读视图,因此我们将使用 ListAPIViewRetrieveAPIView

    # apis.py
    
    from rest_framework import generics
    from django.contrib.auth.models import User
    from myApp.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
    

    接下来是在 urls.py 添加相应的路径:

    url(r'^api/users/$', apis.UserList.as_view()),
    url(r'^api/users/(?P<pk>[0-9]+)/$', apis.UserDetail.as_view()),
    

    打开:http://127.0.0.1:8000/api/users/ 看看我们的 User API:

    [
        {
            "id": 1,
            "username": "diego",
            "books": [
                1,
                2
            ]
        },
        {
            "id": 2,
            "username": "user1",
            "books": []
        }
    ]
    

    现在每一个用户都会带上来 book 字段,没有 book 与之关联的 user1 也包含一个空的 book 字段。




    将 Book 和 User 关联

    现在,如果我们创建了一个 book 对象时,并不能将创建该 book 的用户与
    book 对象相关联。因为用户不是作为序列化表示的一部分发送的,而是作为请求的一部分发送的。(user 不在传过来的 json 数据中,而是通过 request.user 获得)

    我们处理的方式是在我们的 book 视图中重写一个 .perform_create() 方法,这样我们可以修改序列化器中实例保存的方法,添加上 User 信息。

    # apis.py
    
    class BookList(generics.ListCreateAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    
        def perform_create(self, serializer):
            serializer.save(owner=self.request.user)
    

    这样我们的序列化器的 create() 方法执行时将被传递一个附加的 'owner' 字段。

    现在,这些 book 和创建它们的用户相关联,让我们更新我们的 BookSerializer 来体现这个关联关系。将 owner 字段添加到 serializers.py 中的序列化器定义:

    # serializers.py
    
    from django.contrib.auth.models import User
    from myapp.models import Book
    
    from rest_framework import serializers
    
    
    class BookSerializer(serializers.ModelSerializer):
    
        owner = serializers.ReadOnlyField(source='owner.username')
    
        class Meta:
            model = Book
            fields = ('id', 'title', 'author', 'amount', 'owner')
    
    
    class UserSerializer(serializers.ModelSerializer):
        
        books = serializers.PrimaryKeyRelatedField(many=True, queryset=Book.objects.all())
    
        class Meta:
            model = User
            fields = ('id', 'username', 'books')
    

    owner 的 source 参数控制哪个属性用于填充字段,并且可以指向序列化实例上的任何属性。

    我们添加的字段是无类型的 ReadOnlyField 类,区别于其他类型的字段(如 CharFieldBooleanField 等)。无类型的 ReadOnlyField 始终是只读的,只能用于序列化表示,不能用于在反序列化时更新模型实例。我们也可以在这里使用 CharField(read_only=True)

    更新完 Book 的 API 后我们能看到的数据是这样的:

    {
        "id": 1,
        "title": "book1",
        "author": "aythor1",
        "amount": 1,
        "owner": "diego"
    },
    

    owner 字段被填充了 user 的 username 属性。

    如果我们不显示地指定 owner 字段:

    class BookSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = Book
            fields = ('id', 'title', 'author', 'amount', 'owner')
    

    得到的 API 是这样的:

     {
        "id": 1,
        "title": "book1",
        "author": "aythor1",
        "amount": 1,
        "owner": 1
    },
    

    owner 保存的只是 user 的主键。




    添加视图所需的权限

    现在,book 与用户是相关联的,我们希望确保只有经过身份验证(可理解为已登录或属于 book 的创建者)的用户才能创建,更新和删除 book。

    REST 框架包括许多权限类,我们可以使用这些权限类来限制谁可以访问给定的视图。 在这种情况下,我们需要的是 IsAuthenticatedOrReadOnly 类,这将确保经过身份验证的请求获得读写访问权限,未经身份验证的请求将获得只读访问权限。

    要使用权限模块,首先我们先把它引入:

    from rest_framework import permissions
    

    然后,将相关 permission_classes 字段添加到 BookListBookDetail 的视图类:

    # apis.py
    
    from rest_framework import generics
    from rest_framework import permissions
    
    from myApp.serializers import UserSerializer
    from myApp.serializers import BookSerializer
    
    from django.contrib.auth.models import User
    from myapp.models import Book
    
    
    class BookList(generics.ListCreateAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        # 权限相关字段
        permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    
        def perform_create(self, serializer):
            serializer.save(owner=self.request.user)
    
    
    class BookDetail(generics.RetrieveUpdateDestroyAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        # 权限相关字段
        permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    
    
    class UserList(generics.ListAPIView):
        queryset = User.objects.all()
        serializer_class = UserSerializer
        
    
    class UserDetail(generics.RetrieveAPIView):
        queryset = User.objects.all()
        serializer_class = UserSerializer
    

    完成这些操作后未登录用户就只能对 API 进行只读的操作。




    对象级别的权限

    我们希望所有的 book 都可以被任何人看到,但也要确保只有 book 的创建者(owner)才能更新或删除它。

    为此,我们将需要创建一个自定义权限。在 myApp 中,创建一个新文件permissions.py

    # permissions.py
    
    from rest_framework import permissions
    
    
    class IsOwnerOrReadOnly(permissions.BasePermission):
        """
        自定义权限只允许对象的所有者编辑它。
        """
    
        def has_object_permission(self, request, view, obj):
            # 读取权限允许任何请求,
            # 所以我们总是允许GET,HEAD或OPTIONS请求。
            if request.method in permissions.SAFE_METHODS:
                return True
    
            # 只有该 book 的所有者才允许写权限。
            return obj.owner == request.user
    

    现在,我们可以通过在 BookDetail 视图类中的 permission_classes 属性添加自定义的权限。

    # apis.py
    
    from myapp.permissions import IsOwnerOrReadOnly # 自定义权限
    
    class BookDetail(generics.RetrieveUpdateDestroyAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
    
        permission_classes = (
            permissions.IsAuthenticatedOrReadOnly,
            IsOwnerOrReadOnly,
            )
    

    现在,如果再次打开浏览器,你会发现如果你以 book 创建者的身份登录的话,DELETEPUT 操作才会显示。




    给 Browsable API 添加登陆功能

    REST 框架中内置了 API 的登录页面,只需要在 urls.py 添加就能直接使用。

    from django.conf.urls import include
    
    urlpatterns = [
        ......
    ]
    
    urlpatterns += [
        url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    ]
    

    现在 API 的登录页面就是:http://127.0.0.1:8000/api-auth/login/

    r'^api-auth/' 部分实际上可以是你自定义的任何 URL。唯一的限制是包含的 URL 必须使用 'rest_framework' 命名空间。

    相关文章

      网友评论

          本文标题:认证和权限

          本文链接:https://www.haomeiwen.com/subject/zylpkftx.html