认证权限频率
认证就是判断用户是否登陆; 权限就是用户登陆后是否有权限访问这个接口.
注意: 只有继承了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)
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__'
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
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
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
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", ],
}
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
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", ]
}
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)
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')
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',
}
}
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)
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,]
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装饰器
-- 认证类:用户是否登陆
-- 权限类:用户是否有权限访问这个接口
-- 频率类
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