rest-framework之认证组件(token、uuid)、权限组件、频率组件

drf框架自带一些组件,完成认证、权限、频率控制等功能。

rest_framework.views的APIView实际上里面的as_view()调用了原生django的,但是它有自己的dispatch方法,在dispatch方法中的self.initial(request, *args, **kwargs)里面定义了三个组件。

一、认证组件

认证组件主要用来对用户的登录状态做校验。比如某些数据是需要登录才能查看和操作的。

 使用时的写法与配置:

  1. 先创建一个认证的类,继承BaseAuthentication(一般认证的代码单独写一个文件)
  2. 局部使用:在视图类中——authentication_classes=[MyAuth,]
  3. 全局使用:在配置文件中配置
    REST_FRAMEWORK={
    	"DEFAULT_AUTHENTICATION_CLASSES":["app01.MyAuths.MyAuth",]
    }

    4. 局部禁用:比如配置了全局,某个视图不想用,可以在视图中写个空——authentication_classes = [ ]

认证时,由于是前后端分离开发,无法知道移动端是什么,所以不能使用cookie和session,因此需要使用一个新的东西来标识用户身份——token。

 具体看看token

由于服务器端要保存大量的session,造成了压力,所以就想到了服务端如何能够在不保存session的情况下对来访的用户做验证呢?

如图,我们可以给用户发送一个token,里面包含他的id,然后利用一个加密算法,再加上一个只有我自己知道的密钥,生成一个签名,然后把这个签名连同数据一起发回给用户,服务端并不保存这个token;

等用户下次来访问的时候,带着这个token过来,然后我把这个token里面的数据和签名拿出来,然后对数据和我的密钥以原来的加密算法加密,再去对比这个加密的结果是否和签名的结果一致,如果一致,说明此用户已经登录过了,并且我可以直接拿到用户的id;如果不一致,说明数据被人篡改过了,就给客户端回复一个未通过认证即可。

 然后用户需要一个唯一的id,这个可以通过很多方法生成,这里先来看看uuid

uuid是128位的全局唯一标识符(univeral unique identifier),通常用32位的一个字符串的形式来表现。

python中自带了uuid模块来进行uuid的生成和管理工作。

uuid的作用:

很多应用场景需要一个id,但是又不要求这个id 有具体的意义,仅仅用来标识一个对象。常见的用处有数据库表的id字段;用户session的key值;前端的各种UI库,因为它们通常需要动态创建各种UI元素,这些元素需要唯一的id, 这时候就需要使用UUID了。例如:一个网站在存储视频、图片等格式的文件时,这些文件的命名方式就可以采用 UUID生成的随机标识符,避免重名的出现。

UUID主要有五个算法,也就是五种方法来实现:

python的uuid模块提供的UUID类和函数uuid1(),uuid3(),uuid4(),uuid5() 来生成1, 3, 4, 5各个版本的UUID ( 需要注意的是:python中没有uuid2()这个函数)。

uuid.uuid1(node clock_seq)
#   基于时间戳
#   使用主机ID, 序列号, 和当前时间来生成UUID, 可保证全球范围的唯一性.
#   但由于使用该方法生成的UUID中包含有主机的网络地址, 因此可能危及隐私.
#   该函数有两个参数, 如果 node 参数未指定, 系统将会自动调用 getnode() 函数来获取主机的硬件(mac)地址.
#   如果 clock_seq  参数未指定系统会使用一个随机产生的14位序列号来代替.
import uuid
print(uuid.uuid1())


uuid.uuid3(namespace, name)
#   通过计算名字和命名空间的MD5散列值得到,保证了同一命名空间中不同名字的唯一性,
#   和不同命名空间的唯一性,***但同一命名空间的同一名字生成相同的uuid****。
print(uuid.uuid3(uuid.NAMESPACE_URL,'python'))
print(uuid.uuid3(uuid.NAMESPACE_URL,'python'))


uuid.uuid4() : 基于随机数
#   通过随机数来生成UUID. 使用的是伪随机数有一定的重复概率.
print(uuid.uuid4())


uuid.uuid5(namespace, name)
#   通过计算命名空间和名字的SHA-1散列值来生成UUID, 算法与 uuid.uuid3() 相同
print(uuid.uuid5(uuid.NAMESPACE_URL,'python'))
  • 1 Python中没有基于 DCE 的,所以uuid2可以忽略
  • 2 uuid4存在概率性重复,由无映射性
  • 3 若在Global的分布式计算环境下,最好用uuid1
  • 4 若有名字的唯一性要求,最好用uuid3或uuid5

 了解了token和uuid生成唯一id,下面我们来看看认证组件的写法:

先来创建两张表(这里没有使用缓存数据库,先用mysql保存一下用户的随机串,未使用真正的token机制):

from django.db import models


class User(models.Model):
    name=models.CharField(max_length=32)
    pwd=models.CharField(max_length=64)
    user_type=models.IntegerField(choices=((1,"超级管理员"),(2,"普通管理员"),(3,"2b用户")),default=3)

