目录
- 一、接口文档
- 二、cookie session token 发展史(彻底理解cookie,session,token,便于理解jwt)
- 三、jwt介绍和原理
- 四、drf-jwt快速使用
- 五、定制返回格式
- 六、jwt的认证类
- 七、作业
一、接口文档
在前后端分离的web应用模式下,我们编写后端只需要编写接口,前端根据我们的接口编写各式各样的前端界面。
作为后端,我们十分清除自己编写的各种接口的作用,以及接口的要求,但是前端人员并不知道,因此我们需要编写接口文档,让前端可以明白需要往什么接口发送请求,请求需要符合的要求。
举例:登陆接口的要求
-作为后端来讲,我们很清楚,比如登录接口 /api/v1/login/---->post---->变量名称username,password 编码方式json----》返回的格式 {code:100,msg:登录成功}
在公司里面写API接口文档的方式有三种:
# 接口文档如何编写
-1 使用word,md 编写接口文档
-2 使用第三方平台(比如showdoc),编写我们的接口文档(非常多)---》收费
-https://www.showdoc.com.cn/item/index
-3 公司自己使用第三方开源的搭建的---》Yapi ---》你如果想自己搭建(去看教程)
-https://zhuanlan.zhihu.com/p/366025001
-4 使用drf编写的接口,可以自动生成接口文档
-swagger---》drf-yasg---》官方推荐使用
-coreapi----》咱们讲的是这个
接口文档,需要有的东西
-描述
-地址
-请求方式
-请求编码格式
-请求数据详解(必填,类型)
-返回格式案例
-返回数据字段解释
-错误码
使用coreapi自动生成接口文档步骤
- 步骤一:安装coreapi模块
pycharm下载或是
pip install coreapi
- 步骤二:配置路由
在总路由中添加接口文档路径。
文档路由对应的视图配置为rest_framework.documentation.include_docs_urls
,
参数title
为接口文档网站的标题。
from rest_framework.documentation import include_docs_urls
urlpatterns = [
...
path('docs/', include_docs_urls(title='站点页面标题'))
]
- 步骤三:在视图类或视图集的内部写上注释
我们可以视图类或是视图类中的类方法内加注释,让他接口文档中进行显示,写在类中的注释会出现在接口文档的每一个方法内,写在类方法内的注释会出现在接口文档中该方法所在的位置
如果我们想要修改接口文档中的序列化类或是模型表中的字段的名称或限制条件,可以在字段中进行修改,接口文档中也会显示出来(help_text:给字段配置注释,required:不能为空约束)。
- 步骤四:配置文件中添加配置信息
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
- 步骤五:访问地址,查看接口文档
http://127.0.0.1:8000/docs
代码
urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework.documentation import include_docs_urls
from rest_framework.routers import SimpleRouter
from app01 import views
router = SimpleRouter()
router.register('books', views.BookView, 'books')
urlpatterns = [
path('admin/', admin.site.urls),
# 这是接口文档的路由
path('docs/', include_docs_urls(title='zzh的项目接口文件')),
# 这是视图类自动匹配的路由
path('api/v1/', include(router.urls)),
]
models.py
两个def方法在数据库迁移的时候要先注释掉
from django.db import models
# 图书跟作者:多对多,需要建立中间表,但是我们可以通过ManyToManyField自动生成,写在哪里都行
# 图书跟出版社:一对多,一个出版社,出版多本书,关联字段写在多的一方,写在Book
class Book(models.Model):
name = models.CharField(max_length=32, default='xx', help_text='图书名字')
price = models.IntegerField()
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE) # 留住,还有很多
authors = models.ManyToManyField(to='Author')
def publish_detail(self):
return {'name': self.publish.name, 'addr': self.publish.addr}
def author_list(self):
l = []
for author in self.authors.all():
l.append({'name': author.name, 'phone': author.phone})
return l
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
phone = models.CharField(max_length=11)
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
user_type = models.IntegerField(choices=((1, '超级管理员'), (2, '普通用户'), (3, '2B用户')), default=2)
class UserToken(models.Model): # 跟User是一对一
token = models.CharField(max_length=32)
user = models.OneToOneField(to='User', on_delete=models.CASCADE, null=True)
# user :反向,表名小写,所有有user字段
serializer.py
这是序列化类,收发请求会用到所以要写
from rest_framework import serializers
from .models import Book, Author, Publish
from rest_framework.exceptions import ValidationError
class BookSerializer(serializers.ModelSerializer):
# 跟表有关联
class Meta:
model = Book
fields = ['id', 'name', 'price', 'publish_detail', 'author_list', 'publish', 'authors']
extra_kwargs = {
'id': {'help_text': 'id号'},
'name': {'max_length': 8},
'publish_detail': {'read_only': True},
'author_list': {'read_only': True},
'publish': {'write_only': True},
'authors': {'write_only': True},
}
settings.py
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
'我们要在浏览器中显示接口文档还需要注册drfapp'
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'rest_framework',
]
views.py
from django.shortcuts import render
from rest_framework.generics import ListAPIView
from .models import Book
from .serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet
# Create your views here.
'这里的注释必须写在代码的最上方,否则可能不会显示'
class BookView(ModelViewSet):
"""
你是猪
"""
queryset = Book.objects.all()
serializer_class = BookSerializer
两点说明
1) 视图集ViewSet中的retrieve名称,在接口文档网站中叫做read
2)参数的Description需要在模型类或序列化器类的字段中以help_text选项定义,如:
class Student(models.Model):
...
age = models.IntegerField(default=0, verbose_name='年龄', help_text='年龄')
...
或
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"
extra_kwargs = {
'age': {
'required': True,
'help_text': '年龄'
}
}
二、cookie session token 发展史(彻底理解cookie,session,token,便于理解jwt)
1、Cookie,Session,Token发展史
无cookie时期
很久很久以前,Web 基本上就是文档的浏览而已, 既然是浏览,作为服务器, 不需要记录谁在某一段时间里都浏览了什么文档,每次请求都是一个新的HTTP协议, 就是请求加响应, 尤其是我不用记住是谁刚刚发了HTTP请求, 每个请求对我来说都是全新的。这段时间很嗨皮
cookie+session认证时期
但是随着交互式Web应用的兴起,像在线购物网站,需要登录的网站等等,马上就面临一个问题,那就是要管理会话,必须记住哪些人登录系统, 哪些人往自己的购物车中放商品, 也就是说我必须把每个人区分开,这就是一个不小的挑战,因为HTTP请求是无状态的,所以想出的办法就是给大家发一个会话标识(session id), 说白了就是一个随机的字串,每个人收到的都不一样, 每次大家向我发起HTTP请求的时候,把这个字符串给一并捎过来, 这样我就能区分开谁是谁了
cookie+session存在问题及解决
这样大家很嗨皮了,可是服务器就不嗨皮了,每个人只需要保存自己的session id,而服务器要保存所有人的session id ! 如果访问服务器多了, 就得由成千上万,甚至几十万个。
这对服务器说是一个巨大的开销 , 严重的限制了服务器扩展能力, 比如说我用两个机器组成了一个集群, 小F通过机器A登录了系统, 那session id会保存在机器A上, 假设小F的下一次请求被转发到机器B怎么办? 机器B可没有小F的 session id啊。
有时候会采用一点小伎俩: session sticky , 就是让小F的请求一直粘连在机器A上, 但是这也不管用, 要是机器A挂掉了, 还得转到机器B去。
那只好做session 的复制了, 把session id 在两个机器之间搬来搬去, 快累死了。
后来有个叫Memcached的支了招: 把session id 集中存储到一个地方, 所有的机器都来访问这个地方的数据, 这样一来,就不用复制了, 但是增加了单点失败的可能性, 要是那个负责session 的机器挂了, 所有人都得重新登录一遍, 估计得被人骂死。
也尝试把这个单点的机器也搞出集群,增加可靠性, 但不管如何, 这小小的session 对我来说是一个沉重的负担
Token认证
于是有人就一直在思考, 我为什么要保存这可恶的session呢, 只让每个客户端去保存该多好?
可是如果不保存这些session id , 怎么验证客户端发给我的session id 的确是我生成的呢? 如果不去验证,我们都不知道他们是不是合法登录的用户, 那些不怀好意的家伙们就可以伪造session id , 为所欲为了。
嗯,对了,关键点就是验证 !
比如说, 小F已经登录了系统, 我给他发一个令牌(token), 里边包含了小F的 user id, 下一次小F 再次通过Http 请求访问我的时候, 把这个token 通过Http header 带过来不就可以了。
不过这和session id没有本质区别啊, 任何人都可以可以伪造, 所以我得想点儿办法, 让别人伪造不了。
那就对数据做一个签名吧, 比如说我用HMAC-SHA256 算法,加上一个只有我才知道的密钥, 对数据做一个签名, 把这个签名和数据一起作为token , 由于密钥别人不知道, 就无法伪造token了。
这个token 我不保存, 当小F把这个token 给我发过来的时候,我再用同样的HMAC-SHA256 算法和同样的密钥,对数据再计算一次签名, 和token 中的签名做个比较, 如果相同, 我就知道小F已经登录过了,并且可以直接取到小F的user id , 如果不相同, 数据部分肯定被人篡改过, 我就告诉发送者: 对不起,没有认证。
Token 中的数据是明文保存的(虽然我会用Base64做下编码, 但那不是加密), 还是可以被别人看到的, 所以我不能在其中保存像密码这样的敏感信息。
当然, 如果一个人的token 被别人偷走了, 那我也没办法, 我也会认为小偷就是合法用户, 这其实和一个人的session id 被别人偷走是一样的。
这样一来, 我就不保存session id 了, 我只是生成token , 然后验证token , 我用我的CPU计算时间获取了我的session 存储空间 !
解除了session id这个负担, 可以说是无事一身轻, 我的机器集群现在可以轻松地做水平扩展, 用户访问量增大, 直接加机器就行。 这种无状态的感觉实在是太好了!
2、Cookie,Session解释
2.1 Cookie
cookie 是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。
cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的cookie数量是有限的。
2.2 Session
session 从字面上讲,就是会话。这个就类似于你和一个人交谈,你怎么知道当前和你交谈的是张三而不是李四呢?对方肯定有某种特征(长相等)表明他就是张三。
session 也是类似的道理,服务器要知道当前发请求给自己的是谁。为了做这种区分,服务器就要给每个客户端分配不同的“身份标识”,然后客户端每次向服务器发请求的时候,都带上这个“身份标识”,服务器就知道这个请求来自于谁了。至于客户端怎么保存这个“身份标识”,可以有很多种方式,对于浏览器客户端,大家都默认采用 cookie 的方式。
服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。这种用户信息存储方式相对cookie来说更安全,可是session有一个缺陷:如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。
2.3 cookie和session的区别
session是存储服务器端,cookie是存储在客户端,所以session的安全性比cookie高
获取session里的信息是通过存放在会话cookie里的session id获取的。而session是存放在服务器的内存中里,所以session里的数据不断增加会造成服务器的负担,所以会把很重要的信息存储在session中,而把一些次要东西存储在客户端的cookie里。
session的信息是通过sessionid获取的,而sessionid是存放在会话cookie中
当浏览器关闭的时候会话cookie消失,所以sessionid也就消失了,但是session的信息还存在服务器端,只是查不到所谓的session,但它并不是不存在
3、Token介绍
在Web领域基于Token的身份验证随处可见。在大多数使用Web API的互联网公司中,tokens 是多用户下处理认证的最佳方式。
以下几点特性会让你在程序中使用基于Token的身份验证
无状态、可扩展
支持移动设备
跨程序调用
安全
那些使用基于Token的身份验证的大佬们:大部分你见到过的API和Web应用都使用tokens。例如Facebook, Twitter, Google+, GitHub等。
在介绍基于Token的身份验证的原理与优势之前,不妨先看看之前的认证都是怎么做的。
3.1 传统方式——基于服务器的验证
由于 HTTP 协议是无状态的,这种无状态意味着程序需要验证每一次请求,从而辨别客户端的身份。在这之前,程序都是通过在服务端存储登录的用户信息来辨别身份的。这种方式一般都是通过存储 session 来完成,可放在内存或磁盘上。
随着Web,应用程序,已经移动端的兴起,这种验证的方式逐渐暴露出了问题。尤其是在可扩展性方面
3.2 基于服务器验证方式的问题
3.2.1 Seesions
每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加。
3.2.2 可扩展性
由于sessions 存放在服务器内存中,伴随而来的是可扩展性问题。当我们想要增加服务器来解决负载问题时,session 里的关键性信息会限制我们的扩展。
3.2.3 CORS (跨域资源共享)
当我们扩展应用程序,让数据能够从不同设备上访问时,跨域资源的共享会是一个让人头疼的问题。在使用 Ajax 抓取另一个域的资源时(移动端访问我们的 API 服务器),可能会出现禁止请求的情况。
3.2.4 CSRF (跨站请求伪造)
用户在访问银行网站时,他们很容易受到跨站请求伪造的攻击,并且能够被利用其访问其他的网站。
在这些问题中,可扩展性是最突出的。因此我们有必要去寻求一种更有行之有效的方法。
3.3 基于Token的验证原理
基于 Token 的身份验证是无状态的,我们不用将用户信息存在服务器或 Session 中。这种概念解决了在服务端存储信息时的许多问题。没有 session 信息意味着你的程序可以根据需要去增减机器,而不用去担心用户是否登录和已经登录到了哪里。
虽然基于Token的身份验证实现的方式很多,但大致过程如下:
1 用户通过用户名和密码发送请求
2 程序验证
3 程序返回一个签名的 token 给客户端
4 客户端储存 token, 并且每次请求都会附带它
5 服务端验证 token 并返回数据
每一次请求都需要Token。Token 应该在 HTTP的头部发送从而保证了 Http 请求无状态。我们也需要设置服务器属性
Access-Control-Allow-Origin: *
来让服务器能接受到来自所有域的请求。需要注意的是,在ACAO头部指定 * 时,不得带有像HTTP认证,客户端SSL证书和cookies的证书。
实现思路:
1.用户登录校验,校验成功后就返回Token给客户端。
2.客户端收到数据后保存在客户端
3.客户端每次访问API是携带Token到服务器端。
4.服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码
当我们在程序中认证了信息并取得 token 之后,我们便能通过这个 token 做许多的事情。我们甚至能基于创建一个基于权限的token传给第三方应用程序,这些第三方程序能够获取到我们的数据(当然只限于该 token 被允许访问的数据)
原理图如下:
3.4 Token的优势
3.4.1 无状态、可扩展
在客户端存储的 token 是无状态的,并且能够被扩展。基于这种无状态和不存储Session信息,负载均衡服务器 能够将用户的请求传递到任何一台服务器上,因为服务器与用户信息没有关联。相反在传统方式中,我们必须将请求发送到一台存储了该用户 session 的服务器上(称为Session亲和性),因此当用户量大时,可能会造成 一些拥堵。使用 token 完美解决了此问题。
3.4.2 安全性
请求中发送 token 而不是 cookie,这能够防止 CSRF(跨站请求伪造) 攻击。即使在客户端使用 cookie 存储 token,cookie 也仅仅是一个存储机制而不是用于认证。另外,由于没有 session,让我们少我们不必再进行基于 session 的操作。
Token 是有时效的,一段时间之后用户需要重新验证。我们也不一定需要等到token自动失效,token有撤回的操作,通过 token revocataion可以使一个特定的 token 或是一组有相同认证的 token 无效。
3.4.3 可扩展性
使用 Tokens 能够与其它应用共享权限。例如,能将一个博客帐号和自己的QQ号关联起来。当通过一个 第三方平台登录QQ时,我们可以将一个博客发到QQ平台中。
使用 token,可以给第三方应用程序提供自定义的权限限制。当用户想让一个第三方应用程序访问它们的数据时,我们可以通过建立自己的API,给出具有特殊权限的tokens。
3.4.4 多平台与跨域
我们已经讨论了CORS (跨域资源共享)。当我们的应用和服务不断扩大的时候,我们可能需要通过多种不同平台或其他应用来接入我们的服务。
可以让我们的API只提供数据,我们也可以从CDN提供服务(Having our API just serve data, we can also make the design choice to serve assets from a CDN.)。 在为我们的应用程序做了如下简单的配置之后,就可以消除 CORS 带来的问题。只要用户有一个通过了验证的token,数据和资源就能够在任何域上被请求到。
Access-Control-Allow-Origin: *
3.4.5 基于标准
有几种不同方式来创建 token。最常用的标准就是 JSON Web Tokens。很多语言都支持它
三、jwt介绍和原理
通过上面的第二部分的发展史,我们可以得知目前我们的校验方式的本质就是给前端发cookie,但是后端不存,这个cookie的本质分成三块:请求头、用户的信息以及充当cookie的唯一码、以及签名,这个签名就是签名两部分的内容加密后的东西,这样后端就不用存储session了,前端发送请求的时候只需要把签名解密,然后跟请求头还有内部的用户信息以及cookie进行对比就能校验,并且安全性也比较高。
概念
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
构成与工作原理
JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.
链接一起就构成了Jwt字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
1.header
jwt的头部主要承载两部分信息:
- 声明类型,这里是jwt
- 声明加密的算法 通常直接使用 HMAC SHA256
- 还可能有公司信息
完整的头部就像下面这样的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2.payload
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
- 标准中注册的声明
- 公共的声明
- 私有的声明
主要的信息如下
-荷载:payload
-存放有效信息的地方
-过期时间
-签发时间
-用户id
-用户名字等
标准中注册的声明 (建议但不强制使用) :
- 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
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扩展来完成。
文档网站:http://getblimp.github.io/django-rest-framework-jwt/
本质原理
jwt认证算法:签发与校验
"""
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对象就是合法的登录用户
"""
drf项目的jwt认证开发流程(重点)
"""
1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中
2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户
注:登录接口需要做 认证 + 权限 两个局部禁用
"""
补充base64编码解码
ps:base64编码后,字符长度一定是4的倍数,如果不是,使用 = 补齐, = 不表示数据,不补齐会报错
import base64
import json
dic_info={
"sub": "1234567890",
"name": "lqz",
"admin": True
}
byte_info=json.dumps(dic_info).encode('utf-8')
# base64编码
base64_str=base64.b64encode(byte_info)
print(base64_str)
# base64解码
base64_str='eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9'
str_url = base64.b64decode(base64_str).decode("utf-8")
print(str_url)
base64 应用场景
'''
1 jwt 使用了base64
2 网络中传输数据,也会经常使用 base64编码
3 网络传输中,有的图片使用base64编码
'''
s='去网上找,比如12306的图片都是用base64加密过的,找到他的地址去掉前面两部分就能得到base64加密后的字符串'
res=base64.b64decode(s)
with open('a.png','wb') as f:
f.write(res)
drf-jwt安装和简单使用
2.1 官网
http://getblimp.github.io/django-rest-framework-jwt/
2.2 安装
pip install djangorestframework-jwt
2.3 使用:
# 1 创建超级用户
python3 manage.py createsuperuser
# 2 配置路由urls.py
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
# 3 postman测试
向后端接口发送post请求,携带用户名密码,即可看到生成的token
# 4 setting.py中配置认证使用jwt提供的jsonwebtoken
# 5 postman发送访问请求(必须带jwt空格)
四、drf-jwt快速使用
django+drf 平台开发jwt这套,有两个模块,加上自定义的,总共有三种方式
-djangorestframework-jwt ---》一直可以用(但是已经停止维护了,可是内部逻辑仍然是对的,可以使用,如果面试官问起来,咋们就说咋们用的是我们自己自定义的就行了,顺手装一波)
-djangorestframework-simplejwt---》公司用的多---》我们可以自己尝试一下
-自己封装jwt签发和认证
使用步骤
步骤一:安装djangorestframework-jwt模块
步骤二:快速签发token
urls.py
'需要在接口文档代码的基础上添加一些配置'
from rest_framework_jwt.views import obtain_jwt_token
'然后要添加一条路由用于登陆(这里因为我们用的是别人的模块,所以要按别人的意思来,如果我们需要重写这个模块对应的user表,就需要自己编写jwt的认证代码了)'
urlpatterns = [
path('admin/', admin.site.urls),
path('docs/', include_docs_urls(title='zzh的项目接口文件')),
path('api/v1/', include(router.urls)),
path('login/', obtain_jwt_token),
]
步骤三:postman发送请求
http://127.0.0.1:8000/login/发送post请求,携带username和password(这两个变量的名称需要跟User表中的字段名称一致)
五、定制返回格式
上文我们也提到了,如果是基于auth的User表签发token,就可以不自己写了对应的登陆方法了,但是登录接口返回的格式,只有token,不符合公司规范,因此我们就有了定制返回格式的需求
使用步骤
步骤一:重写一个jwt_response_payload_handler函数,定制返回的格式(我们是通过查看jwt的源码发现他是定义返回格式的,他的注释中给我们提示了返回的格式应该是什么形式,我们在内部添加数据即可)
在app中创建一个utils.py
def jwt_response_payload_handler(token, user=None, request=None):
return {
'code': 100,
'msg': '登录成功',
'token': token,
'username': user.username
# 'icon':user.icon
}
步骤二:编写了这个自定义的方法后,我们需要在注册文件中更改配置信息
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
}
步骤三:使用postman测试,就能看到返回的格式了
六、jwt的认证类
接口要登录后才能访问、使用,jwt内部有一个认证类,如果我们要使用他的认证类就必须要配合着drf的一个权限类一起使用。
同时这个认证类的校验方式也有点特别,当我们传入token的时候,他会进行校验,但是当我们不传入token 的时候就直接不校验了。
使用方式
视图层(views.py)中导入认证类和权限类然后再视图类中使用
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
class BookView(ModelViewSet):
"""
你是猪
"""
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [JSONWebTokenAuthentication]
permission_classes = [IsAuthenticated]
通过查看IsAuthenticated的源码我们可以发现这个权限类其实就是用来判断我们是否登陆的。
测试
当我们在测试的时候需要注意,我们登陆后会获得一串字符,而我们后面在用各种请求进行查看图书等操作时,需要在请求头中添加字段:Authorization,以及对应的值,而这个值就是登陆后得到的字符串
jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Inp6aCIsImV4cCI6MTY3NTk0NTY1MSwiZW1haWwiOiI5NzY4NjM0MjlAcXEuY29tIn0.auQ1NDp-WQwKGh2Tg9rlKUOZt3kaUzL-oKLloHlPT0Q
我们可以看到,他是jwt三个字母开头,然后中间接一个空格,然后再接字符串
七、作业
1 使用simplejwt,签发token
2 drf-jwt 登录接口怎么写的
-它把逻辑写在了序列化类中----》全局钩子中