Django编写RESTful API(四):认证和权限

欢迎访问个人我的网站:www.comingnext.cnhtml

前言:

按照前面几篇文章里那样作,使用Django编写RESTful API的基本功能已经像模像样了。咱们能够经过不一样的URL访问到不一样的资源,经过不一样的HTTP请求来实现对资源的不一样操做。python

可是如今咱们的API还有一个很明显的缺陷,那就是没有认证和权限功能,任何资源都会任何用户被随意更改,因此咱们要改进程序,实现如下功能:sql

  1. snippet与其建立者相互关联
  2. 只有通过身份验证(登陆)的用户才能够建立snippets
  3. 只有建立该snippet的用户才能够对其进行更改或者删除
  4. 未经验证的用户只具备访问(只读)的功能

修改snippet模型

首先,咱们想让snippets都和它们的建立用户关联起来,因此咱们天然的要在Snippet模型添加一个owner字段来表示。另外,咱们还添加一个highlighted字段用来实现代码高亮,修改snippets/models.py的Snippet:shell

owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()

 

想要实现代码高亮,固然不是上面一行代码就搞定了,它如今还只是一个普通的字段而已。咱们要作的是在保存的时候,也就是当执行save()时, 咱们使用pygments生成高亮后的HTML,仍是在model.py,首先导入相关的库:数据库

from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

 

而后在Snippet类中添加save()方法:django

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)

 

在保存数据的时候就会执行上面这个方法,整个方法的功能如注释所示,在这一篇文章中还不会具体的展现这个功能,在接下来的文章中会展现。api

修改了模型固然须要同步一下数据库了,在这里咱们和官方文档同样把数据库删了在从新生成,首先把工程目录下的db.sqlite3以及snippets下的migrations文件夹删除,而后再执行迁移步骤:浏览器

python manage.py makemigrations snippets
python manage.py migrate

 

同时,因为咱们想要实现的是访问各个snippet时显示相应的建立者,因此这里须要建立几个不一样的帐户稍后才能够显示。安全

python manage.py createsuperuser

 


为咱们的用户模型添加端点

原理和以前的SnippetSerializer基本同样,在snippets/serializers.py中添加一个User序列化器:ide

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 = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

 

由于snippets在User模型中是一个反向关系,在使用ModelSerializer类时默认状况是不会包括这个关系,就是说经过Snippet的owner能查询到User,而User这边查询不到一个用户建立的snippet,因此咱们须要手动为用户序列添加这个字段。

弄好了User的序列化器,接着就要让其可以显示出来,因此要添加相关的视图类,编辑view.py:

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访问到它们,确定是配置一下路由分发啦,编辑snippets/urls.py,添加下面的匹配模式:

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

 


把Snippets和Users关联起来

到了这里,若是像以前那样建立代码段的话,咱们还不能把Snippets和Users关联起来。由于在使用的时候User的数据是经过request传入的,而不是以序列化的数据传递过来。

而咱们刚才添加了一个owner做为外键,这个时候就要看到它的用处了,编辑view.py,为SnippetList视图类添加一个方法:

class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

 

这个perform_create() 可让用户在经过POST请求建立一个新的Snippet时,在保存新的Snippet数据的时候会把request中的user赋值给Snippet的owner。等下具体使用的时候就能够轻松的理解了。


更新serializer

上一步已经把二者关联起来了,owner会在建立新的Snippet的时候拥有User的各个属性,那么在API中要让owner显示id仍是用户名,为了提升可读性,答案固然是显示用户名了,因此咱们在SnippetSerializer 下面增长一个字段:

owner = serializers.ReadOnlyField(source='owner.username')

 

这里的source参数就指定了哪一个属性用于填充字段,为了在使用的时候显示owner,可是还要把它添加进Meta类里面,因此整个SnippetSerializer以下:
class SnippetSerializer(serializers.ModelSerializer):
    # 这里可使用也 CharField(read_only=True) 来替换
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style','owner')

 


添加权限

如今Snippet和User已经关联起来而且是可浏览的。接下来咱们要实现的及时权限的问题了。也就是咱们一开始说的几点中的:

  1. 只有通过身份验证(登陆)的用户才能够建立snippet

  2. 只有建立该snippet的用户才能够对其进行更改或者删除

  3. 未经验证的用户只具备访问(只读)的功能

