DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • Django

  • 第一次学drf

    • Postman和restful
    • APIView
    • Serializer
    • ModelSerializer
    • drf的请求与响应
    • drf的视图组件
    • drf的路由组件
    • 认证权限频率
      • Authentication
        • 登陆接口
        • 认证类
        • 编写
        • 局部/全局使用
      • Permissions
        • 权限类
        • 编写
        • 局部/全局使用
      • Throttling
        • request.META
        • 频率类
        • 编写
        • 局部/全局使用
        • get_ident方法
        • 自定义
    • 过滤排序分页异常
    • 源码分析
    • jwt
    • 大回顾
    • 零散的知识点
  • 第二次学drf

  • 温故知新

  • flask

  • 后端
  • 第一次学drf
DC
2023-08-10
目录

认证权限频率

认证就是判断用户是否登陆; 权限就是用户登陆后是否有权限访问这个接口.
注意: 只有继承了APIView及其子类的视图才会走 认证、权限、频率 的流程!!!

# Authentication

# 登陆接口

把随机字符串存入我们自己写的UserToken表,<如果存在就更新Token;如果不存在就新增>!

models.py

from django.db import models


class User(models.Model):
    name = models.CharField(max_length=32)
    password = models.CharField(max_length=32)


class UserToken(models.Model):
    # -- 简单的看下源码,本质就是外键关系,一对多、多对多的本质也是.
    #    OneToOneField继承了ForeignKey,在__init__里指定了kwargs['unique'] = True 唯一
    #    So,也可以这样写!(但不建议 Hhh 代码要简单明了)
    #    user = models.ForeignKey(to="User", on_delete=models.CASCADE, unique=True)
    user = models.OneToOneField(to="User", on_delete=models.CASCADE)
    token = models.CharField(max_length=32)


class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=32)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

serializer.py

from rest_framework import serializers
from . import models


class PublishSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Publish
        fields = '__all__'
1
2
3
4
5
6
7
8

views.py

"""
登陆功能就是一个登陆接口,前端输入用户名和密码,通过post请求提交到后端
一旦认证通过,就返回"登陆成功".并且附带一个随机字符串.
往后,但凡这个前端请求带着该随机字符串过来,就意味这登陆成功啦!
"""
from rest_framework.viewsets import ViewSet, ModelViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import User, UserToken, Publish
from .serializer import PublishSerializer
import uuid

class LoginView(ViewSet):
    @action(methods=['POST'], detail=False)
    def login(self, request):
        name = request.data.get('name')
        password = request.data.get('password')
        user = User.objects.filter(name=name, password=password).first()
        print(user)
        if not user:
            # -- 为了数据安全,不会单独细分是用户不存在还是密码错误.
            return Response({"code": 101, "msg": "用户名或密码错误!"})
        # -- 生成一个随机字符串
        #    可以取当前时间转化成str.
        #    但我们一般 借助uuid4来实现 - 通过伪随机数生成一个几乎永不重复的字符串
        token = str(uuid.uuid4())
        # -- 需求:把随机字符串存入我们自己写的UserToken表,<如果存在就更新Token;如果不存在就新增>!
        # 误解-这种方式不管存不存在都会增一条数据!!不妥.
        # UserToken.objects.create(user=user, token=token)
        # 正解-通过user=user去查,查到/查不到 的话, 更新/新增 defaults里的内容.
        UserToken.objects.update_or_create(defaults={"token": token}, user=user)
        return Response({"code": 100, "msg": "登陆成功!", "token": token})
      

class PublishView(ModelViewSet):
    queryset = Publish.objects.all()
    serializer_class = PublishSerializer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

urls.py

from django.contrib import admin
from django.urls import path
from app01 import views
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register('user', views.LoginView, basename='login')
router.register('publish', views.PublishView, basename='publish')

urlpatterns = [
    path('admin/', admin.site.urls),
]
urlpatterns += router.urls
1
2
3
4
5
6
7
8
9
10
11
12
13

# 认证类

想要出版社的五个接口登陆后才能使用!!
简单来说,认证就是判断用户是否登陆.

http://127.0.0.1:8000/publish/?token=82324421-6f62-4f4c-83d4-e932974eadff
通过该url的get请求可以拿到所有出版社的信息;
若该url中不带token,或者带的token值有误!! 会返回报错信息 {"detail": "token不合法,或者没有携带token!"} .