#跟User表做一对一关联
class Token(models.Model):
    user=models.OneToOneField(to='User')
    token = models.CharField(max_length=64)

认证首先要重写authenticate方法:

from rest_framework.authentication import BaseAuthentication
from app01 import models
from rest_framework.exceptions import AuthenticationFailed


class MyAuth(BaseAuthentication):
    def authenticate(self, request):
        # 这里我用postman发请求的时候直接在url后面带了token
        token = request.GET.get('token')
        token_obj = models.Token.objects.filter(token=token).first()
        if token_obj:
            # 有值表示登录了,把当前登录的user对象token_obj.user返回
            return token_obj.user, token_obj
        else:
            # 没有值就抛异常
            raise AuthenticationFailed('您没有登录')

然后到视图中使用(局部使用):

from rest_framework.views import APIView
from app01.MyAuths import MyAuth
from rest_framework.response import Response
from app01 import models
from django.core.exceptions import ObjectDoesNotExist
import uuid


class Login(APIView):
    # 局部禁用
    # authentication_classes = []
    def post(self, request):
        response = {'code': 100, 'msg': '登陆成功'}
        name = request.data.get('name')
        pwd = request.data.get('pwd')
        try:
            # .get(),有且只有一条才不报错,其他都抛异常
            user = models.User.objects.filter(name=name, pwd=pwd).get()
            # 登录成功,需要去Token表中存数据,利用uuid生成一个随机的id
            token = uuid.uuid4()
            models.Token.objects.update_or_create(user=user, defaults={'token':token})
            response['token'] = token
        except ObjectDoesNotExist as e:
            response['code'] = 101
            response['msg'] = '用户名或密码错误'
        except Exception as e:
            response['msg'] = str(e)
        return Response(response)


# 用户必须登录之后才能获取所有图书接口
class Books(APIView):
    # 可以写多个认证类
    authentication_classes = [MyAuth, ]
    def get(self, request):
        # request.user就是当前登录的用户
        print(request.user.name)
        # 这里就不到数据库取数据了
        return Response('返回了所有图书')


class Publish(APIView):
    def get(self, request):
        return Response('返回了所有出版社信息')

路由:

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^books/', views.Books.as_view()),
    url(r'^login/', views.Login.as_view()),
    url(r'^publish/', views.Publish.as_view()),
]

这里我给图书的查询做了登录校验,出版社的没有做。利用postman在不登录的情况下发一个请求试试:

 

出版社信息正常返回。

图书的要求登录。那么我们利用postman发送数据直接登陆试试。

以下是登录成功后返回的信息:

 

可以看到,返回给我们一个token,这时候我们再带上这个token去访问图书接口就可以成功访问。

 

认证源码分析:

我们的CBV继承了APIView类,APIView类中的dispatch方法中有self.initial方法,这个方法包含了认证、权限和频率控制。

self.perform_authentication(request)就是认证的方法

可以看到,这个方法内部调用了request的user方法,必须明确的是,request是封装后的request。所以我们要去Request类中找user方法。

一个个认证类的对象是在reuqest对象实例化的时候传入的。

APIView中的get_authenticators,通过列表推导式生成一个个的认证类对象,然后传入request对象中

自己写一个不存数据库的token验证:

 自己瞎写的,只是把id作为有效的加密部分,只是给自己做个参考。

 认证:

from rest_framework.authentication import BaseAuthentication
from app01 import models
from rest_framework.exceptions import AuthenticationFailed
import hashlib
def get_md5(info):
    md5 = hashlib.md5()
    md5.update('23784325174'.encode('utf-8'))
    md5.update(info.encode('utf-8'))
    md5.update('236556219'.encode('utf-8'))
    return md5.hexdigest()+'|'+info