首先在views.py导入一个库:

from rest_framework import permissions

 

接着为SnippetList 和 SnippetDetail添加权限判断,在这两个视图类中都加入:

permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

这里要特别注意,有一个坑,就是那个逗号必定要加上去,否则就会报错。

这行代码的做用就是判断当前用户是否为该Snippet的建立者,而其余用户只有只读属性,就是只能查看。

 


为可浏览的API添加登陆功能

刚才添加了权限判断,若是没有登陆用户,那就至关于游客啦,什么功能都没有只能看,因此在浏览器浏览API的时候就须要登陆 功能。在这里,强大的django-rest-framework又为咱们作了不少事情,想要在添加登陆按钮和页面,只须要修改一个rest_tutorial/urls.py,添加一个URL匹配:

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

 

这里的r'^api-auth/'你能够设置成任意你喜欢的,可是命名空间必定要相同,就是namespace='rest_framework'。

好了,如今打开浏览器,就能够看到在咱们的API页面的右上角有一个登陆的按钮,点击以后就可使用以前建立的用户登陆了。

这个时候访问单个用户的详情,就能够看到该用户建立的全部Snippet的id值(须要先建立好几个Snippet,能够按照本系列第一篇文章中在shell模式中的方法来建立)。好比访问:

http://127.0.0.1:8000/users/2/

 

能够看到: 
image


添加对象权限

接着咱们要实现的是让全部的Snippet能够被全部人访问到,可是每一个Snippet只有其建立者才能够对其进行更改、删除等操做。

所以,咱们须要设置一下自定义权限,使每一个Snippet只容许其建立者编辑它。在snippets目录下新建一个permissions.py:

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    使每一个Snippet只容许其建立者编辑它
    """

        def has_object_permission(self, request, view, obj):
        # 任何用户或者游客均可以访问任何Snippet,因此当请求动做在安全范围内,
        # 也就是GET,HEAD,OPTIONS请求时,都会被容许
        if request.method in permissions.SAFE_METHODS:
            return True

        # 而当请求不是上面的安全模式的话,那就须要判断一下当前的用户
        # 若是Snippet全部者和当前的用户一致,那就容许,不然返回错误信息
        return obj.owner == request.user

 

代码的逻辑已在注释中,简单说就是提供判断功能,而后咱们要把它运用起来,在view.py中的SnippetDetail 修改一下:

class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly,)

 

注意要导入IsOwnerOrReadOnly类:

from snippets.permissions import IsOwnerOrReadOnly

 

如今用浏览器打开单个Snippet详情页,若是你当前登陆的用户是这个Snippet的建立者,那你会发现多了DELETE和PUT两个操做,好比访问http://127.0.0.1:8000/snippets/2/,效果以下: 
image


使用API受权

因为如今咱们还没使用authentication 类,因此项目目前仍是使用默认的SessionAuthentication 和 BasicAuthentication.

在使用浏览器访问API的时候,浏览器会帮咱们保存会话信息,因此当权限知足时就能够对一个Snippet进行删除或者更改,或者是建立一个新的Snippet。

当若是是经过命令行来操做API,咱们就必须在每次发送请求的时候添加受权信息,也就是用户名和密码,没有的话就会报错,好比:

http POST http://127.0.0.1:8000/snippets/ code="print 123"

{
    "detail": "Authentication credentials were not provided."
}

 

正确的操做以下:

http -a username1:password POST http://127.0.0.1:8000/snippets/ code="print 789"

{
    "id": 1,
    "owner": "username1",
    "title": "",
    "code": "print 789",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}

 

咱们能够看出owner就是提交过来的用户名,这就是上面代码的功能体现:

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

 

经过实际使用更能理解程序,owner会在一个用户建立Snippet时获得该用户的信息就是这么来的。


关于认证和权限的部分就先到这了。咱们在上面写的代码中,highlight部分,咱们说它的功能是生成能让代码段高亮的HTML代码,这一部分尚未使用到,接下来就介绍它。

本文地址:http://www.cnblogs.com/zivwong/p/7456591.html
做者博客:ziv欢迎转载,请在明显位置给出出处及连接

相关文章
相关标签/搜索