django restframework 中 APIView中 验证 权限 以及节流使用 以及一点点原理说明

本博客作为个人笔记使用 主要是 记录 APIView 类的 用户认证 权限校验 以及 节流的使用与一些 原理说明 水平十分有限

创建 用户 使用的是django自带的User。

序列化器 如下

from django.contrib.auth.models import User
from rest_framework import serializers

class MyUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'password', 'email']

如果不会用序列化器可以参考https://blog.csdn.net/dandanfengyun/article/details/84779459

所使用的 包

import uuid

from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.core.cache import caches
from django.shortcuts import render
from rest_framework import status

from rest_framework.generics import ListCreateAPIView, ListAPIView

from rest_framework.response import Response

cache = caches['user']		# 缓存

使用一个视图类完成 User 创建 主要是 密码的设置

class RegisterAPI(ListCreateAPIView):
    serializer_class = MyUserSerializer

    def list(self, request, *args, **kwargs):
        res = {
            'code': 0,
            'msg': '注册界面',
        }

        return Response(res)

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)

        user = User.objects.get(pk=serializer.data.get('id'))
        user.set_password(user.password) 
        user.save()
        new_serializer = self.get_serializer(user)

        return Response(new_serializer.data, status=status.HTTP_201_CREATED, headers=headers)

User 类自带的方法 self.set_password()可以将设置保护密码

在 url 路由中 加入该视图函数

url(r'^register$', RegisterAPI.as_view()),

前端代码未写 可以直接使用 Postman 工具发起请求
django restframework 中 APIView中 验证 权限 以及节流使用 以及一点点原理说明_第1张图片
图为注册成功 返回的 数据

登录 逻辑 则是 当用户登录时 验证通过后 生成一个 随机 token 值 可用 UUID生成。然后将token 值 作为 key 登录用户 id 作为value 保存在 缓存中,缓存可以设置过期时间 。然后将token值 返回给客户端。 当某个视图需要用户为登录状态才能访问时 。客户发起的请求中应该 带有 token 值。可以设置为请求附带的参数 ?token=‘asghjfugyig’ 之类。 如果根据该token可以在缓存中查找到 用户id 且根据该id能找到对应user. 则为 登录状态。 可以访问 , 否则 不可以访问。

这里 需要用到 缓存 可以使用 redis 缓存 。
setting 中设置

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    "user": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/10",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

# 用户登录缓存时间
USER_ALIVE = 60*60*24*15    # 15天

登录的视图类

class LoginAPI(ListAPIView):

    def list(self, request, *args, **kwargs):

        username = request.data.get('username')
        password = request.data.get('password')
        user_login = authenticate(username=username, password=password) # User自带的验证 返回值 为user

        if user_login:
            token = request.query_params.get('token', None)
            user_id = cache.get(token, None)
            if not user_id:
                user_cache = User.objects.filter(pk=user_id).first()
                if not (user_cache is user_login):
                    # 当前 缓存中 没有保存 用户
                    token = uuid.uuid4().hex

            # 设置 token 到 缓存中
            cache.set(token, user_login.id, settings.USER_ALIVE)

            res = {
                'code': 0,
                'msg': '登陆成功',
                'username': user_login.username,
                'token': token,
            }
        else:
            res = {
                'code': 1,
                'msg': '登录失败, 请检查用户名密码',
            }

        return Response(res)

主要是 缓存的 token 设置 。

 cache.set(token, user_login.id, settings.USER_ALIVE)

设置了 token 对应 的 用户id 以及 到期时间。
主要逻辑是 当 用户登录且 验证通过 时。 判断 是否有 token 如果 没有 那么就随机生成 token 设置 缓存。 如果 已经 附带 token 且 根据 token 查询到的 用户 为 当前登录用户。那么就不重新生成 token 而是 根据原token 重新设置 过期事件 。 所以 前端设计时 如果 已经登录过的用户 一定 附带上token 。 如果 未附带 会使用 UUID 重新生成 新的 token 导致 缓存 浪费。
下图是 重新登录且token未过期是返回结果
django restframework 中 APIView中 验证 权限 以及节流使用 以及一点点原理说明_第2张图片

