drf之认证、权限、限流、过滤

目录

drf之认证、权限、限流、过滤

一 drf认证Authentication

1 drf认证功能介绍

0) 认证,频率,权限
1) 用户是否登录到系统中
2) 后期基本上会用JWT的认证
3) 自定制的认证

2 认证功能源码分析

2.1 源码分析

1) APIView---》dispatch---》self.initial(request, *args, **kwargs)--》self.perform_authentication(request)---》Request.user--->self._authenticate(self):Request类的方法---》self.authenticators:Request类的属性---》在Request对象实例化的时候传入的----》Request在什么时候实例化的?dispatch的时候---》APIView:self.get_authenticators()--》return [auth() for auth in self.authentication_classes]----》如果在自己定义的视图类中写了authentication_classes=[类1,类2]----》Request的self.authenticators就变成了我们配置的一个个类的对象

# # 源码分析详解
# a) APIView---》
    @classmethod
    def as_view(cls, **initkwargs): 
        view = super().as_view(**initkwargs)

	# 调用view下的as_view()
    @classonlymethod
    def as_view(cls, **initkwargs):        
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)

# b) dispatch---》  # 调用了dispatch,APIView中有此方法,故是APIView中的dispatch方法
    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling.        """
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request   # 对request进行了属性的赋值

        try:
            self.initial(request, *args, **kwargs)   #  运行位置

# c) self.initial(request, *args, **kwargs)--》
    def initial(self, request, *args, **kwargs):        
        self.perform_authentication(request)  # 运行到此处
        self.check_permissions(request)
        self.check_throttles(request)

# d) self.perform_authentication(request)---》  # 运行
    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request. Note that if you override this and simply 'pass', then authentication will instead be performed lazily, the first time either
 `request.user` or `request.auth` is accessed.        """
        # 不是原生django的user,是新的Request对象的方法
        request.user

# e) Request.user--->  # Request下的user方法
	@property
    def user(self):
        """  Returns the user associated with the current request, as authenticated by the authentication classes provided to the request.   """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user

# f) self._authenticate(self):Request类的方法---》
    def _authenticate(self):
        """ Attempt to authenticate the request using each authentication instance in turn. """
        for authenticator in self.authenticators:  # 是Request类的属性
            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.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()

# g) self.authenticators:  Request类的属性---》  h) 在Request对象实例化的时候传入的----》
    def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )
        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()

# i) Request在什么时候实例化的?  dispatch开始的时候的时候---》
    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling.        """
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)  # request在APIView的dispatch这里实例化的

# j) APIView:self.get_authenticators()--》
    def initialize_request(self, request, *args, **kwargs):
        """       Returns the initial request object.        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

# k) return [auth() for auth in self.authentication_classes]----》
    def get_authenticators(self):
        """        Instantiates and returns the list of authenticators that this view can use.        """
        return [auth() for auth in self.authentication_classes]  # 一个列表推导式

# l) 如果在自己定义的视图类中写了authentication_classes=[类1,类2]----》
class APIView(View):
    # The following policies may be set at either globally, or per-view.
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

# m) Request的self.authenticators就变成了我们配置的一个个类的对象

2.2 Request类的: self._authenticate(self) 方法

2) self._authenticate(self):Request类的方法
def _authenticate(self):
     for authenticator in self.authenticators: # BookView中配置的一个个类的对象
            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.user, self.auth = user_auth_tuple
                return

3) 只要在视图类中配置authentication_classes = [MyAuthen.LoginAuth, ]
	就会执行上面的方法,执行认证

3 自定义认证类(重点)

from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import SessionAuthentication, BasicAuthentication

# 1 使用(GET方法)
1)-定义一个类,继承BaseAuthentication
class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.GET.get('token')
        res = models.UserToken.objects.filter(token=token).first()
        if res:
            return 元组
        else:
            raise AuthenticationFailed('您没有登录')

2)-重写authenticate方法

3)-局部使用和全局使用
	-局部:在视图类中配置(只要配置了,就是登录以后才能访问,没配置,不用登录就能访问)
		authentication_classes = [MyAuthen.LoginAuth, ]
	-全局
        REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.MyAuthen.LoginAuth", ]
        }
        
4)-注意:
	1) 认证类,认证通过可以返回一个元组,有两个值,第一个值会给,request.user,第二个值会个request.auth
    2) 认证类可以配置多个,按照从前向后的顺序执行,如果前面有返回值,认证就不再继续往下走了

4 认证功能局部使用和全局使用

1) 全局使用(所有接口,都需要登录才能访问)
	-在配置文件中
        REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.MyAuthen.LoginAuth", ]
        }

2) 局部使用
	-在想局部使用的视图类上
	authentication_classes = [MyAuthen.LoginAuth,]

