day91 DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件

DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件

 

 

本节目录html

一 认证组件

  1. 局部认证组件前端

    咱们知道,咱们无论路由怎么写的,对应的视图类怎么写的,都会走到dispatch方法,进行分发,vue

    在我们看的APIView类中的dispatch方法的源码中,有个self.initial(request, *args, **kwargs),那么认证、权限、频率这三个默认组件都在这个方法里面了,若是咱们本身没有作这三个组件的配置,那么会使用源码中默认的一些配置。进源码去看看你就会看到下面三个东西:数据库

复制代码
# Ensure that the incoming request is permitted
#实现认证
self.perform_authentication(request)
#权限判断
self.check_permissions(request)
#控制访问频率
elf.check_throttles(request)
复制代码

    目前为止你们知道的认证机制是否是有cookie、session啊,session更安全一些,可是你会发现session的信息都存到我们的服务器上了,若是用户量很大的话,服务器压力是比较大的,而且django的session存到了django_session表中,不是很好操做,可是通常的场景都是没有啥问题的,如今生产中使用的一个叫作token机制的方式比较多,如今咱们是否是就知道个csrf_token啊,其实token有不少种写法,如何加密,你是hashlib啊仍是base64啊仍是hmac啊等,是否是加上过时时间啊,是否是要加上一个secret_key(客户端与服务端协商好的一个字符串,做为双方的认证依据),是否是要持续刷新啊(有效时间要短,不断的更新token,若是在这么短的时间内仍是被别人拿走了token,模拟了用户状态,那这个基本是没有办法的,可是你能够在网络或者网络设备中加安全,存客户的ip地址等,防黑客)等等。django

    大体流程图解:json

      

    

    首先咱们须要建立一个表,用户表,里面放一个token字段,其实通常我都是放到两个表里面,和用户表是一个一对一关系的表,看代码:后端

复制代码
################################# user表 ###############################
class User(models.Model):
    user = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    type_choice=((1,"VIP"),(2,"SVIP"),(3,"SSVIP"))
    user_type = models.IntegerField(choices=type_choice)

class UserToken(models.Model):
    user = models.OneToOneField(to=User) #一对一到用户表
    token = models.CharField(max_length=128) #设置的长度大一些
    # expire_time = models.DateTimeField() #若是作超时时间限制,能够在这里加个字段来搞,这里我没有写昂,简单搞了
复制代码

    urls.py内容以下:api

#登录认证接口
url(r'^login/$', views.LoginView.as_view(),), #别忘了$符号结尾

     views.py内容以下:本身写一个每次登录成功以后刷新token值浏览器

复制代码
###################login逻辑接口#######################
#关于逻辑接口而不是提供数据的接口,咱们不用ModelViewSet,而是直接写个类,继承APIView,而后在类里面直接写咱的逻辑
import uuid
import os
import json
class LoginView(APIView):
    #从先后端分离的项目来说,get请求不须要写,由于get就是个要登录页面的操做,vue就搞定了,因此咱们这里直接写post请求就能够了
    def post(self,request):
        # 通常,请求过来以后,咱们后端作出的响应,都是个字典,不只包含错误信息,还有要状态码等,让客户端明白到底发生了什么事情
        # 'code'的值,1表示成功,0表示失败,2表示其余错误(本身能够作更细致的错误代码昂)
        res = {'code': 1, 'msg': None, 'user': None,'token':None}
        print(request.data)
        try:
            user = request.data.get('user')
            pwd = request.data.get('pwd')
            # 数据库中查询
            user_obj = models.User.objects.filter(user=user, pwd=pwd).first()
            if user_obj:
                res['user'] = user_obj.user
                # 添加token,用到我们usertoken表
                # models.UserToken.objects.create(user=user,token='123456')
                # 建立token随机字符串,我写了两个方式,简写的昂,最好再加密一下
                random_str = uuid.uuid4()
                # random_str = os.urandom(16) bytes类型的16位的随机字符串
                models.UserToken.objects.update_or_create(
                    user=user_obj,  # 查找筛选条件
                    defaults={  # 添加或者更新的数据
                        "token": random_str,
                    }
                )
                res['token'] = random_str
                res['msg'] = '登录成功'
            else:
                res['code'] = 0
                res['msg'] = '用户名或者密码错误'
                return Response(res)
        except Exception as e:
            res['code'] = 2
            res['msg'] = str(e)

        return Response(res)
