DRF频率、自动生成接口文档、jwt简单使用

DRF

#1.表关系
	OneToOneField本质其实就是foreignkey加unique
	on_delete :
    	CASCADE:这就是默认的选项,级联删除,你无需显性指定它。
        PROTECT: 保护模式,如果采用该选项,删除的时候,会抛出ProtectedError错误。
        SET_NULL: 置空模式,删除的时候,外键字段被设置为空,前提就是blank=True, null=True,定义该字段的时候,允许为空。
        SET_DEFAULT: 置默认值,删除的时候,外键字段设置为默认值,所以定义外键的时候注意加上一个默认值。
        SET(): 自定义一个值,该值当然只能是对应的实体了
    字段建索引,字段唯一
    联合索引联合唯一
    日期类型 auto_now_add 记录创建时间   
    	auth_now 修改时间
#2.book 
	单条查询 多条查询 :
		写在一个视图类,配路由时候,转到这  取出kwargs判断有没有pk 有就是单条 没有就是多条
    单条新增 多条新增:
    	直接把内容反序列化然后判断is_valid一下,调save保存 在return 一个Response一个对象
     单条修改 多条修改:
    	调用序列化器时候list_serializer_class = MyListSerializer 会走自己定义的MyListSerializer方法里面的update	
    单条删除,多条删除:
    	要注意删除修改(is_delete)
#3.频率
	自定义频率 (限制ip、id)
    -第一步:写一个类,继承SimpleRateThrottle,重写get_cache_key
    -第二步:get_cache_key返回什么就以什么做限制,
    必须写类属性 scope='字符串'限制了setting里
    -第三步:配置文件中配置
      'DEFAULT_THROTTLE_RATES': {
        '字符串': '3/m',  # key:ip_1m_3 对应频率类的scope属性, value: 3/m 一分钟访问3次
#4、分页
   PageNumberPagination,
   LimitOffsetPagination
   CursorPagination

1.频率SimpleRateThrottle源码分析

#先寻找allow_request       
	if self.rate is None:
            return True

#在进入他里面的__init__ 
def __init__(self):
        if not getattr(self, 'rate', None):
            self.rate = self.get_rate()
        self.num_requests, self.duration = self.parse_rate(self.rate)

通过反射去get_rate里面获取到了return self.THROTTLE_RATES[self.scope]  配置文件找key=scope的对应的value(3/min),回到init self.rate=3/min,在通过parse_rate把rate分别给num_requests和duration
再次回到allow_request 获取get_cache_key =key 以什么做限制,在通过获取self.history=self.cache.get(限制条件,[])  这句话意思是能取到值就是history  取不到就是空列表
然后再通过self.time() 获取当前时间  time里面是time.time的内存地址
然后 判断history有没有值并且当前时间now减去最后一次的时间-history[-1]大于或者等于>=duration  pop出去   
然后又判断了一下history长度 是否大于num_requests 这个是settings配置的3/min 的3如果大于  直接走throttle_failure 方法 直接return了false
最下面retrun  这个throttle_success
    def throttle_success(self):

        self.history.insert(0, self.now) #当前时间加入到history
        self.cache.set(self.key, self.history, self.duration) #缓存加入
        return True 

2.自动生成接口文档

#给前端写一个文档 
#安装
1.pip3 install coreapi
2 配置路径 主路由
from rest_framework.documentation import include_docs_urls

urlpatterns = [
    ...
    path('docs/', include_docs_urls(title='站点页面标题'))
]
3.文档说明 视图类:自动生成接口文档的是继承APIView及其子类的视图
	1)单一方法的视图,可直接使用类视图的文档字符串,如
 
class BookListView(generics.ListAPIView):
    """
    返回所有图书信息.
    """
	2)包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如
class BookListCreateView(generics.ListCreateAPIView):
    """
    get:
    返回所有图书信息.

    post:
    新建图书.
    """
	3)对于视图集ViewSet,仍在类视图的文档字符串中封开定义,但是应使用action名称区分,如
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    """
    list:
    返回图书列表数据

    retrieve:
    返回图书详情数据

    latest:
    返回最新的图书数据

    read:
    修改图书的阅读量
    """
    
#如果报错 settings加入
#AttributeError: 'AutoSchema' object has no attribute 'get_link'
REST_FRAMEWORK = {
 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    # 新版drf schema_class默认用的是rest_framework.schemas.openapi.AutoSchema

}

3.jwt

3.1jwt简单介绍

1.什么是集群: 相同代码部署在多台机器上

2.加密
"""
1)jwt分三段式:头.体.签名 (head.payload.sgin)
2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
	"company": "公司信息",
	...
}
5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
	"user_id": 1,
	...
}
6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
	"head": "头的加密字符串",
	"payload": "体的加密字符串",
	"secret_key": "安全码"
}
"""
3.校验
"""
1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""
4.drf项目的jwt认证开发流程(重点)
"""
1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中

2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户