笔记 的 重点

APIVIew继承了 django 的View。在路由中使用时 都是 类名.as_view()
as_view()方法 实际是 一个 闭包 方法内 调用 dispatch 方法 做路由 分发
View 中 的 dispatch 将请求 方法 转 小写 然后返回 给 对应的 已实现方法进行处理

def dispatch(self, request, *args, **kwargs):
    # Try to dispatch to the right method; if a method doesn't exist,
    # defer to the error handler. Also defer to the error handler if the
    # request method isn't on the approved list.
    if request.method.lower() in self.http_method_names:
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    else:
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)

APIView 继承 View 且 继承了 父类 的 as_view()方法 。而且 重写 了 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
    self.headers = self.default_response_headers  # deprecate?

    try:
        self.initial(request, *args, **kwargs)

        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                              self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs)

    except Exception as exc:
        response = self.handle_exception(exc)

    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

从 原 码 可以看到 使用了self.initialize_request() 方法对 request 做了封装。
且在之后调用了 self.initial(request, *args, **kwargs)

查看 self.initial(request, *args, **kwargs) 方法 发现最后执行了

self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)

这 三个 方法 分别就是 用户认证 权限校验 以及 节流

首先 是 用户认证

def perform_authentication(self, request):

    request.user

认证方法 只是 一句话 request.user 但 该 request此时应为
self.initialize_request(request, *args, **kwargs) 方法的 返回对象。

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
    )

该方法的返回值 是 类 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

大概就是如果 没有 _user属性 就会 执行self._authenticate()方法

 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.user, self.auth = user_auth_tuple
            return

    self._not_authenticated()

内部 做了一个 循环 for authenticator in self.authenticators: 而authenticators的值也是在Request实例化时设置 authenticators=self.get_authenticators(),

def get_authenticators(self):
    """
    Instantiates and returns the list of authenticators that this view can use.
    """
    return [auth() for auth in self.authentication_classes]

def get_authenticators(self) 方法 返回的 一个 列表 。是self.authentication_classes中的类

如果我们做自定义验证的话 就 在 继承APIView 时 赋值authentication_classes
authentication_classes应为可迭代对象 如列表 元组 等 且其中元素 应为 类 。
user_auth_tuple = authenticator.authenticate(self)
且类中 有 authenticate方法。

注意 _authenticate(self): 是Request 的 方法
因此

    if user_auth_tuple is not None:
        self._authenticator = authenticator
        self.user, self.auth = user_auth_tuple
        return

实际上 是 使 我们的 request 对象 即请求 中 多了 属性 user, auth

关于authentication_classes中 类的 定义 我们可以参考 default

'DEFAULT_AUTHENTICATION_CLASSES': (
    'rest_framework.authentication.SessionAuthentication',
    'rest_framework.authentication.BasicAuthentication'
),
'DEFAULT_PERMISSION_CLASSES': (
    'rest_framework.permissions.AllowAny',
),
'DEFAULT_THROTTLE_CLASSES': (),

并不在同一文件

class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

经过 寻找 我们 找到 方法继承的父类

class BaseAuthentication(object):
    """
    All authentication classes should extend BaseAuthentication.
    """

    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        raise NotImplementedError(".authenticate() must be overridden.")

只要 继承 该 类。 然后 重写 authenticate 方法 即可 。 返回值 为 user token 。正好验证 登录。 验证通过 返回 值 就是 user, token

终于可以真正开始做了
定义一个类继承

from django.contrib.auth.models import User
from django.core.cache import caches
from rest_framework.authentication import BaseAuthentication

cache = caches['user']


class LoginAuth(BaseAuthentication):

    def authenticate(self, request):
        token = request.query_params.get('token')
        user_id = cache.get(token)
        user = User.objects.filter(pk=user_id).first()

        if user:
            return user, token
        else:
            return None

如果获取到 user 则验证通过 视图类

class AuthUserAPI(ListAPIView):

    serializer_class = MyUserSerializer

    authentication_classes = (LoginAuth, )

    def list(self, request, *args, **kwargs):
        user = request.user
        if user:
            queryset = user
            serializer = self.get_serializer(queryset)
            return Response(serializer.data)
        else:
            res = {
                'code': 1,
                'msg': '未登录或已过期 请重新登录'
            }