复制代码

    经过上面的代码咱们将token返回给了用户,那么之后用户无论发送什么请求,都要带着我给它的token值来访问,认证token经过才行,而且更新token。安全

    下面咱们玩一下drf提供的认证组件的玩法。

  DRF的认证组件

    未来有些数据接口是必需要求用户登录以后才能获取到数据,因此未来用户登录完成以后,每次再过来请求,都要带着token来,做为身份认证的依据。

复制代码
from app01.serializer import BookSerializers

#####################Book表操做##########################

class UserAuth():
   def authenticate_header(self,request):
         pass
#authenticate方法固定的,而且必须有个参数,这个参数是新的request对象,不信,看源码
    def authenticate(self,request):

        if 1:
        #源码中会发现,这个方法会有两个返回值,而且这两个返回值封装到了新的request对象中了,request.user-->用户名 和 request.auth-->token值,这两个值做为认证结束后的返回结果
            return "chao","asdfasdfasdf"

class BookView(APIView):
    #认证组件确定是在get、post等方法执行以前执行的,还记得源码的地方吗,这个组件是在dispatch的地方调用的,咱们是上面写个UserAuth类
    authentication_classes = [UserAuth,] #认证类能够写多个,一个一个的顺序验证
    def get(self,request):
        '''
        查看全部书籍
        :param request:
        :return:
        '''
        #这样就拿到了上面UserAuth类的authenticate方法的两个返回值
        print(request.user)  
        print(request.auth)
        book_obj_list = models.Book.objects.all()
        s_books = BookSerializers(book_obj_list,many=True)
        return Response(s_books.data)
复制代码

    

复制代码
    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self) 
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                #值得注意的是,self是APIView封装的新的request对象
                self.user, self.auth = user_auth_tuple 
                return   #退出了这个函数,函数就不会执行了,不会再循环了,因此若是你的第一个认证类有返回值,那么第二个认证类就不会执行了,因此别忘了return是结束函数的意思,因此若是你有多个认证类,那么返回值放到最后一个类里面
复制代码

 

  好,咱们写一写获取token值,而后校验的功能,看views.py的代码:

复制代码
from django.shortcuts import render,HttpResponse,redirect
from django.views import View
from rest_framework import serializers
from app01 import models
from rest_framework.views import APIView
from rest_framework.response import Response
#将序列化组件都放到一个单独的文件里面,而后引入进来
from app01.serializer import BookSerializers,PublishSerializers,AuthorSerializers
#drf提供的认证失败的异常
from rest_framework.exceptions import AuthenticationFailed

class UserAuth():
    #每一个认证类,都须要有个authenticate_header方法,而且有个参数request
    def authenticate_header(self,request):
        pass
    #authenticate方法固定的,而且必须有个参数,这个参数是新的request对象,不信,看源码
    def authenticate(self,request):
        # token = request._request.GET.get("token")
        #因为咱们这个request是新的request对象,而且老的request对象被封装到了新的request对象中,名字是self._request,因此上面的取值方式是没有问题的,不过人家APIView不只封装了老的request对象,而且还给你加了query_params属性,和老的request.GET获得的内容是同样的,因此能够直接按照下面的方式来写
        token = request.query_params.get("token")
        #用户请求来了以后,咱们获取token值,到数据库中验证
        usertoken = models.UserToken.objects.filter(token=token).first()
        if usertoken:
            #验证成功以后,能够返回两个值,也能够什么都不返回
            return usertoken.user.user,usertoken.token

        #源码中会发现,这个方法会有两个返回值,而且这两个返回值封装到了新的request对象中了,request.user-->用户名 和 request.auth-->token值,这两个值做为认证结束后的返回结果
        else:
            #由于源码内部进行了异常捕获,而且给你主动返回一个forbiden错误,因此咱们在这里主动抛出异常就能够了
            raise AuthenticationFailed("认证失败")
复制代码

    urls.py内容以下:

url(r'^books/$', views.BookView.as_view(),),

    经过postman发送请求,你会发现错误:

      

    若是咱们请求中带了数据库中保存的token值,那么就会成功获取数据,看数据库中的token值:

      

    而后经过postman再请求,带着token值,看效果,成功了:

      

    

    继承drf的BaseAuthentication认证类的写法:

复制代码
from app01 import models
from rest_framework.views import APIView
from rest_framework.response import Response
#将序列化组件都放到一个单独的文件里面,而后引入进来
from app01.serializer import BookSerializers,PublishSerializers,AuthorSerializers
#drf提供的认证失败的异常
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
#继承drf的BaseAuthentication类
class UserAuth(BaseAuthentication):
    # 继承了BaseAuthentication类以后,这个方法就不用写了
    # def authenticate_header(self,request):
    #     pass
    def authenticate(self,request):
        # token = request._request.GET.get("token")
        token = request.query_params.get("token")
        #有request对象,那么不只仅能够认证token,还能够认证请求里面的其余内容
        usertoken = models.UserToken.objects.filter(token=token).first()
        if usertoken:
            #验证成功以后
            return usertoken.user.user,usertoken.token
        else:
            raise AuthenticationFailed("认证失败")