class Au(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_TOKEN')
        name = request.GET.get('name')
        user_obj = models.User.objects.filter(name=name).first()
        if get_md5(str(user_obj.pk)) == token:
            return user_obj,token
        else:
            raise AuthenticationFailed('您没有登录')

 视图:

from rest_framework.views import APIView
from app01.MyAuths import Au
from rest_framework.response import Response
from app01 import models
import hashlib

# 获取token
def get_md5(info):
    md5 = hashlib.md5()
    md5.update('23784325174'.encode('utf-8'))
    md5.update(info.encode('utf-8'))
    md5.update('236556219'.encode('utf-8'))
    return md5.hexdigest()+'|'+info

# 登录接口
class Login(APIView):
    def post(self, request):
        response = {'code': 100, 'msg': '登陆成功'}
        name = request.data.get('name')
        pwd = request.data.get('pwd')
        try:
            user = models.User.objects.filter(name=name, pwd=pwd).get()
            id = user.id
            signal = get_md5(str(id))
            response['token'] = signal
        except Exception as e:
            print(e)
        return Response(response)



# 用户必须登录之后才能获取所有图书接口
class Books(APIView):
    # 可以写多个认证类
    authentication_classes = [Au, ]
    def get(self, request):
        # request.user就是当前登录的用户
        print(request.user.name)
        # 这里就不到数据库取数据了
        return Response('返回了所有图书')

 利用postman发请求验证:用户信息直接带在GET请求里,token放在请求头里。

注意:请求头里的东西这么取:request.META.get("HTTP_键")

 二、权限组件

权限组件用来验证某个用户是否拥有某个权限。比如一般运维只有管理服务器的简单权限,而高级运维有root管理员权限。

之前建表的时候,在用户里面创建了不同权限的用户,下面我们根据权限来判断用户是否有权干某事。

权限类使用顺序:先用视图类中的权限类,再用settings里配置的权限类,最后用默认的权限类

先去写一个权限组件 ,需要继承BasePermission类,重写has_permission(self, request, view)方法

from rest_framework.permissions import BasePermission

class MyPermission(BasePermission):
    # 通过message可以自己定制发给前台的信息
    message = '您不是超级用户,无法查看图书'
    def has_permission(self, request, view):
        # 因为权限是在认证之后执行的,所以可以取到request.user
        user_type = request.user.get_user_type_display()
        if user_type == '超级管理员':
            # 这里需要返回True或False
            return True
        return False

然后到视图中使用,这里注意,他跟认证的使用是一样的

  • 局部使用:permission_classes = [MyPermission, ]
  • 全局使用:
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.MyAuths.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.MyAuths.MyPermission",]
}
  • 局部禁用:permission_classes = [ ]
from rest_framework.views import APIView
from app01.MyAuths import MyPermission
from rest_framework.response import Response

# 用户必须登录之后且是超级管理员才能获取所有图书接口
class Books(APIView):
    # 可以写多个认证类
    authentication_classes = [MyAuth, ]
    # 只有超级管理员才能访问该接口
    permission_classes = [MyPermission, ]
    def get(self, request):
        # request.user就是当前登录的用户
        print(request.user.name)
        # 这里就不到数据库取数据了
        return Response('返回了所有图书')

接下来用postman发请求:

先用超级管理员发

然后用普通用户发:

权限源码分析:

循环拿到一个个权限类的对象,执行我们自己重写的has_permission方法。

如果验证不通过,则返回异常。

注意:这里就是为什么我们可以写一个message="错误信息",然后发送到前台了 

三、频率控制

3.1、使用

第一步:

  •         写一个频率类,继承SimpleRateThrottle
  •         重写get_cache_key,返回self.get_ident(request)
  •         一定要记住配置一个scope=字符串

第二步:

  •         在settings中配置
REST_FRAMEWORK = {

	'DEFAULT_THROTTLE_RATES':{
		'limit':'3/m'  # 3/m指的是一分钟限制3次访问
	}
}

源码中的对应关系如下,所以在配置是可以写minutes,但是后面只取索引0,前面是字典对应关系。 

局部使用:

  •         在视图中: throttle_classes = [自己写的频率认证类名, ]

全局使用:

  •         在settings配置文件中:
REST_FRAMEWORK = {
    # 这里路径是频率类所在的路径
    'DEFAULT_THROTTLE_CLASSES': ['app01.MyThrottle.Throttle'],
	'DEFAULT_THROTTLE_RATES':{
		'limit':'3/m'  # 3/m指的是一分钟限制3次访问
	}
}

局部禁用:

  •         在视图中: throttle_classes = [ ]

 频率控制类:

from rest_framework.throttling import SimpleRateThrottle


# 频率控制类
class Throttle(SimpleRateThrottle):
    scope = 'limit'
    def get_cache_key(self, request, view):
        """
        返回什么值就以什么过滤。比如返回id,就按id过滤
        return self.get_ident(request)
        """
        # 下面的方式是以ip做过滤条件
        return request.META.get('REMOTE_ADDR')

路由:

url(r'^books/', views.Books.as_view())

视图:

from rest_framework.views import APIView
from rest_framework.response import Response
from app01.MyThrottle import Throttle


class Books(APIView):
    # 局部使用
    # throttle_classes = [Throttle, ]
    # 局部禁用
    throttle_classes = []
    def get(self, request):
        return Response('返回了所有图书')

3.2、自定义频率类(继承BaseThrottle,重写allow_request方法)

from rest_framework.throttling import BaseThrottle

class Throttle(BaseThrottle):
    VISIT_RECORD = {}

    def __init__(self):
        self.history = None

    # 自定义控制每分钟访问多少次,允许访问返回true,不允许访问返回false
    def allow_request(self, request, view):
        # (1)取出访问者的ip,{ip1:[第二次访问时间,第一次访问时间],ip2:[]}
        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掉
        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])

3.3、源码分析

来看一下SimpleRateThrottle类的源码分析

因为我们写频率控制类,继承它之后更加方便。只需要重写get_cache_key来获取用户ip之类的即可。

 再看其他方法

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值