视图类 中 设置 了 authentication_classes = (LoginAuth, )
如此 便可以 使得 request 对象中 包含 属性 user.

权限认证
权限认证 比 用户认证 更简单一点 APIView类中方法

def get_permissions(self):
    """
    Instantiates and returns the list of permissions that this view requires.
    """
    return [permission() for permission in self.permission_classes]

def check_permissions(self, request):
    """
    Check if the request should be permitted.
    Raises an appropriate exception if the request is not permitted.
    """
    for permission in self.get_permissions():
        if not permission.has_permission(request, self):
            self.permission_denied(
                request, message=getattr(permission, 'message', None)
            )

同样是 设置permission_classes属性 。且 其中 类 get_permissions方法 返回值 只需为 True 或 False True则有该权限。否则没有。就会 执行
self.permission_denied(request, message=getattr(permission, ‘message’, None))
同auth一样 我们找到 该 方法默认父类

class BasePermission(object):
    """
    A base class from which all permission classes should inherit.
    """

    def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

只要 继承 该类 然后 判断 权限 有无 返回 True 或 False即可

判断是否是超级用户 的类

from rest_framework.permissions import BasePermission


class SuperPermission(BasePermission):

    def has_permission(self, request, view):
        user = request.user
        return user.is_superuser

视图类

class PermissionUserAPI(ListAPIView):
    queryset = User.objects.all()
    serializer_class = MyUserSerializer

    authentication_classes = (LoginAuth,)
    permission_classes = (SuperPermission,)

节流Throttle
节流 也 类似于 权限验证 只有f not throttle.allow_request(request, self): 为真 即throttle.allow_request(request, self)返回值 为 False 是 才会执行节流设置

throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES

def get_throttles(self):
    """
    Instantiates and returns the list of throttles that this view uses.
    """
    return [throttle() for throttle in self.throttle_classes]

def check_throttles(self, request):
    """
    Check if request should be throttled.
    Raises an appropriate exception if the request is throttled.
    """
    for throttle in self.get_throttles():
        if not throttle.allow_request(request, self):
            self.throttled(request, throttle.wait())

默认的 throttle_classes是一个 空元组 。也就是 不做节流 设置。
定义节流类时 根据经验 该类 应有 allow_request 方法 。查找网上资料 。restframe_work有一个简单的 节流设置 我们 可以继承该类

class SimpleRateThrottle(BaseThrottle):

原码过长 大家可以 自己找找看

从节流类在 执行时 直接调用 的 allow_request与类初始化函数开始

def __init__(self):
    if not getattr(self, 'rate', None):
        self.rate = self.get_rate()
    self.num_requests, self.duration = self.parse_rate(self.rate)

如果 未设置 直接设置rate 调用 self.rate = self.get_rate() 通过 get_rate()方法 为 rate属性赋值 。。。 为求 简单 直接 设置 rate属性。 现在还不知道应该设置为什么。继续向下
初始化函数执行了self.parse_rate(self.rate) 设置了 self.num_requests, self.duration

    def parse_rate(self, rate):
    """
    Given the request rate string, return a two tuple of:
    , 
    """
    if rate is None:
        return (None, None)
    num, period = rate.split('/')
    num_requests = int(num)
    duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
    return (num_requests, duration)

可以查找观察 得出 (查看网络资料更好 观察的不一定是对的) rate 应为 一个频率 字符串 格式 如 ‘5/min’ 意思就是 每分钟访问 5次 。
self.num_requests = 5 可访问次数
self.duration = 60 时间 间隔

然后 看 类 直接 调用的 allow_request 方法。假设设置了 rate=‘5/min’
def allow_request(self, request, view):
if self.rate is None:
return True

        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True
    
        self.history = self.cache.get(self.key, [])
        self.now = self.timer()
    
        # Drop any requests from the history which have now passed the
        # throttle duration
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()

执行到self.key = self.get_cache_key(request, view)

