• Restframework的认证,权限,节流


    1.认证

      流程:请求到达REST framework的时候,会对request进行二次封装,在封装的过程中会对客户端发送过来的request封装进认证,选择,解析等功能。request方法封装完成之后,执行initial方法时,又会再次对客户端的请求执行认证操作,确保请求的合法性

      生命周期:

      发送请求-->Django的wsgi-->中间件-->路由系统_执行CBV的as_view(),就是执行内部的dispath方法-->在执行dispath之前,有版本分析 和 渲染器-->在dispath内,对request封装-->版本-->认证-->权限-->限流-->视图-->如果视图用到缓存( request.data or request.query_params )就用到了 解析器-->视图处理数据,用到了序列化(对数据进行序列化或验证) -->视图返回数据可以用到分页

     自定义认证:

    models.py(创建完后自行添加几条数据)

    from django.db import models
    
    # 用户信息
    class UserInfo(models.Model):
    	username = models.CharField(max_length=32, unique=True)
    	password = models.CharField(max_length=32)
    	# 小整数字段
    	type = models.SmallIntegerField(
    		choices=((1,"普通用户"),(2,"VIP用户")),
    		default=1
    	)
    
    
    # 用户认证
    class Token(models.Model):
    	token = models.CharField(max_length=32)
    	user = models.OneToOneField(to="UserInfo")
    
    
    # 评论表
    class Comment(models.Model):
    	content = models.CharField(max_length=128)
    	user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, default=1) 

    url.py

    from django.conf.urls import url
    from django.contrib import admin
    from app01 import views
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'login/$', views.LoginView.as_view()),
    ]
    
    
    from rest_framework.routers import DefaultRouter
    router = DefaultRouter()
    # 注册路由,表示路径comment对应视图函数CommentViewSet
    router.register(r'comment', views.CommentViewSet)
    urlpatterns += router.urls  

    views.py

    import time
    import hashlib
    from app01 import models
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from app01 import app01_serializers     # 自定义的序列化
    from rest_framework.viewsets import ModelViewSet
    
    # 生成token函数
    def get_token_code(username):
    	"""
    	根据用户名和时间戳生成用户登陆成功的随机字符串
    	:param username:字符串格式的用户名
    	:return:字符串格式的token
    	"""
    	timestamp = str(time.time())
    	m = hashlib.md5(bytes(username,encoding="utf-8"))
    	m.update(bytes(timestamp,encoding="utf-8"))
    	return m.hexdigest()
    
    
    class LoginView(APIView):
    	"""
    	1. 接收用户发过来(POST)的用户名和密码数据
    	2. 校验用户名密码是否正确
    		- 成功就返回登陆成功(发Token)
    		- 失败就返回错误提示
    	"""
    	def post(self,request):
    		res = {"code":0}
    		username = request.data.get("username")
    		password = request.data.get("password")
    		user_obj = models.UserInfo.objects.filter(
    			username=username,
    			password=password,
    		).first()
    		if user_obj:
    			token = get_token_code(username)
    			# 保存token
    			# 如果有记录就更新defaults里传的参数, 没有记录就用defaults里传的参数创建一条数据
    			models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
    			# 将token返回给用户
    			res["token"] = token
    		else:
    			res["token"] = 1
    			res["error"] = "用户名或密码错误"
    		return 	Response(res)
    
    
    # 对文章进行增删改查操作
    class CommentViewSet(ModelViewSet):
    	queryset = models.Comment.objects.all()
    	serializer_class = app01_serializers.CommentSerializer

    app01_serializers.py(app下创建的py文件)

    from app01.models import Comment
    from rest_framework import serializers
    
    
    class CommentSerializer(serializers.ModelSerializer):
    	class Meta:
    		model = Comment
    		fields = "__all__"
    

     通过在postman上模拟post发送请求登录,如果用户名和密码正确的话,会生成token值,下次该用户再登录时,token的值就会更新

    当用户名或密码错误时,抛出异常

    上面看是没有什么问题,但是任何用户都能访问评论,如何只让登陆用户查看信息呢?这里我们就需要添加一个认证类

    在app01(应用名)目录下创建目录utils,在此目录下创建auth.py,用于放置自定义的认证类

    auth.py

    from app01 import models
    from rest_framework.authentication import BaseAuthentication
    # 导入处理REST框架引发的异常的模块
    from rest_framework.exceptions import AuthenticationFailed
    
    
    class MyAuth(BaseAuthentication):
    	# 重写BaseAuthentication里的方法
    	def authenticate(self, request):
    		if request.method in ["POST","PUT",]:
    			# 取token值
    			token = request.data.get("token")
    			# 去数据库查询有没有这个token
    			token_obj = models.Token.objects.filter(token=token).first()
    			if token_obj:
    				# token_obj有2个属性,详见models.py中的Token。
    				# return后面的代码,相当于分别赋值。例如a=1,b=2等同于a,b=1,2
    				# return多个值,返回一个元组
    				# 在rest framework内部会将这两个字段赋值给request,以供后续操作使用
    				return token_obj.user, token
    			else:
    				raise AuthenticationFailed("无效的token")
    		else:
    			return None, None  

      views.py下的CommentViewSet类:

    # app01.utils.auth表示app01目录下的utils下的auth.py
    from app01.utils.auth import MyAuth
    
    class CommentViewSet(ModelViewSet):
    	queryset = models.Comment.objects.all()
    	serializer_class = app01_serializers.CommentSerializer
    	authentication_classes = [MyAuth, ]  # 局部使用认证方法MyAuth
    

     验证:发送一个空的post请求或者错误的post请求

    发送正确的post请求:

    ## 以上只是做了一个局部的认证,对于全局认证:

    需要在setting中配置:

    REST_FRAMEWORK = {
        # 表示app01-->utils下的auth.py里面的MyAuth类
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ]
    }
    

     这时候CommentViewSet中的authentication_classes就可以注释掉了,效果也是一样的  

    2.权限

      自定义我们的权限,如只有让VIP用户才能看的内容

     注意:这里我把上面的认证权限设立在了全局,在settings里面设置全局认证,所有业务都需要经过认证,只有这样配置执行下面代码时你的permissions.py下的request才不会始终返回AnonymousUser(匿名用户)

    2.1 验证一

    permissions.py(在utils文件下下创建方认证文件)

    from rest_framework.permissions import BasePermission
    
    class MyPermission(BasePermission):
    	# 重写BasePermission里的方法,点击源码,里面只给了框架
    	def has_permission(self, request, view):
    		"""
    		判断该用户有没有权限
    		"""
    		print('我要进行自定义的权限判断啦....')
    		print(request)
    		print(request.user)
    		return True
    

     views.py(其他的不变动)

    from app01.utils.permissions import MyPermission
    class CommentViewSet(ModelViewSet):
    	queryset = models.Comment.objects.all()
    	serializer_class = app01_serializers.CommentSerializer
    	permission_classes = [MyPermission, ]
    

    发送get请求,request.user 打印值为None

    发送post请求,并携带之前用户登录加载的token值,request.user 打印值为UserInfo object

    2.2验证二(验证request到底是啥玩意)

    修改permission.py

    class MyPermission(BasePermission):
    	# 重写BasePermission里的方法,点击源码,里面只给了框架
    	def has_permission(self, request, view):
    		print('我要进行自定义的权限判断啦....')
    		print(request)
    		print(request.user)
    		print(request.user.username)
    		print(request.user.type)
    		return True
    

     再次发送相同的post请求,request.user.username打印值为携带当前token值的用户名

                request.user.type打印值为携带当前token值的用户的type

       Request有个user方法,加 @property 表示调用user方法的时候不需要加括号“user()”,可以直接调用:request.user

        在rest framework内部会将这两个字段赋值给request,以供后续操作使用

        return (token_obj.user,token_obj) 上面的return的值,来源于app01utilsauth.py里面的MyAuth类中的return token_obj.user, token

        通过认证之后,它会request进行再次封装,所以调用request.user时,得到了一个对象

        这个对象就是执行models.Token.objects.filter(token=token).first()的结果

        如果ORM没有查询出结果,它就一个匿名用户(AnonymousUser)!

    2.3 验证三(只有VIP用户登录才能操作内容)

       修改permission.py,仅限VIP用户登录操作文章内容

    permission.py

    from rest_framework.permissions import BasePermission
    
    class MyPermission(BasePermission):
    	# 重写BasePermission里的方法,点击源码,里面只给了框架
    	# 自定义输出语句
    	message = '快滚,没有权限!'
    	def has_permission(self, request, view):
    		if request.method in ['POST', 'PUT', 'DELETE']:
    			print(request.user.username)
    			print(request.user.type)
    			if request.user.type == 2:  # 是VIP用户
    				return True
    			else:
    				return False
    		else:
    			return True

    效果(VIP客户):

    comment内容:

    非VIP客户:

    2.4 验证四(使用普通用户的token发送delete类型的请求)

      重写BasePermission里的has_object_permission方法

    from rest_framework.permissions import BasePermission
    
    class MyPermission(BasePermission):
    	# 重写BasePermission里的方法,点击源码,里面只给了框架
    	# 自定义输出语句
    	message = '快滚,没有权限!'
    	def has_permission(self, request, view):
    		print("源码里面这个方法下啥也没有")
    		return True
    
    
    	# 源码中给的另一方法
    	def has_object_permission(self, request, view, obj):
    		"""
    		判断当前评论用户的作者是不是你当前的用户
    		只有是本人才能修改或者删除自己的评论
    		"""
    		print('这是在自定义权限类中的has_object_permission')
    		print(obj.id)
    		if request.method in ['PUT', 'DELETE']:
    			if obj.user == request.user:
    				# 当前要删除的评论的作者就是当前登陆的用户
    				return True
    			else:
    				return False
    		else:
    			return True  

     使用普通用户的token发送delete类型的请求:

    使用VIP用户的token发送delete类型的请求,返回结果为空,删除成功

    全局设置权限:

    在setting中配置:

    REST_FRAMEWORK = {
        # 表示app01-->utils下的auth.py里面的MyPermission类
      "DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.MyPermission", ]
    }
    

     再注释掉局部设置 

    3.限制(节流)

     对IP做限制,用户固定时间内只能访问固定次数

    在app01utils下面创建throttle.py

     throttle.py

    from rest_framework.throttling import BaseThrottle
    import time
    
    D = {}  # {'127.0.0.1': [1533302442, 1533302439,...]}
    
    class MyThrottle(BaseThrottle):
    	# 点进源码直接看当中携带的参数
    	def allow_request(self, request, view):
    		"""
            Return `True` if the request should be allowed, `False` otherwise.
            """
    		# 访问当前IP
    		ip = request.META.get('REMOTE_ADDR')
    		print(ip)
    		now = time.time()
    		if ip not in D:
    			D[ip] = []  # 初始化一个空的访问历史列表
    
    		history = D[ip]
    		# 当历史列表中有元素,并且当前时间戳减去最后一个元素的时间戳大于10
    		while history and (now - history[-1]) > 10:
    			history.pop()
    		# 判断最近10秒内的访问次数是否超过了阈值(3次)
    		if len(history) >= 3:
    			return False
    		else:
    			# 把这一次的访问时间加到访问历史列表的第一位
    			D[ip].insert(0,now)
    			return True
    

     views.py

    from app01.utils.throttle import MyThrottle
    class CommentViewSet(ModelViewSet):
    	queryset = models.Comment.objects.all()
    	serializer_class = app01_serializers.CommentSerializer
    	throttle_classes = [MyThrottle, ]   # 局部配置
    

     或全局配置,在setting中加入:

    REST_FRAMEWORK = {
         "DEFAULT_THROTTLE_CLASSES": ["app01.utils.throttle.MyThrottle", ]
    }  

     效果:使用postman发送GET请求,十秒内点击数大于三次就会提示你:

    等待10秒后又能使用

    3.1使用内置的SimpleRateThrottle类

     throttle.py

    from rest_framework.throttling import SimpleRateThrottle
    class MyThrottle(SimpleRateThrottle):
        scope = "rate"  # rate是名字,可以随便定义!
        def get_cache_key(self, request, view):
            return self.get_ident(request)  # 远程IP地址

     修改全局配置setting:

    REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": ["app01.utils.throttle.MyThrottle", ],
    "DEFAULT_THROTTLE_RATES": {
    	# rate对应的是throttle.py里面MyThrottle定义的scope属性的值
    	"rate": "3/m",  # 1分钟3次
    		}
    }
    

    效果:

    更过关于节流的代码原理性操作猛戳这里

  • 相关阅读:
    日志规范实践
    序列化和反序列化及Protobuf 基本使用
    简述TCP网络编程本质
    笔记:多线程服务器的适用场合(1)
    聊聊同步、异步、阻塞与非阻塞(转)
    《EntrePreneur》发刊词
    make和makefile简明基础
    luogu P3687 [ZJOI2017]仙人掌 |树形dp
    luogu P3172 [CQOI2015]选数 |容斥原理
    luogu P4513 小白逛公园 |线段树
  • 原文地址:https://www.cnblogs.com/LearningOnline/p/9416817.html
Copyright © 2020-2023  润新知