注:登录接口需要做 认证 + 权限 两个局部禁用
"""

第三方写好的:django-rest-framework-jwt

3.2安装jwt

pip3 install djangorestframework-jwt -i https://pypi.douban.com/simple/

3.3使用jwt

1.models 扩展user表 继承AbstractUser   数据库没迁移之前的项目
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    phone=models.CharField(max_length=32)
    #image依赖于pillow模块
    icon=models.ImageField(upload_to='icon')

2.配置文件设置
    #一定不要忘记 app名字.表名小写
    AUTH_USER_MODEL='app01.user'   后面指定的是APP名字
    #配置图片路径
    MEDIA_URL='/media/'
    #配置图片文件夹
    MEDIA_ROOT=os.path.join(BASE_DIR,'media')

3.数据库迁移
	makemigrations
	migrate
4.创建超级用户
	creatsuperuser
5.简单快速使用(路由层配置)
	from rest_framework_jwt.views import ObtainJSONWebToken,VerifyJSONWebToken,\
    RefreshJSONWebToken,obtain_jwt_token
#基类:JSONWebTokenAPIView
#ObtainJSONWebToken,VerifyJSONWebToken,RefreshJSONWebToken都继承了JSONWebTokenAPIView
urlpatterns = [
    path('admin/', admin.site.urls),
    #下面的这两个路由一个意思
    # path('login/', ObtainJSONWebToken.as_view()),
    path('login/', obtain_jwt_token),
]
6.其他设置
settings app里面要加入
'rest_framework_jwt'
然后下面全局配置认证
REST_FRAMEWORK={
    'DEFAULT_AUTHENTICATION_CLASSES':['rest_framework_jwt.authentication.JSONWebTokenAuthentication']
}
7.写一个认证类

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class BookView(APIView):
    def get(self,request):
        #局部配置
        # 只写这一个认证类,如果没有携带token,并不会限制,需要搭配一个权限类,来完成登录认证
        authentication_class=[JSONWebTokenAuthentication,]
        # 权限类
        permission_classes = [IsAuthenticated,]
        return Response('ok')
    
8测试

3.4jwt源码部分

3.5drf-jwt修改签发响应格式

# 写一个函数,函数的返回值,就是响应的格式
# 在配置文件中配置一下

def jwt_response_payload_handler(token, user=None, request=None):
    return {
        'code': 100,
        'msg': '登录成功',
        'token': token,
        'username':user.username

    }

在配置文件中配置
JWT_AUTH = {
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.common.jwt_response_payload_handler',
}

3.6drf-jwt自定义用户表签发token

# 自定义签发token
from app01.models import User
from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class Login(APIView):
    def post(self, request):
        response = {'code': 101, 'msg': '用户名或者密码错误'}
        username = request.data.get('username')
        password = request.data.get('password')
        user = User.objects.filter(username=username, password=password).first()
        if user:
            # 登陆成功签发token  去路由层找obtain_jwt_token,最终找到了JSONWebTokenAPIView里的post方法
            # 登录成功,签发token,通过当前登录用户获取荷载(payload)
            payload = jwt_payload_handler(user)
            # 通过payload生成token串(三段:头,payload,签名)
            token = jwt_encode_handler(payload)
            # 有这个用户状态码信息修改成正常的
            response['code'] = 100
            response['msg'] = '登陆成功—自定义的'
            response['token'] = token

        return Response(response)

#自定义签发token,无法使用自带的认证类,因为自带的认证类return的user是auth_user,
#所以需要自己写认证类

3.7自定制认证类

# 自定义认证类
from rest_framework.authentication import BaseAuthentication
from rest_framework_jwt.settings import api_settings
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
import jwt
from rest_framework.exceptions import AuthenticationFailed
from .models import User
class MyJwtAuthentication(BaseAuthentication):
    def authenticate(self, request):
        #1.取出token
        token=request.META.get('HTTP_TOKEN')
        #2.验证token是否合法
        if token:
            try:
                payload = jwt_decode_handler(token)
            except jwt.ExpiredSignature:
                raise AuthenticationFailed('签名过期')
            except jwt.DecodeError:
                raise AuthenticationFailed('签名认证失败')
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('未知错误')
            print(payload)
            #认证一次就需要走一次数据库 效率低,
            user=User.objects.get(pk=payload['user_id'])
            #效率上来了 但是可能会有问题,以后request.user是用户id 如果想用其他属性,
            # 需要通过id查找对象 在点属性
            # user=User(id=payload['user_id'],username=payload['username'])
            return (user, token)
        else:
            raise AuthenticationFailed('没有携带token')

    


# 全局使用,在配置文件中配置
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.JwtAuthentication'], # 全局生效

}
# 全局使用后,局部禁用
class LoginView(APIView):
    authentication_classes = []
    
    
# 注意:配置的所有认证,权限,频率。。。优先用视图类自己的,再用配置文件的,最后用drf内置的



# 补充:token过期时间很快,改改过期时间,配置7天过期
JWT_AUTH = {
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.common.jwt_response_payload_handler',
    # 过期时间7天
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
}

posted @ 2022-01-20 16:40  迪迦张  阅读(171)  评论(1编辑  收藏  举报