def get_cache_key(self, request, view):
    """
    Should return a unique cache-key which can be used for throttling.
    Must be overridden.

    May return `None` if the request should not be throttled.
    """
    raise NotImplementedError('.get_cache_key() must be overridden')

根据描述 我们 应该 重写 该 方法 返回一个 唯一的 cache-key 。作为 self.key的值
如果 返回的 是 None 那么就不会节流 。

这是 class SimpleRateThrottle(BaseThrottle):的属性

class SimpleRateThrottle(BaseThrottle):
	cache = default_cache
	timer = time.time
	cache_format = 'throttle_%(scope)s_%(ident)s'
	scope = None
	THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
	...

由此 可知
self.history = self.cache.get(self.key, []) 根据key从默认缓存中取出数据 没有 则返回一个列表
self.now = self.timer() 当前时间戳

        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()

self.history应该是一个列表。但我们并不知道 里面存的什么数据。第一次判断时由于是空列表 while 循环不会执行 return self.throttle_failure() 也不会执行 会执行
return self.throttle_success()

def throttle_success(self):
    """
    Inserts the current request's timestamp along with the key
    into the cache.
    """
    self.history.insert(0, self.now)
    self.cache.set(self.key, self.history, self.duration)
    return True

可以看出 向self.history中插入了当前时间戳。
即当前访问时间时间戳插入到列表中且占据第一个元素位置
然后设置保存到了 了 默认缓存 。 key值为 self.key value值 为 self.history 过期时间 是 self.duration。
如果 我们设置的 rate = ‘5/min’ 那么 self.duration = 60也就是说 60s 后 该缓存过期。执行self.history = self.cache.get(self.key, [])时不能取出数据,再次为 空列表。

然后 如果据 上次 访问时间 1 分钟内 再次访问。 那么 self.history 中有数据 就会进行while循环 和判断。

    while self.history and self.history[-1] <= self.now - self.duration:
        self.history.pop()

self.history列表 的 最后 一位 是 记录中 最先 访问 的时间 戳。
如果 当前时间 - 规定时间 间隔 >= 该时间戳 那就说明 该访问 记录 是 时间间隔之外的 也就是 1 分钟之前的 。。。不应在 我们统计范围之内。。 从列表中 删除 即可

然后 列表中数据 即为 1 分钟内 访问 的 时间戳。

    if len(self.history) >= self.num_requests:
        return self.throttle_failure()

如果 该列表 长度 大于等于 规定访问 的次数 。 那就说明 在这个时间间隔内 访问 量超过了规定。 会进行节流 return self.throttle_failure()

回到 APIView 函数。 会执行 self.throttled(request, throttle.wait()) 这个方法就是 节流时执行的。 有兴趣可以看看 。。 我是看不懂。

下面开始 设置 节流 类

from rest_framework.throttling import SimpleRateThrottle


class MyThrottle(SimpleRateThrottle):

    scope = 'test'
    THROTTLE_RATES = {
        'test': '5/min'
    }

    def get_cache_key(self, request, view):
        user = request.user
        if user:
            ident = user
        else:
            ident = self.get_ident(request)

        return self.cache_format % {
            'scope': self.scope,
            'ident': ident,
        }

看到并没有 设置 rate 但设置 了 scope 且 设置了

THROTTLE_RATES = {
            'test': '5/min'
        }

这是因为 设置 rate 时 就会执行 根据 scope 从THROTTLE_RATES字典中中查找到相应值 作为 rate。

def get_rate(self):
    """
    Determine the string representation of the allowed request rate.
    """
    if not getattr(self, 'scope', None):
        msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
               self.__class__.__name__)
        raise ImproperlyConfigured(msg)

    try:
        return self.THROTTLE_RATES[self.scope]
    except KeyError:
        msg = "No default throttle rate set for '%s' scope" % self.scope
        raise ImproperlyConfigured(msg)

然后 get_cache_key 方法 中返回的 cache-key值 应该 是 与 用户 或 浏览器相关的 。。。

视图类

class ThrottleAPI(ListAPIView):
    queryset = User.objects.all()
    serializer_class = MyUserSerializer

    throttle_classes = (MyThrottle,)

诸多 不足 担待

你可能感兴趣的:(Django,restful,APIVIew)