# 编写

auth.py
在app01下创建auth.py文件!

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


# -- 自定义认证类,要继承BaseAuthentication,重写authenticate.
#    若认证通过,返回两个值,第一个值必须是当前登陆用户;
#    若认证失败,抛出<认证失败>的异常!(提醒哈,异常是会被全局捕获的!)
#    ★ 特别注意一点,查看源码可知.
#      认证通过,返回两个值,不会再执行其他认证类的authenticate方法了.因为源码里会跳出for循环!
#      认证通过,不返回值或者是return None. 会继续走后续的认证类的authenticate方法!
class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        """
        认证逻辑:校验用户是否登陆 - 看它有没有带token来,以及token是不是合法的(是不是我给的!)
        """
        # -- 拿到token. PS:query_params等同于GET
        #    若请求的url地址中不带token,拿到的值是None
        # token = request.query_params.get('token')
        token = request.GET.get('token')
        # -- 校验token是否合法
        #    根据token去库中查询是否存在,如果存在就是登陆啦!放行.
        user_token = UserToken.objects.filter(token=token).first()
        if not user_token:
            raise AuthenticationFailed("token不合法,或者没有携带token!")
        # -- 若带的token是有效的,不会抛出异常,才会执行到这一步
        #    user_token.user是当前登陆的用户;返回的第二个值一般是token是随机字符串!
        #    ★★返回的第一个值user_token.user会给request.user!!源码决定的!
        return user_token.user, token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 局部/全局使用

局部配置和全局配置!!

views.py

from rest_framework.viewsets import ViewSet, ModelViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import User, UserToken, Publish
from .serializer import PublishSerializer
from .auth import LoginAuth
import uuid


class LoginView(ViewSet):
    # -- 切记,如果在settings.py里进行了认证类的全局配置,登陆接口需要禁用掉!
    #    因为查找顺序是先局部再全局然后再drf的认证配置文件里找.
    authentication_classes = []  # -- 不使用任何认证类!

    @action(methods=['POST'], detail=False)
    def login(self, request):
        name = request.data.get('name')
        password = request.data.get('password')
        user = User.objects.filter(name=name, password=password).first()
        print(user)
        if not user:
            return Response({"code": 101, "msg": "用户名或密码错误!"})
        token = str(uuid.uuid4())
        UserToken.objects.update_or_create(defaults={"token": token}, user=user)
        return Response({"code": 100, "msg": "登陆成功!", "token": token})


# -- 想要出版社的五个接口登陆后才能使用!!
class PublishView(ModelViewSet):
    queryset = Publish.objects.all()
    serializer_class = PublishSerializer
    # -- 局部配置:只针对当前这个PublishView类的五个接口有效!
    #    authentication_classes是APIView的源码里的类变量,用于配置认证类
    authentication_classes = [LoginAuth]
  

