在编程的世界中,咱们认为,用户输入的全部数据都是不可靠的,不合法的,直接使用用户输入的数据是不安全的,不只如此,咱们还须要控制用户的访问行为,接下来,咱们要学习认证组件、权限组件、频率组件。git
引入
同窗们,经过前面三节课的学习,咱们已经详细了解了DRF提供的几个重要的工具,DRF充分利用了面向对象编程的思路,对Django的View类进行了继承,并封装了其as_view方法和dispatch方法,随后提供了几个很是方便的编程工具,好比解析器、序列化。程序员
咱们经过解析器,能够对来自客户端的application/json数据进行解析,另外,经过序列化工具,咱们可以快速构建一套符合REST规范的api,随后又经过DRF的mixin、view及viewset对这些接口逻辑进行优化。github
有了他们,程序员开发Web应用的效率大大提升了,虽然咱们也尝试本身动手实现了这些功能,可是既然有了优秀的工具,咱们就没必要费尽心思重复发明轮子。DRF并不只仅提供了这几个工具,今天咱们就来继续深刻学习DRF提供的一些其余的工具。django
跟以往同样,咱们不只仅要学会这些工具的使用方式,而且要深刻研究他们的源码,但愿你们在研究源码的过程当中,可以对面向对象编程的思路有更加深入的认识。编程
固然,按照惯例,为了方便学习新知识以及阅读源码,咱们先来复习回顾一下以前已经学习过的知识。json
今日概要
- retrieve方法源码剖析
- 认证组件的使用方式及源码剖析
- 权限组件的使用方式及源码剖析
- 频率组件的使用方式及源码剖析
知识点复习回顾
知识点复习回顾一:Python逻辑运算
有了前两天的基础,今天看源码咱们就没有那么大的压力了,所要复习的知识也仅仅只有一个,那就是Python的逻辑运算,固然,稍后还会有几个简单的知识点,就不单独拿出来复习了。api
什么是逻辑运算呢?就是and、or、not。not为取反,比较简单,而and和or表示经过运算,计算表达式的布尔值,判断最终结果为真即止浏览器
- and:x and y 表示布尔与,意为,判断and运算以后的最终结果,为真即止,and运算必须表达式两端全部值均为真才能肯定最终结果,必须全部值都为真
- or:x and y 笔试布尔或,意为,判断or运算以后的最终结果,为真即止,or运算遇到真即返回,即有一个真值便可。
- not x:取反
看下面的代码吧:安全
1 2 3 4
|
x = 10 and 20 # x = 20 x = 0 and 20 # x = 0 x = 10 or 20 # x = 10 x = 0 or 20 # x = 20
|
好了,知识点复习就这么多。服务器
今日详细
mixin之retrieve源码剖析
上节课,咱们分析过mixin中create方法的源码,今天,create方法比较简单,今天,咱们来分析分析retrieve方法的源码,它比create方法稍微复杂一点点,复杂的地方在于如何获取须要操做的那条数据,由于咱们知道,咱们传递给不一样的视图类的全部方法都是同样的,惟一变化的两个是queryset和serializer_classes。
好了,废话很少说,下面来分析一下:
- Django程序启动,开始初始化,获取配置信息,获取视图类并加载到内存中,获取url及视图类的对应关系
- 开始绑定视图类和url的对应关系,执行as_view()方法
- as_view()方法被执行的时候传递了参数,为字典形式:{ “get”: “retrieve”, “delete”: “destroy”, “put”: “update” }
- 上一步中执行as_view()方法传递参数的目的是为了完成优化,将delete请求方式从新命名为不一样的函数
- ViewSetMixin类重写了as_view()方法,也就是在这个地方将几个函数从新绑定,它并无重写dispatch方法
- 该方法返回视图函数view,注意在这个函数中有一个行 self = cls(**initkwargs), cls是视图类,执行视图函数时self就指向视图函数的实例对象
- 等待客户端请求
- 请求到来,开始执行视图函数,注意,调用视图函数时的方式是view(request),而若是url带有参数,调用方式为view(request, xxx=id)的形式
- 显然,咱们有命名参数(?P\d+),因此此时的调用方式为view(request, pk=id)
- 视图函数中有一行self.kwargs = kwargs,因此pk已经被视图函数找到了
- 视图函数返回self.dispatch(),开始执行dispatch方法,注意self是视图类的实例化对象(每一个请求都被封装为一个对象)
- dispatch开始执行get方法,注意此时的get方法会执行retrieve,觉得已经被重定向了
- 开始执行retrieve,有一行instance = self.get_object(), 该方法在GenericAPIView中
- 相当重要的是拿到self.kwargs中的pk关键字,而后从queryset中拿到想要的数据
- 返回结果
从以上过程当中咱们能够看出,最关键的一步就是对kwargs的封装,这就是玄机所在,看到这里,你对面向对象有了什么新的领悟吗?对于反射呢,有了跟多的思考和理解吗?
若是没有,不用着急,任何质的飞跃都须要量的积累,等咱们写的多了,看得多了,天然就会突破瓶颈。
好了,同志们,这些内容算是对于视图组件的进一步挖掘和吸取,至此,视图组件咱们就差很少讲完了。接下来,咱们要学习其余工具了。
认证组件
好久好久之前,Web站点只是做为浏览服务器资源(数据)和其余资源的工具,甚少有什么用户交互之类的烦人的事情须要处理,因此,Web站点的开发这根本不关心什么人在何时访问了什么资源,不须要记录任何数据,有客户端请求,我即返回数据,简单方便,每个http请求都是新的,响应以后当即断开链接。
而现在,互联网的世界发生了翻天覆地的变化,用户不只仅须要跟其余用户沟通交流,还须要跟服务器交互,无论是论坛类、商城类、社交类、门户类仍是其余各种Web站点,你们都很是重视用户交互,只有跟用户交互了,才能进一步留住用户,只有留住了用户,才能知道用户需求,知道了用户需求,才会产生商机,有了用户,就等于有了流量,才可以骗到…额…抱歉…是融到钱,有了资金企业才能继续发展,可见,用户交互是很是重要的,甚至能够说是相当重要的一个基础功能。
而谈到用户交互,则必需要谈到咱们今天所要学习的知识点,认证、权限和频率。首先咱们来看看认证。
登陆成功后生成token
以前咱们学习过使用cookie和session两种方式能够保存用户信息,这两种方式不一样的是cookie保存在客户端浏览器中,而session保存在服务器中,他们各有优缺点,配合起来使用,可将重要的敏感的信息存储在session中,而在cookie中能够存储不太敏感的数据。
今天咱们要讲到的是使用token的方式,token称之为令牌。cookie、session和token都有其应用场景,没有谁好谁坏,不过咱们开发数据接口类的Web应用,目前用token仍是比较多的。
token认证的大体步骤是这样的:
- 用户登陆,服务器端获取用户名密码,查询用户表,若是存在该用户且第一次登陆(或者token过时),生成token,不然返回错误信息
- 若是不是第一次登陆,且token未过时,更新token值
接下来,咱们建立两个model,以下所示(token也能够存储在user表中,不过建议存储在user表中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
from django.db import models
# Create your models here.
class User(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=32) user_type_entry = ( (1, 'Delux'), (2, 'SVIP'), (3, "VVIP") ) user_type = models.IntegerField(choices=user_type_entry) address = models.CharField(max_length=32)
def __str__(self): return self.username
class UserToken(models.Model): user = models.OneToOneField("User", on_delete=models.CASCADE) token = models.CharField(max_length=128)
|
咱们无需实现get方法,由于涉及登陆认证,全部写post方法接口,登陆都是post请求,视图类以下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
from django.http import JsonResponse
from rest_framework.views import APIView
from .models import User, Book, UserToken from .utils import get_token
class UserView(APIView):
def post(self, request): response = dict() try: username = request.data['username'] password = request.data['password']
user_instance = User.objects.filter( user_name=username, password=password ).first()
if user_instance: access_token = get_token.generater_token()
UserToken.objects.update_or_create(user=user_instance, defaults={ "token": access_token }) response["status_code"] = 200 response["status_message"] = "登陆成功" response["access_token"] = access_token response["user_role"] = user_instance.get_user_type_display() else: response["status_code"] = 201 response["status_message"] = "登陆失败,用户名或密码错误" except Exception as e: response["status_code"] = 202 response["status_message"] = str(e)
return JsonResponse(response)
|
简单写了个获取随机字符串的方法用来生成token值:
1 2 3 4 5 6 7
|
# -*- coding: utf-8 -*- import uuid
def generater_token(): random_str = ''.join(str(uuid.uuid4()).split('-')) return random_str
|
以上就是token的简单生成方式,固然,在生产环境中不会如此简单,关于token也有相关的库,好了,咱们构造几条数据以后,能够经过POSTMAN工具来建立几个用户的token信息。
接下来,如何对已经登陆成功的用户实现访问受权呢?也就是说,只有登陆过的用户(有token值)才能访问特定的数据,该DRF的认证组件出场了。
DRF认证组件使用
首先,咱们来看一看,DRF认证组件的使用方式,首先,咱们必须新建一个认证类,以后的认证逻辑就包含在这个类里面:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
class UserAuth(object):
def authenticate_header(self, request): pass
def authenticate(self, request): user_post_token = request.query_params.get('token')
token_object = UserToken.objects.filter(token=user_post_token).first() if token_object: return token_object.user.username, token_object.token else: raise APIException("认证失败")
|
实现方式看上去很是简单,到token表里面查看token是否存在,而后根据这个信息,返回对应信息便可,而后,在须要认证经过才能访问的数据接口里面注册认证类便可:
1 2 3 4 5 6
|
class BookView(ModelViewSet):
authentication_classes = [UserAuth, UserAuth2]
queryset = Book.objects.all() serializer_class = BookSerializer
|
至于为何这么写,接下来,咱们一块儿分析源码,你们就都很是清楚了。
DRF认证源码剖析
前面的步骤都差很少,咱们来看有差异的地方,咱们说,request对象是APIView重写的,这个是在dispatch方法里面实现的,继续日后看dispatch方法,咱们会看到self.initial方法,就是在这个方法里面,咱们会看到认证、权限、频率几个组件的实现:
- 执行self.initial()方法
- 执行self.perform_authentication(request),方法,注意,新的request对象被传递进去了
- 该方法只有一行request.user,根据以前的经验,解析器(request.data),咱们知道这个user确定也是request对的一个属性方法
- 所料不错,该方法继续执行self._authenticate(),注意此时的self是request对象
- 该方法会循环self.authenticators,而这个变量是在从新实例化request对象时经过参数传递的
- 传递该参数是经过get_authenticatos()的返回值来肯定的,它的返回值是
- [ auth for auth in self.authentication_classes ]
- 也就是咱们的BookView里面定义的那个类变量,也就是认证类
- 一切都明朗了,循环取到认证类,实例化,而且执行它的authenticate方法
- 这就是为何认证类里面须要有该方法
- 若是没有该方法,认证的逻辑就没办法执行
- 至于类里面的header方法,照着写就行,有兴趣的能够研究源码,这里就不细究了
- 该方法若是执行成功就返回一个元组,执行完毕
- 若是失败,它会捕捉一个APIException
- 若是咱们不但愿认证经过,能够raise一个APIException
这就是认证组件的实现方式,很是简单。
多个认证类的实现
而且,咱们还能够指定多个认证类,只是须要注意的是,若是须要返回什么数据,请在最后一个认证类中返回,由于若是在前面返回,在self._authentication()方法中会对返回值进行判断,若是不为空,认证的过程就会停止,多个认证类的实现方式以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
class UserAuth2(object):
def authenticate(self, request): raise APIException("认证失败")
class UserAuth(object):
def authenticate_header(self, request): pass
def authenticate(self, request): user_post_token = request.query_params.get('token')
token_object = UserToken.objects.filter(token=user_post_token).first() if token_object: return token_object.user.username, token_object.token else: raise APIException("认证失败")
class BookView(ModelViewSet):
authentication_classes = [UserAuth, UserAuth2]
|
若是不但愿每次都写那个无用的authenticate_header方法,咱们能够这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
from rest_framework.authentication import BaseAuthentication
class UserAuth2(BaseAuthentication):
def authenticate(self, request): raise APIException("认证失败")
class UserAuth(BaseAuthentication):
def authenticate(self, request): user_post_token = request.query_params.get('token')
token_object = UserToken.objects.filter(token=user_post_token).first() if token_object: return token_object.user.user_name, token_object.token else: raise APIException("认证失败")
|
继承BaseAuthentication类便可。
全局认证
若是但愿全部的数据接口都须要认证怎么办?很简单,仍是根据以前的经验,就是这句代码:
1
|
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
若是认证类本身没有authentication_classes,就会到settings中去找,经过这个机制,咱们能够将认证类写入到settings文件中便可实现全局认证:
1 2 3 4 5 6
|
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'authenticator.utils.authentication.UserAuth', 'authenticator.utils.authentication.UserAuth2', ), }
|
好了,认证到这里就差很少了。接下来继续介绍权限组件
权限组件
与认证组件几乎差很少,咱们直接看使用方式吧
权限组件使用
定义权限类:
1 2 3 4 5 6 7
|
class UserPerms(): message = "您没有权限访问该数据" def has_permission(self, request, view): if request.user.user_type > 2: return True else: return False
|
一样的逻辑,一样的方式,只是执行权限的方法名与执行认证的方法名不同而已,名为has_permission,而且须要将当前的视图类传递给该方法。
视图类中加入permission_classes变量:
1 2 3 4 5 6 7
|
class BookView(ModelViewSet):
authentication_classes = [UserAuth] permission_classes = [UserPerms2]
queryset = Book.objects.all() serializer_class = BookSerializer
|
权限组件源码剖析
权限组件的源码与认证组件是同样的。
频率组件
使用自定义方式实现对ip地址进行访问频率控制
使用方式介绍,上面两个组件也是几乎同样,只是用来作判断的逻辑不同而已,下面是做业的答案:
throttles.py(该方式没有DRF提供的方式简洁)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
import time import math
from rest_framework import exceptions
class MyException(exceptions.Throttled): default_detail = '链接次数过多' extra_detail_plural = extra_detail_singular = '请在{wait}秒内访问'
def __init__(self, wait=None, detail=None, code=None): super().__init__(wait=wait, detail=detail, code=code)
class VisitThrottle(): user_visit_information = dict() visited_times = 1 period = 60 allow_times_per_minute = 5 first_time_visit = True
def allow_request(self, request, view): self.request_host = request_host = request.META.get("REMOTE_ADDR") current_user_info = self.user_visit_information.get(request_host, None)
if not self.__class__.first_time_visit: self.user_visit_information[request_host][0] += 1 current_visit_times = self.user_visit_information[request_host][0]
if current_visit_times > self.allow_times_per_minute: if self._current_time - current_user_info[1] <= self.period: if len(current_user_info) > 2: current_user_info[2] = self._time_left else: current_user_info.append(self._time_left)
view.throttled = self.throttled return None else: self.__class__.first_time_visit = True
if self.first_time_visit: self.__class__.first_time_visit = False self._initial_infomation()
return True
def wait(self): return self.period - self.user_visit_information[self.request_host][2]
def throttled(self, request, wait): raise MyException(wait=wait)
@property def _current_time(self): return time.time()
@property def _time_left(self): return math.floor(self._current_time - self.user_visit_information.get(self.request_host)[1])
def _initial_infomation(self): self.user_visit_information[self.request_host] = [self.visited_times, self._current_time]
|
视图类中:
1 2 3 4
|
class BookView(ModelViewSet): throttle_classes = [ VisitThrottle ] queryset = Book.objects.all() serializer_class = BookSerializer
|
使用DRF简单频率控制实现对用户进行访问频率控制
局部访问频率控制
1 2 3 4 5 6 7 8
|
from rest_framework.throttling import SimpleRateThrottle
class RateThrottle(SimpleRateThrottle): rate = '5/m'
def get_cache_key(self, request, view): return self.get_ident(request)
|
rate表明访问评率,上面表示每分钟五次,get_cache_key是必须存在的,它的返回值告诉当前频率控制组件要使用什么方式区分访问者(好比ip地址)。
以后在视图中使用便可:
1 2 3 4 5 6 7 8 9
|
from .utils.throttles import RateThrottle
# Create your views here.
class BookView(ModelViewSet): throttle_classes = [ RateThrottle ] queryset = Book.objects.all() serializer_class = BookSerializer
|
全局访问频率控制
首先定义一个频率控制类,而且必须继承SimpleRateThrottle这个类,它是DRF提供的一个方便的频率控制类,请看下面的代码:
1 2 3 4 5
|
class RateThrottle(SimpleRateThrottle): scope = "visit_rate"
def get_cache_key(self, request, view): return self.get_ident(request)
|
另外,咱们须要在全局配置频率控制参数
1 2 3 4 5 6
|
REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": ('throttler.utils.throttles.RateThrottle',), "DEFAULT_THROTTLE_RATES": { "visit_rate": "5/m" } }
|
这样就实现了,每分钟最多五次访问的逻辑。
今日总结
- retrieve方法源码剖析
- 认证组件的使用方式及源码剖析
- 权限组件的使用方式及源码剖析
- 频率组件的使用方式及源码剖析
转自:pizzali