三大认证-认证、权限、频率

认证

认证源码分析

1.执行APIView中的dispatch方法时执行了三大认证:认证、权限、频率
    def dispatch(self, request, *args, **kwargs):
        try:
           self.initial(request, *args, **kwargs)  # 三大认证
        def initial(self, request, *args, **kwargs):
            self.perform_authentication(request)   # 认证
            self.check_permissions(request)        # 权限
            self.check_throttles(request)          # 频率
2.执行perform_authentication方法:
  def perform_authentication(self, request):
      request.user 
3.执行request.user:user方法(通过@property装饰器伪装了属性)
  @property
  def user(self):
      if not hasattr(self, '_user'):  # 反射判断有无'_user'
         with wrap_attributeerrors():
              self._authenticate()  # 第一次走
      return self._user
4.执行self._authenticate()
    def _authenticate(self):
        for authenticator in self.authenticators: 
            # self.authenticators-配置在视图类中的所有认证对象
            try:
                # 返回的两个值(token_user.user, token)
                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()
 5.通过for循环获取认证的单个对象-for authenticator in self.authenticators:
 6.通过如果有返回值将它们解压赋值给到self.user与self.auth
   self.user, self.auth = user_auth_tuple
 7.认证类:
    from rest_framework.authentication import BaseAuthentication
    from app01.models import UserToken
    from rest_framework.exceptions import AuthenticationFailed

    class LoginAuth(BaseAuthentication):
        def authenticate(self, request):
            token = request.META.get('HTTP_TOKEN')
            # token = request.GET.get('token')
            token_user = UserToken.objects.filter(token=token).first()
            if token_user:
                return (token_user.user, token)
            raise AuthenticationFailed('你未登录')
注:1.入口APIView中的dispatch方法中执行了三大认证
   2.执行perform_authentication方法
   3.执行request.user:user方法(通过@property装饰器伪装了属性)第一次走_authenticate方法
   4.执行self._authenticate()
    for循环self.authenticators-配置在视图类中的所有认证对象
    (authenticators=None,是在新的Request中传入的参数)
    调用认证类的对象
    将返回的两个值解压赋值self.user, self.auth = user_auth_tuple
   5.所以写认证类继承BaseAuthentication需要重写authenticate方法
   6.拿到用户提交的有带认证的堆积字符串与数据库数据判断后 返回对应数据
     成功会返回两个值(token_user.user, token) 之后就可以使用登录用户对象数据

权限

权限类的使用

 1.定义权限类:继承BasePermission 重写has_permission方法

     from rest_framework.permissions import BasePermission

    class UserPermissions(BasePermission):
        def has_permission(self, request, view):
            if request.user.permission.name == '超级用户':
                return True
            else:
                self.message = f'您是{request.user.permission.name}不是超级用户无法访问'
                return False

 2.全局配置:

 REST_FRAMEWORK = {'DEFAULT_PERMISSION_CLASSES': ['app01.permissions.UserPermissions', ]}

 3.局部配置:

     class BookView(ModelViewSet):
        permission_classes = [UserPermissions, ]
        queryset = Book.objects.all()
        serializer_class = BookSerializer

 4.局部禁用:

	permission_classes = []

 注:1.定义一个权限类 继承BasePermission
  2.重写has_permission方法
  3.获取用户权限并判断 有权限返回True 没有权限返回False(可以写message属性指定无法访问原因)
  4.可以通过在配置文件中全局配置或在视图类中局部配置或者局部禁用

权限的源码分析

   1.执行APIView中的dispatch方法时执行了三大认证:认证、权限、频率()
        def dispatch(self, request, *args, **kwargs):
            try:
                self.initial(request, *args, **kwargs)  # 三大认证
        def initial(self, request, *args, **kwargs):
            self.perform_authentication(request)   # 认证
            self.check_permissions(request)        # 权限
            self.check_throttles(request)          # 频率
   2.执行check_permissions方法(self是视图类的对象)
    def check_permissions(self, request):
        for permission in self.get_permissions():   # self.get_permissions 权限类对象
       # for permission in permission(): 
            # 对象调用has_permission方法 把自己当做第一个对象传入request(包装后的Request)
              self(视图类的对象==自己写的权限类中的view)
            if not permission.has_permission(request, self): 
            # 写的权限类重写has_permission方法
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None), 
                    # 通过反射拿到message对应的
                    code=getattr(permission, 'code', None)
                )
  3.通过for循环self.get_permissions(执行get_permissions方法)
    def get_permissions(self):
        return [permission() for permission in self.permission_classes] # 列表生成式
  4.self.permission_classes是视图类中的权限列表 permission_classes = [UserPermissions, ]
  5.通过列表生成式执行权限类()-permission()获得权限类对象
  6.继承BasePermission重写has_permission方法
  class UserPermissions(BasePermission):
    def has_permission(self, request, view):
        if request.user.permission.name == '超级用户':
            return True
        else:
            self.message = f'您是{request.user.permission.name}不是超级管理员 无法访问'
            return False
 注:1.写一个权限类 继承BasePermission
     2.重写has_permission方法
     3.return的结果为True或False
     4.messags在自己写的权限类中写首先使用自己的 不写使用父类的(返回前端的提示信息)