3) 局部禁用
	-在想禁用的视图类上
    authentication_classes = []

5 内置认证方案(需要配合权限使用)

5.1 全局使用

# 可以在配置文件中配置全局默认的认证方案
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',  # session认证
        'rest_framework.authentication.BasicAuthentication',   # 基本认证
    )
}

5.2 局部使用

# 也可以在每个视图中通过设置authentication_classess属性来设置
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.views import APIView

class ExampleView(APIView):
    # 类属性
    authentication_classes = [SessionAuthentication, BasicAuthentication]
    ...

5.3 认证失败的返回值

# 认证失败会有两种可能的返回值:
-401 Unauthorized       # 未认证
-403 Permission Denied  # 权限被禁止

二 def权限Permissions

# 权限控制可以限制用户对于视图的访问和对于具体数据对象的访问。
    -在执行视图的dispatch()方法前,会先进行视图访问权限的判断
    -在通过get_object()获取具体对象时,会进行模型对象访问权限的判断

1 自定义权限功能(重点)

1) 登录成功以后,超级用户可以干某些事,普通用户不能干---》超级用户可以查看某些接口,普通用户不能查看

2) 使用:写一个类继承BasePermission,重写has_permission
    class SuperPermission(BasePermission):
        def has_permission(self, request, view):
            # Return `True` if permission is granted, `False` otherwise.
            # 超级用户可以访问,除了超级用户以外,都不能访问
            if request.user.user_type == '1':
                return True
            else:
                return False
       
3) 局部使用和全局使用
	-在想局部使用的视图类上
	permission_classes = [MyAuthen.SuperPermission]
    -全局使用
      REST_FRAMEWORK = {
        "DEFAULT_PERMISSION_CLASSES": ["app01.MyAuthen.SuperPermission", ]
        }
     -局部禁用
    permission_classes = []

2 权限功能局部使用和全局使用

# 1 使用方式
1)-在想局部使用的视图类上
	permission_classes = [MyAuthen.SuperPermission]

2)-全局使用
	REST_FRAMEWORK = {
        "DEFAULT_PERMISSION_CLASSES": ["app01.MyAuthen.SuperPermission", ]
        }

3)-局部禁用
    permission_classes = []

3 内置的权限类

# 内置权限类
from rest_framework.permissions import AllowAny,IsAuthenticated,IsAdminUser,IsAuthenticatedOrReadOnly
    - AllowAny          # 允许所有用户
    - IsAuthenticated   # 仅通过认证的用户
    - IsAdminUser       # 仅管理员用户
    - IsAuthenticatedOrReadOnly  # 已经登陆认证的用户可以对数据进行增删改操作,没有登陆认证的只能查看数据。

4 权限源码分析

4.1 源码分析

# 1 源码分析
	-APIView的dispatch---》APIView的initial---》APIView的check_permissions(request)
        for permission in self.get_permissions(): # 权限类对象放到列表中
        if not permission.has_permission(request, self):
            self.permission_denied(
                 request,
                 message=getattr(permission, 'message', None),
                 code=getattr(permission, 'code', None)
                )

4.2 错误信息的中文显示

# 2 错误信息的中文显示
	在权限类中加一个 message=字符串  ————》message=getattr(permission, 'message', None),

5 模型层choice字段使用(重点)

1) 模型表:Student表,写接口应该选择继承哪个视图类
2) 推荐使用自动生成路由的方式(继承ViewSetMixin及它的字类)
3) 但是目前来说,你先实现功能即可(至于选择哪个,慢慢体会)

4) choice的使用
	-在模型类中使用
    sex = models.SmallIntegerField(choices=((1, '男'), (2, '女'), (3, '未知')), default=1)
    -在视图类中,在序列化类中
    	-get_字段名_dispaly()的方法,该方法获得choice字段对应的数据

三 drf限流Throttling

1 自定义频率类(分析,了解)

1.1 自定义频率类用途及使用

1) 限制某个人,某个ip的访问频次

2) 自定义频率类及使用
from rest_framework.throttling import BaseThrottle
class MyThrottle(BaseThrottle):
    VISIT_RECORD = {}  # 存用户访问信息的大字典
    def __init__(self):
        self.history = None
    def allow_request(self,request,view):
        # 根据ip进行频率限制,每分钟只能访问3次
        # 限制的逻辑
        '''
        #(1)取出访问者ip
        #(2)判断当前ip不在访问字典里,添加进去,并且直接返回True, 表示第一次访问,在字典里,继续往下走
        #(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        #(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        #(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        '''
        # (1)取出访问者ip
        # print(request.META)
        ip = request.META.get('REMOTE_ADDR')
        import time
        ctime = time.time()
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
        if ip not in self.VISIT_RECORD:
            self.VISIT_RECORD[ip] = [ctime, ]
            return True
        self.history = self.VISIT_RECORD.get(ip)
        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        while self.history and ctime - self.history[-1] > 60:
            self.history.pop()
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        if len(self.history) < 3:
            self.history.insert(0, ctime)
            return True
        else:
            return False

    def wait(self):
        # 还剩多长时间能访问
        import time
        ctime = time.time()
        return 60 - (ctime - self.history[-1])   

