drf频率源码、自动生成接口文档、JWT

一、drf频率源码分析

from rest_framework.throttling import SimpleRateThrottle


# 在频率限制中最重要的方法就是allow_request
# 可以直接去频率类的顶级父类里找,告诉我们如果要写频率限制,就必须重写这个方法,返回值True/False
# wait返回的数字是在前端展示的还差的时间
# 获取rate的方法
 def __init__(self):
        if not getattr(self, 'rate', None):
            self.rate = self.get_rate()
        self.num_requests, self.duration = self.parse_rate(self.rate)
# 所以在分析内置的SimpleRateThrottle频率限制模块的时候就看这个方法········
    def allow_request(self, request, view):
		# 根据配置文件的DEFAULT_THROTTLE_RATES中的scope获取频率配置
        # scope不是写死的,如果我们在继承的类属性中写了scope=‘xxx’那么去配置信息找的时候就是xxx:3/m
        # 我们配置的3/m
        # 将这个字符串分成次数和时间,分别存放到self.num_requests和self.duration中
        if self.rate is None:
            return True
		# 调用get_cache_key方法,这个类中没有写这个方法
        # 也就是我们要使用这个类就必须调用这个方法
        # 这个方法的返回值就是我们限制频率的约束如ip,username,如果返回None就不限制
        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True
		# 把访问信息添加到缓存当中
        self.history = self.cache.get(self.key, [])
        self.now = self.timer()
		# 根据时间判断是否超出限制
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            # 列表长度如果超出我们限定的就返回错误
            return self.throttle_failure()
        # 如果没超出就返回True并把列表保存到缓存中
        return self.throttle_success()

二、自动生成接口文档

REST framework可以自动帮助我们生成接口文档。

接口文档以网页的方式呈现。

自动接口文档能生成的是继承自APIView及其子类的视图。

1 安装依赖

REST framewrok生成接口文档需要coreapi库的支持。

pip install coreapi

2 设置接口文档访问路径

在总路由中添加接口文档路径。

文档路由对应的视图配置为rest_framework.documentation.include_docs_urls

参数title为接口文档网站的标题。

from rest_framework.documentation import include_docs_urls

urlpatterns = [
    ...
    path('docs/', include_docs_urls(title='站点页面标题'))
]

3 文档描述说明的定义位置

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:
    修改图书的阅读量
    """

4 访问接口文档网页

浏览器访问 127.0.0.1:8000/docs/,即可看到自动生成的接口文档。

image-20200714155134704

三、JWT

1 JWT基本原理

参考:http://liuqingzheng.top/python/Django-rest-framework框架/9-drf-JWT认证/

JWT = Json Web Token,本质上就是token

分布式和集群

分布式是指通过网络连接的多个组件,通过交换信息协作而形成的系统。而集群,是指同一种组件的多个实例,形成的逻辑上的整体。

简单来说集群就是,项目所用的所有功能,前后端,数据库,缓存都放在一个机器上,然后多放几个这样的机器,上面的项目都是一样的

分布式就是我把一个项目用的东西分别放在不同的机器上,a机器装数据库,b机器放后端代码等等

所以集群相对于分布式有负载均衡的效果,一台机子宕机了,这个站点也能继续被访问,而分布式一处死了,就完全没法继续了

jwt的应用场景:当我们用分布式的项目的时候,如果是统一的用一个数据库,那没问题,如果是各个机器用自己的数据库,那如果我在a机器上注册账号,登录了,下次如果访问的是b服务器就又得重新登录,因为我的登录状态不在这个机子上。

jwt认证可以让客户端发送的数据有服务端的特性,检验这种特性来判断是否是这个服务端的数据,再根据特定的规则解码得到我们需要的数据。

jwt数据构成

JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

1.1 header

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

1.2 payload

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避时序攻击。

公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后将其进行base64加密,得到JWT的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

1.3 signature

JWT的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

关于签发和核验JWT,我们可以使用Django REST framework JWT扩展来完成。

1.4 认证算法:签发与校验

"""
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": "安全码"
}
"""

签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token

"""
1)用基本信息存储json字典,采用base64算法加密得到 头字符串
2)用关键信息存储json字典,采用base64算法加密得到 体字符串
3)用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串

账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
"""

校验:根据客户端带token的请求 反解出 user 对象

"""
1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""

2 jwt的基本使用

安装:pip install djangorestframework-jwt

# 路由配置
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    path('login/', obtain_jwt_token),
]
# settings注册app
# 在postman发送JSON格式的注册的用户账号和密码
{
    "username": "hz",
    "password":"hz123456"
}
# 返回的jwt数据
{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Imh6IiwiZXhwIjoxNTk0NzE1MTEyLCJlbWFpbCI6IiJ9.3Qos0Ua0hZfK2Nn3P0jR6yxNSrBeloIVplbUBZILUpM"
}
# 在我们要访问设置的认证配置的url时,要注意必须在请求头中设置
Authorization : JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Imh6IiwiZXhwIjoxNTk0NzE1MTEyLCJlbWFpbCI6IiJ9.3Qos0Ua0hZfK2Nn3P0jR6yxNSrBeloIVplbUBZILUpM"
# 注意这里是JWT空格+token信息,这是jwt认证写死的,必须按照这个规范来

3 自定制auth认证类

from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
# 重写父类的authenticate即可
class MyToekn(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        jwt_value = get_authorization_header(request).split()[1]
        # jwt_value = str(request.META.get('HTTP_AUTHORIZATION'))

        if not jwt_value:
            raise AuthenticationFailed('Authorization 字段是必须的')
        try:
            payload = jwt_decode_handler(jwt_value)
        except Exception:
            raise AuthenticationFailed('认证失败')

        user = self.authenticate_credentials(payload)

        return user, jwt_value

posted @ 2020-07-14 18:09  lxttt521  阅读(261)  评论(0编辑  收藏  举报