欢迎来到夜的世界

莫听穿林打叶声,何妨吟啸且徐行。竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生.料峭春风吹酒醒,微冷,山头斜照却相迎。回首向来萧瑟处,归去,也无风雨也无晴。
扩大
缩小

权限,认证,频率组件

            前言               

  在编程的世界中,我们认为,用户输入的所有数据都是不可靠的,不合法的,直接使用用户输入的数据是不安全的,所以,我们要用权限组件,认证组件,频率组件进行校验.

  我们已经详细了解了DRF提供的几个重要的工具,DRF充分利用了面向对象编程的思路,对Django的View类进行了继承,并封装了其as_view方法和dispatch方法,随后提供了几个非常方便的编程工具,比如解析器、序列化。

  我们通过解析器,可以对来自客户端的application/json数据进行解析,另外,通过序列化工具,我们能够快速构建一套符合REST规范的api,随后又通过DRF的mixin、view及viewset对这些接口逻辑进行优化。

  有了他们,程序员开发Web应用的效率大大提高了,虽然我们也尝试自己动手实现了这些功能,但是既然有了优秀的工具,我们就不必费尽心思重复发明轮子。DRF并不仅仅提供了这几个工具,DRF还提供的一些其他的工具。

             认证组件             

  很久很久以前,Web站点只是作为浏览文档和其他资源的工具,甚少有什么用户交互之类的烦人的事情需要处理,所以,Web站点的开发这根本不关心什么人在什么时候访问了什么资源,不需要记录任何数据,有客户端请求,我即返回数据,简单方便,每一个http请求都是新的,响应之后立即断开连接。

  而如今,不管是论坛类、商城类、社交类、门户类还是其他各类Web站点,大家都非常重视用户交互,只有跟用户交互了,才能进一步留住用户,只有留住了用户,才能知道用户需求,知道了用户需求,就是有商机,有了流量,才能够骗到…额…抱歉…是融到钱,有了资金企业才能继续发展,可见,用户交互是非常重要的,甚至可以说是至关重要的一个基础功能。

  而谈到用户交互,就必须用到认证组件 , 权限组件 , 频率组件 

登录成功后生成token 

  http协议是无状态的,之前我们知道可以使用 cookie 和 session 两种方式可以保存用户信息,这两种方式不同的是 cookie 保存在客户端,而 session 保存在服务器中,各有优缺点,但是如果将他们配合在一起使用,将重要的敏感的信息保存在session中,不太重要的信息保存在cookie.

  而 token 被称之为"令牌",cookie,session,token都有其应用场景. 

  token  认证的大致步骤 :   

    • 用户登录,获取用户名密码,查询用户表,如果存在该用户,生成token,否则返回错误信息
    • 更新或者token信息

接下来,我们创建两个model,如下所示(token也可以存储在user表中,不过建议存储在user表中):  

from django.db import models

# Create your models here.


class User(models.Model):
    user_name = models.CharField(max_length=32)
    password = models.EmailField(max_length=32)
    user_type_entry = (
        (1, 'Delux'),
        (2, 'SVIP'),
        (3, "VVIP")
    )
    user_type = models.IntegerField(choices=user_type_entry)

    def __str__(self):
        return self.user_name


class UserToken(models.Model):
    user = models.OneToOneField("User", on_delete=models.CASCADE)
    token = models.CharField(max_length=128)

我们无需实现get方法,因为涉及登录认证,所有写post方法接口,登录都是post请求,视图类如下所示:

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:
            user_name = request.data['username']
            password = request.data['password']

            user_instance = User.objects.filter(
                user_name=user_name,
                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值:

import uuid


def generater_token():
    random_str = ''.join(str(uuid.uuid4()).split('-'))
    return random_str

以上就是token的简单生成方式,当然,在生产环境中不会如此简单,关于token也有相关的库,好了,我们构造几条数据之后,可以通过POSTMAN工具来创建几个用户的token信息。

接下来,如何对已经登录成功的用户实现访问授权呢?也就是说,只有登录过的用户(有token值)才能访问特定的数据,该DRF的认证组件出场了。

   DRF认证组件使用      

首先,我们来看一看,DRF认证组件的使用方式,首先,我们必须新建一个认证来,之后的认证逻辑就包含在这个类里面:

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.user_name, token_object.token
        else:
            raise APIException("认证失败")

实现方式看上去非常简单,到token表里面查看token是否存在,然后根据这个信息,返回对应信息即可,然后,在需要认证通过才能访问的数据接口里面注册认证类即可:

class BookView(ModelViewSet):

    authentication_classes = [UserAuth, UserAuth2]

    queryset = Book.objects.all()
    serializer_class =  BookSerializer

 多个认证类的实现  :  

  注意 : 如果需要返回什么数据,请在最后一个认证类中返回.

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方法,我们可以这样:
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类即可。
多个认证类的实现

全局认证 : 

  在settings文件中加入认证类即可实现全局认证 : 

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'authenticator.utils.authentication.UserAuth',
        'authenticator.utils.authentication.UserAuth2',
    ),
}

 

      权限组件         

权限组件使用 : 

  --- 定义权限类 

class UserPerms():
    message = "您没有权限访问该数据"
    def has_permission(self, request, view):
      # 如果等级>2,才有该权限
if request.user.user_type > 2: return True else: return False

  --- 指定权限类 

class BookView(ModelViewSet):

    authentication_classes = [UserAuth]
    permission_classes = [UserPerms2]

    queryset = Book.objects.all()
    serializer_class =  BookSerializer

 

       频率组件        

1 . 频率组件使用 : 

  --- 定义一个频率类 

# throttles.py 

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]
频率类

  --- 指定频率类 : 

class BookView(ModelViewSet):
    throttle_classes = [ VisitThrottle ]
    queryset = Book.objects.all()
    serializer_class = BookSerializer

2 . 局部访问频率组件控制 : 

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 代表访问频率.上面表示每分钟可以访问5次,get_cache_key 是必须存在的,它的返回值告诉当前频率控制组件要使用什么方式区分访问者(比如ip地址)

# 视图中
from .utils.throttles import RateThrottle


class BookView(ModelViewSet):
    throttle_classes = [ RateThrottle ]
    queryset = Book.objects.all()
    serializer_class = BookSerializer

3 . 全局访问频率控制

首先定义一个频率控制类,并且必须继承 SimpleRateThrottle 这个类.它是DRF提供的一个方便的频率控制类,

class RateThrottle(SimpleRateThrottle):
    scope = "visit_rate"

    def get_cache_key(self, request, view):
        return self.get_ident(request)

在全局的settings 配置

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": ('throttler.utils.throttles.RateThrottle',),
    "DEFAULT_THROTTLE_RATES": {
        "visit_rate": "5/m"
    }
}

 

posted on 2018-12-09 20:42  二十四桥_明月夜  阅读(187)  评论(0编辑  收藏  举报

导航