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/,即可看到自动生成的接口文档。
三、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