"""★★★ 
-- 全局配置需要在settings.py文件里配!!
-- settings.py
""" 
REST_FRAMEWORK = {
    # -- 认证类的全局配置
    #    注意!!如果全局使用了认证类,登陆接口需要局部禁用掉!
    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.auth.LoginAuth", ],
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# Permissions

# 权限类

认证通过后,拥有超级用户权限的用户才能访问这五个接口

实验开始之前,先给User表添加一个字段
user_type = models.IntegerField(choices=((1, '超级用户'), (2, '2B用户')), default=1)

# 编写

auth.py

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.permissions import BasePermission
from .models import UserToken


# -- 认证类
class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.GET.get('token')
        user_token = UserToken.objects.filter(token=token).first()
        if not user_token:
            raise AuthenticationFailed("token不合法,或者没有携带token!")
        return user_token.user, token


# -- 权限类
#    先记住一点,权限类在认证类之后执行!!能走到权限,一定是认证通过了!
#    继承BasePermission,重写has_permission.判断用户是否有权限.
#    如果有权限就返回True,没有权限就返回False.
class PermissionsUser(BasePermission):
    def has_permission(self, request, view):
        # -- 若有权限就返回True
        #    request.user就是认证类认证登陆成功后返回的第一个值user_token.user!!
        if request.user.user_type == 1:  # 注意是数字1,不是字符串1
            return True  # -- 超级用户允许访问
        else:
            return False
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 局部/全局使用

查找顺序: 视图类 - 项目配置文件 - drf的配置文件

views.py

from rest_framework.viewsets import ViewSet, ModelViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import User, UserToken, Publish
from .serializer import PublishSerializer
from .auth import LoginAuth, PermissionsUser
import uuid


class LoginView(ViewSet):pass


# -- 想要出版社的五个接口登陆后才能使用!!
class PublishView(ModelViewSet):
    queryset = Publish.objects.all()
    serializer_class = PublishSerializer
    # -- 认证类的局部配置:只针对当前这个PublishView类的五个接口有效!
    #    authentication_classes是APIView的源码里的类变量,用于配置认证类
    authentication_classes = [LoginAuth]
    # -- 权限类的局部配置:意味这这五个接口只有超级用户才能访问!
    permission_classes = [PermissionsUser]

    
"""★★★ 
-- 全局配置需要在settings.py文件里配!!
-- settings.py
"""
REST_FRAMEWORK = {
    # -- 认证类的全局配置
    #    注意!!如果全局使用了认证类,登陆接口需要局部禁用掉!
    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.auth.LoginAuth", ],
    # -- 权限类的全局配置
    "DEFAULT_PERMISSION_CLASSES": ["app01.auth.PermissionsUser", ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# Throttling

可以对接口访问的频次进行限制,以减轻服务器压力.
一般用于付费购买次数,投票等场景使用

若不使用频率类, 实现限制一个IP地址一分钟只能访问三次.如何做?
可自定义中间件, 在中间件里取出访问者的IP. request.META.get('REMOTE_ADDR') (不能通过用户限制,因为用户都还没登陆)
k值是ip地址,v值是列表,列表里面放时间.每次进来都放一个时间.比较时间,若没在一分钟以内 or 长度大于3, 进行限制!!

# request.META

用户从浏览器访问过来是一个Http请求.Http请求由请求首行、请求头、请求体组成.
http请求过来会先经过一个wsgi的web服务器,该服务器会先将请求的东西转成一个字典!!
接着进入Django框架,Django会将字典包装成request对象!!
若用户指定了一个特殊的请求头. eg: name-lqz 通过request.META.get('HTTP_NAME')取到!

from django.shortcuts import HttpResponse

def index(request):
    ip = request.META.get('REMOTE_ADDR')
    # -- request.META : 请求头中的所有数据
    #    注意!请求头的数据,会转成HTTP_大写.
    print(request.META.get('HTTP_NAME'))  # -- lqz
    return HttpResponse('您的IP是:' + ip)
1
2
3
4
5
6
7
8

项目可启动在0.0.0.0:8000地址上, 通过ifconfig查到启动的主机的ip.. 内网的主机都可通过该IP访问到!!
注意一点,还有在settings.py文件中进行一个配置 ALLOWED_HOSTS = ['*'] # -- 允许项目部署在任意地址上
注意,内网中的主机都公网IP都是同一个!!

# 频率类

同一IP一分钟只能访问三次

# 编写

auth.py

from rest_framework.throttling import SimpleRateThrottle


# -- 频率类
#    写一个频率限制类,根据ip限制一分钟内只能访问3次
# -- step1:写一个类继承SimpleRateThrottle
class MyThrottle(SimpleRateThrottle):
    # -- step2:定义一个类变量scope,值随便写,但要在配置文件settings中配置一个与之对应的值
    scope = 'ip_m_3'

    # -- step3:重写get_cache_key,这里返回了ip地址,就以ip地址做限制
    def get_cache_key(self, request, view):
        # -- return什么就以什么做限制
        #    这里返回了ip地址,就以ip地址做限制
        #    Ps:以用户id做频率限制 return request.user.id (频率在认证和权限之后,所以能取到)
        #       比如普通的注册用户一分钟只能使用一次这个接口,充了钱的用户一分钟可以使用20次.
        #    还可以通过手机号做频率限制.
        return request.META.get('REMOTE_ADDR')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 局部/全局使用

若 '3/m'的限流类进行了全局配置 . 依次访问了接口1、2、3, 接着再访问接口1, 会被限制吗? 会!
频率类可以配置多个, 不应该限制同一个, 有的限制ip、有的限制用户id..

views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from .auth import MyThrottle


class IndexView(APIView):
    # -- 使用频率类,局部配置
    throttle_classes = [MyThrottle]

    def get(self, request):
        return Response('ok')
      
      
"""★★★ 
-- 全局配置需要在settings.py文件里配!!
-- settings.py
"""
REST_FRAMEWORK = {
    # -- 频率类的全局配置
    'DEFAULT_THROTTLE_CLASSES': ['app01.auth.MyThrottle', ],
    # -- 频率类
    #    DEFAULT_THROTTLE_RATES 不管是全局or局部,都需要配
    'DEFAULT_THROTTLE_RATES': {
        # -- key值是频率类中scope变量的值;value值是访问次数的限制
        'ip_m_3': '3/m',
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# get_ident方法
from rest_framework.throttling import SimpleRateThrottle


# -- 频率类
class MyThrottle(SimpleRateThrottle):
    scope = 'ip_m_3'

    def get_cache_key(self, request, view):
        # return request.META.get('REMOTE_ADDR')
        # -- 同样的返回访问者的ip
        #    可查看源码 get_ident是BaseThrottle类里的方法!
        #    但get_ident更高级一些,它可以对代理进行判断!
        #    Ps:X_FORWARDED_FOR是http的请求头,它的作用是拿出所有ip,包括代理!
        return self.get_ident(request)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 自定义

自定义的写起来太麻烦了,所以我们选择继承SimpleRateThrottle

写一个频率类,限制ip一分钟只能访问三次 (继承BaseThrottle), 重写allow_reequest方法.
如果允许访问就返回True, 如果不允许就返回False.

自定义逻辑:
1> 取出访问者ip
2> 判断当前ip不在访问字典里, 添加进去, 并且直接返回True, 表示第一次访问, 在字典里, 继续往下走
3> 循环判断当前ip的列表, 有值, 并且当前时间减去列表的最后一个时间大于60s
     把这种数据pop掉, 这样列表中只有60s以内的访问时间
4> 判断, 当列表小于3, 说明一分钟以内访问不足三次, 把当前时间插入到列表第一个位置, 返回True, 顺利通过.
     当大于等于3, 说明一分钟内访问超过三次, 返回False验证失败

from rest_framework.throttling import BaseThrottle

# -- 自定义频率类
#    其实继不继承BaseThrottle都行,继承了就是接口的概念,不继承就是鸭子类型..Hhh.
class MyThrottles(BaseThrottle):
    VISIT_RECORD = {}

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # 1)取出访问者ip
        # print(request.META)
        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掉,这样列表中只有60s以内的访问时间
        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])
     
    
"""
全局/局部使用同理
"""
# -- 全局使用
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES':['app01.utils.MyThrottles',],
}

# -- 在视图类里使用
throttle_classes = [MyThrottles,]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

一点回顾

-- drf:方便我们在Django框架上写出符合restful规范的接口.
-- APIview在view的基础上做了三件事: 包装新的(请求类的对象request)、三大认证、全局捕获异常
-- 请求和响应
   请求
   - 请求类的对象request: request._request 、新旧用法一样,__getattr__ 、request.data
   - 请求解析的编码格式: 局部、全局
   响应
   - Response()的参数: data、status、header
   - 响应格式: 浏览器、json
-- 序列化类
   - Serializer
   - ModelSerializer(用的多)
-- 视图类
   - 两个视图基类
     APIView: 继承它的所有类都会经历三个流程!
     GenericAPIView: 两个类属性,三个方法
                     五个接口都可以重写get_serializer_class方法来使用不同的序列化类
   - 五个视图扩展类(不是视图类,需要配置GenericAPIView来使用)
   - 九个视图子类
   - 视图集
     ModelViewSet
     ReadOnlyModelViewSet
     ViewSet
     GenericViewSet
     ViewSetMixin:改变了路由写法
-- 路由
   - 自动生成路由(有个前提)
     DefaultRouter
     SimpleRouter
   - action装饰器
-- 认证类:用户是否登陆
-- 权限类:用户是否有权限访问这个接口
-- 频率类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

drf的路由组件
过滤排序分页异常

← drf的路由组件 过滤排序分页异常→

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式