频率

频率类的使用

 1.定义频率类:继承SimpleRateThrottle 重写get_cache_key方法

    from rest_framework.throttling import SimpleRateThrottle

    class UserThrottling(SimpleRateThrottle):
        scope = 'luffy'
        def get_cache_key(self, request, view):
            return request.META.get('REMOTE_ADDR')

 2.全局配置:配置文件中

 REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': ['app01.throttle.UserThrottling', ],
    'DEFAULT_THROTTLE_RATES': {
        'luffy': '3/m'
    }

 3.局部配置:

     throttle_classes = [UserThrottling,]

 4.局部禁用:

     throttle_classes = []

 注:1.先定义一个频率类
  2.继承SimpleRateThrottle
  3.定义一个属性:scope = 'luffy'(频率设置)
  4.重写get_cache_key方法
   从包含所有的HTTP首部的META中获取ip地址(request.META.get('REMOTE_ADDR'))
   该方法返回什么就以什么作为权限限制
 5.在配置文件中配置频率的设置

       REST_FRAMEWORK = {
           'DEFAULT_THROTTLE_RATES': {
           'luffy': '3/m'}

频率的源码分析

   1.执行APIView中的dispatch方法时执行了三大认证:认证、权限、频率
        def dispatch(self, request, *args, **kwargs):
            try:
                self.initial(request, *args, **kwargs)  # 三大认证
        def initial(self, request, *args, **kwargs):
            self.perform_authentication(request)   # 认证
            self.check_permissions(request)        # 权限
            self.check_throttles(request)          # 频率
  2.执行check_throttles方法
       def check_throttles(self, request):
        throttle_durations = []
        for throttle in self.get_throttles():  # 视图类中的所有频率对象
            if not throttle.allow_request(request, self): # 重写allow_request方法
                throttle_durations.append(throttle.wait())
        if throttle_durations:
            durations = [
                duration for duration in throttle_durations
                if duration is not None
            ]

            duration = max(durations, default=None)
            self.throttled(request, duration)
 3.写频率类继承SimpleRateThrottle(写了allow_request方法)
from rest_framework.throttling import SimpleRateThrottle

class UserThrottling(SimpleRateThrottle):
    scope = 'luffy'
    def get_cache_key(self, request, view):
        return request.META.get('REMOTE_ADDR')

鸭子类型

1.鸭子类型:在面向对象中,子类不需要继承某个类 只有拥有某个类的方法或属性 就说这个子类属于某个类
如:假设有个鸭子类Duck类,有两个方法,run,speak方法
    假设又有一个普通鸭子类,PDuck,如果它也是鸭子,它需要继承Duck类,只要继承了鸭子类,什么都不需要写,普通鸭子类的对象就是鸭子这种类型;如果不继承,普通鸭子类的对象就不是鸭子这种类型
    假设又有一个唐老鸭子类,TDuck,如果它也是鸭子,它需要继承Duck类,只要继承了鸭子类,什么都不需要写,唐老鸭子类的对象就是鸭子这种类型;如果不继承,唐老鸭子类的对象就不是鸭子这种类型
注:python推崇鸭子类型是指:不继承某个类 只要有该类的方法和属性 这个类就是鸭子类型

存在的问题:当方法写错时 会导致该类不是该类型
解决办法:
方式一:abc模块,装饰后,必须重写方法,不重写就报错
方式二:drf源码中使用的:父类中写这个方法,但没有具体实现,直接抛异常

扩展

1 编写图书和出版社的5个接口,所有接口都要有一分钟访问5次的频率限制

1.频率类:
from rest_framework.throttling import SimpleRateThrottle

class UserThrottling(SimpleRateThrottle):
    scope = 'luffy'
    def get_cache_key(self, request, view):
        return request.META.get('REMOTE_ADDR')
2.配置文件全局配置频率及频率限制
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': ['app01.throttle.UserThrottling', ],
    'DEFAULT_THROTTLE_RATES': {
        'luffy': '5/m'
    }
}
3.视图类:
图书
  1.查询所有
class BookView1(ViewSetMixin, ListAPIView):

    queryset = Book.objects.all()
    serializer_class = BookSerializer
  2.新增
class BookView2(ViewSetMixin, CreateAPIView):

    queryset = Book.objects.all()
    serializer_class = BookSerializer
  3.查询单条
class BookDetailView1(ViewSetMixin, RetrieveAPIView):

    queryset = Book.objects.all()
    serializer_class = BookSerializer
  4.修改单条