class BookView(APIView):
    #经过源码看,认证类的查找过程,和解析组件的查找过程是同样的
    authentication_classes = [UserAuth,]
    def get(self,request):
        '''
        查看全部书籍
        :param request:
        :return:
        '''
        print(request.user)
        print(request.auth)
        book_obj_list = models.Book.objects.all()
        s_books = BookSerializers(book_obj_list,many=True)
        return Response(s_books.data)
复制代码

    带时间戳的随机字符串:

复制代码
def get_random_str(user):
    import hashlib,time
    ctime=str(time.time())

    md5=hashlib.md5(bytes(user,encoding="utf8"))
    md5.update(bytes(ctime,encoding="utf8"))

    return md5.hexdigest()
复制代码

 

  全局视图认证组件:

    在settings.py文件中配置:若是我再app01文件夹下的service文件夹下的auth文件夹下写了咱们本身的认证类,那么全局配置的写法就按照下面的方式写。

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",]  #里面是路径字符串
}

   认证组件就说这些,看权限组件吧。

 

二 权限组件

  局部视图权限:

    在app01.service.permissions.py中

复制代码
from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
    message="SVIP才能访问!" #变量只能叫作message
    def has_permission(self, request, view):  #重写has_permission方法,本身写权限逻辑,看看源码就明白了,这个view是咱当前类的实例化对象,通常用不到,可是必须给个参数写在这里。
        if request.user.user_type==3:
            return True  #经过权限
        return False     #没有经过
复制代码

    在views.py:

from app01.service.permissions import *

class BookViewSet(generics.ListCreateAPIView):
    permission_classes = [SVIPPermission,]
    queryset = Book.objects.all()
    serializer_class = BookSerializers

  全局视图权限:

    settings.py配置以下:

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",]
}

 

 

三 频率组件

局部视图throttle,反爬,防攻击

在throttles.py中:

复制代码
from rest_framework.throttling import BaseThrottle,SimpleRateThrottle
import time
from rest_framework import exceptions
visit_record = {}
class VisitThrottle(BaseThrottle):
    # 限制访问时间
    VISIT_TIME = 10
    VISIT_COUNT = 3

    # 定义方法 方法名和参数不能变
    def allow_request(self, request, view):
        # 获取登陆主机的id
        id = request.META.get('REMOTE_ADDR')
        self.now = time.time()

        if id not in visit_record:
            visit_record[id] = []

        self.history = visit_record[id]
        # 限制访问时间
        while self.history and self.now - self.history[-1] > self.VISIT_TIME:
            self.history.pop()
        # 此时 history中只保存了最近10秒钟的访问记录
        if len(self.history) >= self.VISIT_COUNT:
            return False
        else:
            self.history.insert(0, self.now)
            return True

    def wait(self):
        return self.history[-1] + self.VISIT_TIME - self.now
复制代码

 

在views.py中:

from app01.service.throttles import *

class BookViewSet(generics.ListCreateAPIView):
    throttle_classes = [VisitThrottle,]
    queryset = Book.objects.all()
    serializer_class = BookSerializers

全局视图throttle

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
    "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",]
}

 

内置throttle类

在throttles.py修改成: 

class VisitThrottle(SimpleRateThrottle):

    scope="visit_rate"
    def get_cache_key(self, request, view):

        return self.get_ident(request)

  settings.py设置:

复制代码
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
    "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",],
    "DEFAULT_THROTTLE_RATES":{
        "visit_rate":"5/m",
    }
}
复制代码

 

  

四 url注册器

帮咱们自动生成4个url,和咱们本身写的差很少:

复制代码
from django.conf.urls import url,include
from django.contrib import admin
from app01 import views
from rest_framework import routers

router = routers.DefaultRouter()
#自动帮咱们生成四个url
router.register(r'authors', views.AuthorView)
router.register(r'books', views.BookView)


urlpatterns = [

    # url(r'^books/$', views.BookView.as_view(),), #别忘了$符号结尾
    # url(r'api/', include(router.urls)),
    url(r'', include(router.urls)),   #http://127.0.0.1:8000/books/  也能够这样写:http://1270.0..1:8000/books.json/
   
    #登录认证接口
    url(r'^login/$', views.LoginView.as_view(),), #别忘了$符号结尾

]
复制代码

 