1.2 局部使用与全局使用

1)-局部使用
	throttle_classes = [auth.MyThrottle,]

2)-全局使用
	REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES':['app01.auth.MyThrottle',],
}

2 内置频率类使用

2.1 内置频率类

-BaseThrottle:      # 基类
-AnonRateThrottle:  # 限制匿名用户的访问次数
-SimpleRateThrottle:# 我们自定义扩写它
-ScopedRateThrottle:
-UserRateThrottle:  # 限制登录用户访问次数

2.2 扩展内置频率类(重点记住)

# 扩展内置频率类(重点记住)
1)-写一个类,继承SimpleRateThrottle
    class MySimpleThrottle(SimpleRateThrottle):
        scope = 'xxx'
        def get_cache_key(self, request, view):
            #以ip限制
            return self.get_ident(request)

2)-setting.py中配置
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_RATES' : {
            'xxx':'10/m'  # key跟scope对应,value是一个时间
        }
    }

3)-局部使用与全局使用

2.3 源码分析

# 源码分析
-继承SimpleRateThrottle---》allow_request(跟咱们写的一样)

2.4 其它内置频率类

# 其它内置频率类
1)-限制未登录用户的频率(AnonRateThrottle)(根据ip限制)
-使用:
	-局部使用,全局使用
	-setting.py中配置
	'DEFAULT_THROTTLE_RATES' : {
    'anon':'1/m'
	}

2)-限制登录用户访问次数 UserRateThrottle(根据用户id限制)
-使用:
    -局部使用,全局使用
    -setting.py中配置
    'DEFAULT_THROTTLE_RATES' : {
        'user':'1/m'
    }

3)-ScopedRateThrottle(有兴趣看一下,没有就不看了) 

四 过滤Filter与排序

1 内置,第三方过滤功能(次重点)

1) 过滤:筛选查询结果

2) 内置筛选的使用
	-在视图类中配置
        filter_backends = [SearchFilter,]
        search_fields = ('name',) # 表模型中的字段
    -查询的时候
    	http://127.0.0.1:8000/students/?search=e
                
3) 第三方扩展的过滤功能
	-pip3 install django-filter  :最新版本(2.4.0)要跟django2.2以上搭配
    
    -在视图类中配置
        filter_backends = [DjangoFilterBackend,]
    	filter_fields = ['name','age']
    -查询的时候
    	http://127.0.0.1:8000/students/?name=lqz&age=18

2 排序功能(次重点)

-在视图类中配置
    filter_backends = [OrderingFilter,]
    ordering_fields = ['id','age']

-查询的时候
	http://127.0.0.1:8000/students/?ordering=-age
            
            
 ### 过滤后再排序
  -在视图类中配置
    filter_backends = [OrderingFilter,DjangoFilterBackend]
    ordering_fields = ('id', 'age')
    filter_fields = ['name','age']

  -查询的时候
	http://127.0.0.1:8000/students/?name=lqz&age=19&ordering=-age,-id

3 自定制过滤器

3.1 使用原理

1)-查询所有才会有过滤---》list才需要过滤---》queryset = self.filter_queryset(self.get_queryset())---》GenericAPIView-->filter_queryset

3.2 使用方法

# 基于django-filter扩写

1) 写一个类MyFilter,继承BaseFilterBackend

2) 重写filter_queryset方法,在该方法内部进行过滤(自己设置的过滤条件)
    def filter_queryset(self, queryset):
        for backend in list(self.filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, self)
            return queryset
# self.get_queryset() 就是 models.表名.objects.all()

3) 返回queryset对象(过滤后的queryset对象)

4) 配置在视图类中
	filter_backends = [MyFilter,]

五 拓展

1) select_related的使用
# 基于对象的跨表查询,可以减少查询数据库的次数
articleList=models.Article.objects.select_related("category").all()
for article_obj in articleList:
        #  Doesn't hit the database, because article_obj.category
        #  has been prepopulated in the previous query.
        #不再查询数据库,因为第一次查询,数据已经填充进去了
        print(article_obj.category.title)
        
2) mysql的悲观锁和乐观锁

3) drf内部内置了一些认证类,分别干了什么事
	待填坑
上一篇:Android中 ListView,RecyclerView中item显示错位的问题?


下一篇:Java 利用SpringBoot为Android,iOS等移动平台搭建简单的Restful后台接口平台