class BookDetailView2(ViewSetMixin, UpdateAPIView):

    queryset = Book.objects.all()
    serializer_class = BookSerializer
  5.删除单条
class BookDetailView3(ViewSetMixin, DestroyAPIView):

    queryset = Book.objects.all()
    serializer_class = BookSerializer
出版社
  1.查询所有
class PublishView1(ViewSetMixin, ListAPIView):

    queryset = Publish.objects.all()
    serializer_class = PublishSerializer
  2.新增
class PublishView2(ViewSetMixin, CreateAPIView):

    queryset = Publish.objects.all()
    serializer_class = PublishSerializer
  3.查询单条
class PublishDetailView1(ViewSetMixin, RetrieveAPIView):

    queryset = Publish.objects.all()
    serializer_class = PublishSerializer
  4.修改单条
class PublishDetailView2(ViewSetMixin, UpdateAPIView):

    queryset = Publish.objects.all()
    serializer_class = PublishSerializer
  5.删除单条
class PublishDetailView3(ViewSetMixin, DestroyAPIView):

    queryset = Publish.objects.all()
    serializer_class = PublishSerializer

2 图书的接口需要登录才能访问,出版社的接口需要登录,并且是超级用户才能访问

1.认证类
from rest_framework.authentication import BaseAuthentication
from app01.models import UserToken
from rest_framework.exceptions import AuthenticationFailed

class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_TOKEN')
        # token = request.GET.get('token')
        token_user = UserToken.objects.filter(token=token).first()
        if token_user:
            return (token_user.user, token)
        raise AuthenticationFailed('你未登录')
2.权限类
from rest_framework.permissions import BasePermission

class UserPermissions(BasePermission):
    def has_permission(self, request, view):
        if request.user.permission.name == '超级用户':
            return True
        else:
            self.message = f'您是{request.user.permission.name}不是超级用户 无法访问'
            return False
2.视图类-登录
    class UserView(ModelViewSet):
        authentication_classes = []
        @action(methods=['POST', ], detail=False, url_path='login')
        def login(self, request):
            username = request.data.get('username')
            password = request.data.get('password')
            user = User.objects.filter(username=username, password=password).first()
            if user:
                token = str(uuid4())
                UserToken.objects.update_or_create(defaults={'token': token}, user=user)
                return Response({'code': 100, 'msg': '登录成功', 'token': token})
            else:
                return Response({'code': 101, 'msg': '用户名或密码错误'})
3.视图类-图书5个接口(接口需要登录才能访问)
 class BookView(ModelViewSet):
    authentication_classes = [LoginAuth,]   # 局部配置
    queryset = Book.objects.all()
    serializer_class = BookSerializer
4.视图类-出版社5个接口(接口需要登录及超级用户才能访问)
class PublishView(ModelViewSet):
    authentication_classes = [LoginAuth,]    # 局部配置
    permission_classes = [UserPermissions, ] # 局部配置
    queryset = Publish.objects.all()
    serializer_class = PublishSerializer

3 整理认证和频率的执行流程

1.认证:
   1.入口APIView中的dispatch方法中执行了三大认证
   2.执行perform_authentication方法
   3.执行request.user:user方法(通过@property装饰器伪装了属性)第一次走_authenticate方法
   4.执行self._authenticate()
    for循环self.authenticators-配置在视图类中的所有认证对象
    (authenticators=None,是在新的Request中传入的参数)
    调用认证类的对象
    将返回的两个值解压赋值self.user, self.auth = user_auth_tuple
   5.所以写认证类继承BaseAuthentication需要重写authenticate方法
   6.拿到用户提交的有带认证的堆积字符串与数据库数据判断后 返回对应数据
     成功会返回两个值(token_user.user, token) 之后就可以使用登录用户对象数据
2.频率:
   1.执行APIView中的dispatch方法时执行了三大认证:认证、权限、频率
   2.执行check_throttles方法
   3.for循环视图类中的所有频率对象
   4.重写allow_request方法

3.继承BaseThrottle编写频率类

1.频率类
class UserThrottling(BaseThrottle):
    info = {}
    def __init__(self):
        self.hest = None
    def allow_request(self, request, view):
        ip = request.META.get('REMOTE_ADDR')
        nowtime = time.time()
        if ip not in self.info:
          self.info[ip] = [nowtime]
          return True
        self.hest = self.info.get(ip)
        while self.hest and nowtime-self.hest[-1] > 60:
            self.hest.pop()
        if len(self.hest) < 3:
            self.hest.insert(0, nowtime)
            return True
        return False
2.全局配置
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': ['app01.throttle.UserThrottling', ],
}
注:1.自定义的逻辑  {ip:[时间1,时间2]}
    2.取出访问者ip
    3.判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
    4.循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
    5.判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
    6.当大于等于3,说明一分钟内访问超过三次,返回False验证失败
 posted on 2022-10-09 21:13  拾荒菇凉  阅读(86)  评论(0)    收藏  举报