可是有个前提就是,咱们用的是:ModelViewSet序列化组件。

复制代码
from rest_framework.viewsets import ModelViewSet
class AuthorView(ModelViewSet):
    queryset = models.Author.objects.all() 
    serializer_class = AuthorSerializers

class BookView(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = BookSerializers
复制代码

 

 因此,这个url注册器其实并无那么好用,固然啦,看需求.

 

五 响应器

 简单看看就行啦:

复制代码
from rest_framework.viewsets import ModelViewSet
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
#若是咱们没有在本身的视图类里面配置,那么源码里面默认就用的这两个JSONRenderer,BrowsableAPIRenderer
#BrowsableAPIRenderer 是当客户端为浏览器的时候,回复的数据会自动给你生成一个页面形式的数据展现,通常开发的时候,都不用页面形式的
#JSONRenderer:回复的是json数据

class BookView(ModelViewSet):
    # renderer_classes = [JSONRenderer,] #其实默认就是这个JSONRenderer,因此通常不用在这里配置了
    queryset = models.Book.objects.all()
    serializer_class = BookSerializers
复制代码

 

 

六 分页器组件

简单使用:

复制代码
#引入分页
from rest_framework.pagination import PageNumberPagination
class BookView(APIView):
    # 经过源码看,认证类的查找过程,和解析组件的查找过程是同样的
    # authentication_classes = [UserAuth,]
    # throttle_classes = [VisitThrottle,]

    def get(self,request):
        '''
        查看全部书籍
        :param request:
        :return:
        '''
        
        book_obj_list = models.Book.objects.all()
        #建立分页器对象,PageNumberPagination类中除了PAGE_SIZE属性以外,还有个page属性,这个page属性是第几页,用法是http://127.0.0.1:8000/books/?page=1
        pnb = PageNumberPagination()
        #经过分页器对象的paginate_queryset方法进行分页,
        paged_book_list = pnb.paginate_queryset(book_obj_list,request,)
        #将分页的数据进行序列化
        s_books = BookSerializers(paged_book_list,many=True)
        return Response(s_books.data)
复制代码

 settings配置文件:

复制代码
REST_FRAMEWORK={

    # "DEFAULT_THROTTLE_RATES":{
    #     "visit_rate":"5/m",
    # },
    'PAGE_SIZE':5, #这是全局的一个每页展现多少条的配置,可是通常不用它,由于不用的数据展现可能每页展现的数量是不一样的
}
复制代码

 若是咱们不想用全局的page_size配置,咱们本身能够写个类来继承分页类组件,重写里面的属性:

复制代码
#引入分页
from rest_framework.pagination import PageNumberPagination
class MyPagination(PageNumberPagination):
    page_size = 3 #每页数据显示条数
    page_query_param = 'pp'  #http://127.0.0.1:8000/books/?pp=1,查询哪一页的数据
    page_size_query_param = 'size' #若是咱们显示的一页数据不够你用的,你想临时的多看展现一些数据,能够经过你设置的这个page_size_query_param做为参数来访问:http://127.0.0.1:8000/books/?pp=2&size=5 #那么你看到的虽然是第二页,可是能够看到5条数据,意思是将page_size的数字临时扩大了,每页展现的数据就多了或者少了,看你的page_size_query_param设置的值
    max_page_size = 10 #最大每页展现多少条,即使是你前端经过page_size_query_param临时调整了page_size的值,可是最大也不能超过咱们设置的max_page_size的值

class BookView(APIView):
    def get(self,request):
        '''
        查看全部书籍
        :param request:
        :return:
        '''
        book_obj_list = models.Book.objects.all()
        pnb = MyPagination()
        paged_book_list = pnb.paginate_queryset(book_obj_list,request,)
        s_books = BookSerializers(paged_book_list,many=True)
        return Response(s_books.data)
复制代码

 

还有咱们玩的继承了ModelViewSet类的试图类使用分页器的写法:

复制代码
from rest_framework.viewsets import ModelViewSet

from rest_framework.pagination import PageNumberPagination
class MyPagination(PageNumberPagination):
    page_size = 3
    page_query_param = 'pp'
    page_size_query_param = 'size'
    max_page_size = 10

class BookView(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = BookSerializers
    pagination_class = MyPagination #配置咱们本身写的分页类
复制代码

 

还有个偏移分页,了解一下就好了

from rest_framework.pagination import LimitOffsetPagination

 

 

 

 

七 xxx

 

 

 

 

八 xxx

相关文章
相关标签/搜索