DRF从入门到精通七(频率源码分析、接口文档、JWT介绍、构成原理、Base64编码与解码、jwt的开发重点)

一、频率源码分析

从最开始的DRF知识介绍到现在,我们也知道频率组件是在APIView类源码中的dispatch方法里面执行了三大认证,这三大认证也是在视图类方法之前就执行了。

APIView的频率源码分析

		'这里我就不一个个罗列出来了,就直接写有关频率源码了。'
    	def dispatch(self, request, *args, **kwargs):
		    try:
		    	'执行三大认证'
            	self.initial(request, *args, **kwargs)
        
        'APIView里面的initial方法'  	
	    def initial(self, request, *args, **kwargs):
	    	'执行频率'
        	self.check_throttles(request)

		'APIView里面的check_throttles方法'
		def check_throttles(self, request):
	        throttle_durations = []
	        '这里的self.get_throttles方法,就是获取视图类中一个个的频率类的对象,它是一个列表'
	        '然后循环遍历,把一个个的频率类的对象给了throttle'
	        for throttle in self.get_throttles():
	        	'这里就是为什么我们继承BaseThrottle需要重写这个方法的原因。'
	            if not throttle.allow_request(request, self):
		            '''
		            当这个方法的返回值为False时,则获取等待时间并添加到throttle_durations这个列表中
		            这里的throttle.wait就是重写的wait方法的返回值,返回的还剩多长时间才能进行下一次的访问
		            '''
	                throttle_durations.append(throttle.wait()) 
			
			'如果执行了频率限制,就会执行这里'
	        if throttle_durations:
	        	'这里的列表表达式就是duration在这个throttle_durations列表中有还剩多长时间的数据'
	            durations = [
	                duration for duration in throttle_durations
	                if duration is not None  # 这里它过滤掉了为空的
	            ]
				
				'然后在这里取出了这个列表中的最大值,例子:duration[33,54]'
	            duration = max(durations, default=None) # 这里就会取出最大的那个54
	            '这里的self就是视图类的对象,所以我们在APIView中找到了throttled方法'
	            self.throttled(request, duration)
	            'duration有两种情况,一种是有值(被限制频率),一种是None(没有被限制频率)'
        
        
        'APIView中的throttled方法'
        '我们可以看到在上面它把最后的duration传入到这个throttled方法中了'
        def throttled(self, request, wait):
        	'所以这里的wait就是最后的druation的值,所以这里的wait也是有两种情况,有值或者None'
	        raise exceptions.Throttled(wait)
	        '''
	        这里的exceptions.Throttled()就是from rest_framework.exceptions import Throttled
	        也就是类实例化得到对象后把wait也就是duration里面的数字或者None传入进去,
	        具体这个exceptions.Throttled方法里面是如果把我们限制的数字拼接到页面上的这里就不多介绍
	        '''

SimpleRateThrottle 频率类源码分析

我们从上面APIView中的频率源码知道,继承SimpleRateThrottle频率类和继承BaseThrottle频率类,本质都是差不多,都是重写一些方法,而SimpleRateThrottle频率类,它的源码中就是继承了BaseThrottle频率类,所以这里我就直接看SimpleRateThrottle 的源码

'我们自己写自定义频率类的时候,继承SimpleRateThrottle频率类,发现并没有重写allow_request方法,而是写的别的'
	'''
	所以我们在这里去SimpleRateThrottle源码中看看它是如何实现的,
	在SimpleRateThrottle源码中我们看到了它重写了allow_request方法,并且它就是继承了BaseThrottle类
	'''
class SimpleRateThrottle(BaseThrottle):
    def allow_request(self, request, view):
		 """
	    参数:
	    - request: 当前的请求对象
	    - view: 请求对应的视图对象或处理函数
	
	    返回:
	    - True: 允许请求通过
	    - False: 请求被限制
	
	    注意: 这里的逻辑假设限流器的主要参数在初始化时已经设置,
	    如速率(rate)、缓存键(key)、历史记录(history)等。
    	"""
    
      	'我们自己自定义的时候写了这个rate,例如:rate=5/m'
        if self.rate is None: # 当rate为空执行,直接没有限制
            return True
		
		'这里调用了get_chche_key(),这就是为什么我们自定义频率类重写的是这个方法'
		'所以这里的self.key就是重写的get_chche_key方法的返回值,例如按照ip限制,那就是ip'
        self.key = self.get_cache_key(request, view)

		'这里self.key也就是get_chche_key方法的返回值是空的时候执行,相当于没有进行限制'
        if self.key is None:
            return True
		
		'''
		self.key就是get_chche_key方法的返回的限制
		self.cache.get就是缓存,默认设置缓存在内存中,然后去缓存中根据self.key的值,去取出访问者时间列表
		如果没有则是[]
		self.history就是当前ip,访问的时间列表
		'''
        self.history = self.cache.get(self.key, [])

		'这里的self.timer就是加括号调用time.time实例化对象self.now,拿到当前执行时间'
        self.now = self.timer()  # timer = time.time没有加括号
		
		'''
		这里就是拿到访问列表的最后一个时间数据(也是最早的时间数据,这个我们继承BaseThrottle写自定义频率类的时候做过这个类似的逻辑)
		然后当它小于或者等于,当前时间减去self.duration也就是对应的60秒,当它超过了60秒就给超出的数据弹出
		'''
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        
        '''
        这里跟我们继承BaseThrottle写自定义频率类时类似,就是判断这个列表中的时间数据有几个,
        当它大于我们我们设置的rate=5/m中的5个就执行
        '''
        if len(self.history) >= self.num_requests:
        	'这里的self.throttle_failure方法也是SimpleRateThrottle源码内的方法,它直接返回了False'
            return self.throttle_failure()
            '这里我们就可以知道,可以在自定义频率类中重写这个self.throttle_failure方法,定制返回的文字'

		'''
		这个selfthrottle_success方法跟上面一样,它直接返回True,
		def throttle_success(self):
		它把没有被限制的时间一个个放到history这个列表的第零个位置,这也就是我上面说的history[-1]是最早的时间
	        self.history.insert(0, self.now)
	        然后在这里又放入到缓存当中
	        self.cache.set(self.key, self.history, self.duration)
	        return True
		'''
        return self.throttle_success()


	'在上面我并没有去说self.duration和self.num_requests到底为什么是rate=5/m中的60和5。我在这里说一下'
	'首先直接按住Ctrl加鼠标左键点击self.duration就跳转到了它的初始化方法了'
	def __init__(self):
		'这里可以看到self.duration和self.num_requests是self.parse_rate解压赋值的,所以我们去看看这个方法'
	    self.num_requests, self.duration = self.parse_rate(self.rate)
	
	'这个parse_rate也是SimpleRateThrottle的方法'
	def parse_rate(self, rate):
        '当rate为空时,设置两个空'
        if rate is None:  
            return (None, None)
        
        '当有值时按/进行切分,然后解压赋值给num、period两'
        num, period = rate.split('/') # 例子5/m  所以这里的num就是5,period就是m
        '然后再把/斜杠前的数据强转成数字类型后赋值给num-requests'
        num_requests = int(num) # 到了这一步我们就知道,为什么num_requests就是rate=5/m的5了
        
        '''
        这里可以看到duration就是根据period这个第一个字母(索引)的key去duration这个字典中取值,这里都是设置好的
        就是时分秒对应的秒数,这也是为什么我上面的duration说是60了因为对应的rate=5/m中的m就是minute分钟
        它这里的写法比较高级,直接取第一个字符,不管你后面写了什么只要对应上第一个字符就可以
        '''
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]

		'最后给这两个值返回出去'
        return (num_requests, duration)

二、项目自动生成API接口文档

在进行前后端分离开发模式,接口文档是很有必有的,前端通过接口文档可以得知访问什么接口可以得到什么样的数据,而后端只需要规定好接口返回的数据,并定义好接口文档就可以了。

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

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

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

1.CoreAPI

定义

CoreAPI是基于djangorestframework框架下的自动文档生成器,只要按DRF规则写的路由,CoreAPI就可以自动生成接口文档。

REST framewrok生成接口文档需要coreapi库的支持。作为依赖库使用

	pip install coreapi

其实更推荐使用pycharm安装,因为可以指定装到哪个解释器里面。

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

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

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

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

效果如下:
在这里插入图片描述


文档接口描述信息

1、 单一方法的视图,可直接使用类视图的文档字符串

	class BookListView(generics.ListAPIView):
	    """
	    返回所有图书信息.
	    """

2、包含多个方法的视图,在类视图的文档字符串中,分开方法定义

	class BookListCreateView(generics.ListCreateAPIView):
	    """
	    get:
	    返回所有图书信息.
	
	    post:
	    新建图书.
	    """

3、对于视图集ViewSet,仍在类视图的文档字符串中封开定义,但是应使用action名称区分

	class BookView(ModelViewSet):
	    """
	    list:
	    返回图书列表数据
	
	    create:
	    新增图书接口
	
	    retrieve:
	    返回图书详情数据
	
	    update:
	    修改图书接口
	
	    delete:
	    删除图书接口
	    """

4.接口中的字段描述显示

	class MovieSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = '__all__'

        extra_kwargs = {
            'name': {'help_text': '书籍名称'},
            'price': {'help_text': '书籍价格'},
        }

效果如下:
在这里插入图片描述


2.Swagger

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。

总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法、参数和模型紧密集成到服务器端的代码,允许 API 来始终保持同步。Swagger 让部署管理和使用功能强大的 API 从未如此简单。

Swagger 的优势

  • 支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。
  • 提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试。参数和格式都定好了,直接在界面上输入参数对应的值即可在线测试接口。

如何在DRF内使用Swagger?需要先安装指定模块

	pip3 install drf_yasg

该模块还包含了可以生成ReDoc接口文档

DRF内的Swagger配置

	from drf_yasg.views import get_schema_view
	from drf_yasg import openapi
	
	schema_view = get_schema_view(
	    openapi.Info(
	        title="API接口文档平台",  # 必传(接口文档名称)
	        default_version='v1',  # 必传(定义版本)
	        description="图书管理系统接口文档",  # 描述信息
	        terms_of_service="#",
	        contact=openapi.Contact(email="1363004489@qqcom"),  # (接口文档联系人)
	        license=openapi.License(name="BSD License"),  # 许可证
	    ),
	    public=True,
	    # permission_classes=(permissions.AllowAny,), # 指定权限类
	    authentication_classes=[]  # 防止该swagger接口收到认证拦截
	)

路由配置

	urlpatterns = [
	    path('admin/',admin.site.urls),
	    re_path(r'^swagger(?P<format>\.json|\.yaml)$',schema_view.without_ui(cache_timeout=0), name='schema-json'),
	    path('swagger/', schema_view.with_ui('swagger', cache_timeout=0),name='schema-swagger-ui'),
	    path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schemaredoc'),
	]

那么此时我们就可以访问swagger/来接口swagger为我们提供的接口文档了,开头展示的为我们配置里面编写的内容:

在这里插入图片描述
接口描述如何编写,在方法的增加单一注释:

	class GetAllBooks(ListAPIView):
	    def list(self, request, *args, **kwargs):
	    	"""
			查询图书接口
	        :param request:
	        :return: 返回所有图书数据
			"""

list方法处理的就是get请求
在这里插入图片描述
所有书籍接口查看
在这里插入图片描述
并且支持在线测试接口数据,通过Excute来调用接口。

但我们如果使用了Authorization,例如jwt认证,那么我们则需要对swagger进行一定的配置。

settings.py里面使用如下变量,给swagger增加配置项:

	SWAGGER_SETTINGS = {
	    "SECURITY_DEFINITIONS": {
	        "api_key": {
	            "type": 'apiKey',
	            "name": 'Authorization', # 存放Cookie的key
	            "in": 'header' # 存放在请求头内
	        }
	    },
	}

此时页面会出现认证选项,那么我们只需要将Cookie拷贝进去就ok了
在这里插入图片描述
如果我们是根据SessionID进行登录认证的话,那么可以增加如下配置,便可以在swagger页面进行登录操作,无需手动赋值Cookie进行认证。

	SWAGGER_SETTINGS = {
	    "SECURITY_DEFINITIONS": {
	        "api_key": {
	            "type": 'apiKey',
	            "name": 'Authorization',
	            "in": 'header'
	        }
	    },
	    'LOGIN_URL': 'rest_framework:login', # DRF提供的登录行为
	    'LOGOUT_URL': 'rest_framework:logout',
	}

接着我们还需要配置一下主路由

	urlpatterns = [
	    path('admin/', admin.site.urls),
	    re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
	    path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
	    
	    # 配置如下路由放置最低端,这表示任意URL都可以生效,感兴趣的可以去看看rest_framework.urls源码,然后自己重写一套实现逻辑
	    path('', include("rest_framework.urls", namespace="rest_framework")),
	]

现在点击swagger页面右边的Django Login按钮,出现如下界面便可进行登录
在这里插入图片描述


3.接口文档规范

	-需要有描述
	-请求地址
	-请求方式
	-编码方式
	-请求参数----》参数的解释,是否必传,参数类型
	-请求体----》参数的解释,是否必传,参数类型
	-返回示例----》返回数据字段解释
	-错误码解释

三、Cookie、Session、Token介绍

1.Cookie介绍

cookie 是一个非常具体的东西 指的就是浏览器里面能永久存储的一种数据 仅仅是浏览器实现的一种数据存储功能

cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的cookie数量是有限的。


2.Session介绍

session 从字面上讲,就是会话。这个就类似于你和一个人交谈,你怎么知道当前和你交谈的是张三而不是李四呢?对方肯定有某种特征(长相等)表明他就是张三。

session 也是类似的道理,服务器要知道当前发请求给自己的是谁。为了做这种区分,服务器就要给每个客户端分配不同的“身份标识”,然后客户端每次向服务器发请求的时候,都带上这个“身份标识”,服务器就知道这个请求来自于谁了。至于客户端怎么保存这个“身份标识”,可以有很多种方式,对于浏览器客户端,大家都默认采用 cookie 的方式。

服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。这种用户信息存储方式相对cookie来说更安全,可是session有一个缺陷:如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。


3.Cookie和Session的区别

session是存储服务器端,cookie是存储在客户端,所以session的安全性比cookie高

获取session里的信息是通过存放在会话cookie里的session id获取的。而session是存放在服务器的内存中里,所以session里的数据不断增加会造成服务器的负担,所以会把很重要的信息存储在session中,而把一些次要东西存储在客户端的cookie里。

session的信息是通过sessionid获取的,而sessionid是存放在会话cookie中

当浏览器关闭的时候会话cookie消失,所以sessionid也就消失了,但是session的信息还存在服务器端,只是查不到所谓的session,但它并不是不存在

4.Token介绍

无状态、可扩展

在客户端存储的 token 是无状态的,并且能够被扩展。基于这种无状态和不存储Session信息,负载均衡服务器 能够将用户的请求传递到任何一台服务器上,因为服务器与用户信息没有关联。相反在传统方式中,我们必须将请求发送到一台存储了该用户 session 的服务器上(称为Session亲和性),因此当用户量大时,可能会造成 一些拥堵。使用 token 完美解决了此问题。

安全性

请求中发送 token 而不是 cookie,这能够防止 CSRF(跨站请求伪造) 攻击。即使在客户端使用 cookie 存储 token,cookie 也仅仅是一个存储机制而不是用于认证。另外,由于没有 session,让我们少我们不必再进行基于 session 的操作。

Token 是有时效的,一段时间之后用户需要重新验证。我们也不一定需要等到token自动失效,token有撤回的操作,通过 token revocataion可以使一个特定的 token 或是一组有相同认证的 token 无效。

可扩展性

使用 Tokens 能够与其它应用共享权限。例如,能将一个博客帐号和自己的QQ号关联起来。当通过一个 第三方平台登录QQ时,我们可以将一个博客发到QQ平台中。

使用 token,可以给第三方应用程序提供自定义的权限限制。当用户想让一个第三方应用程序访问它们的数据时,我们可以通过建立自己的API,给出具有特殊权限的tokens。

多平台与跨域

我们已经讨论了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: *   

基于标准

有几种不同方式来创建 token。最常用的标准就是 JSON Web Tokens。很多语言都支持它


以上Cookie与Session和Token介绍转自:https://www.cnblogs.com/liuqingzheng/p/17942227


四、JWT介绍

在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。

Json web token (JWT):是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

通俗的说Json web token (JWT):是一种token机制,主要用于前后端登录校验,区别与session的认证机制,不需要在后端存储数据,重点是签发(登录后签发)和认证(访问某些需要登录后才能访问的接口,需要携带认证信息)

在这里插入图片描述

token是一种不需要在服务端的一种认证机制。比如:当客户端第一次登陆时,服务端会响应一个token值给客户端,下次访问时携带这个token值,服务端会解析出这个token值里面的数据,以此来确保是一个合法的用户才能通过认证。


五、JWT的构成原理

JWT的生成

JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.连接一起就构成了Jwt字符串。就像这样:
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。

	eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

1) header头部

JWT头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。

比如:

  • 声明类型:这里是jwt
  • 声明加密的算法,通常直接使用 HMAC、HS256、MD5都可以

这也可以被表示成一个JSON对象

	import base64
	import json
	
	head = {
	  'type': 'JWT',
	  'alg': H5256,
	}
	
	head_json = json.dumps(head).encode('utf-8') # base64只能加密bytes类型内容
	head_base64 = base64.b64encode(head_json) # 对头部head数据进行base64编码加密

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

	eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

2) payload荷载

荷载就是存放有效信息的地方。例如用户信息、签发时间、过期时间等,这些有效信息包含三个部分

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

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

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

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

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

定义一个payload

	import base64
	import json
	
	payload = {
	  "sub": "1234567890",
	  "name": "Tom",
	  "admin": True
	}
	
	payload_json = json.dumps(payload).encode('utf-8')
	payload_base64 = base64.b64encode(payload_json)

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

	eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogIlRvbSIsICJhZG1pbiI6IHRydWV9

3) signature签名

JWT的第三部分是一个签证信息,通过加密方式把第一段和第二段通过秘钥加密得到的,这个签证信息由三部分组成:

  • header (base64编码加密后的)
  • payload (base64编码加密后的)
  • secret(秘钥、也可以称为加盐)
	import hashlib

	md5 = hashlib.md5()
	
	md5.update(head_base64)
	md5.update(payload_base64)
	md5.update(b'wmwm') # secret
	
	signature = md5.hexdigest()
	
	token = f'{head_base64.decode("utf-8")}.{payload_base64.decode("utf-8")}.{signature}'
	
	print(token)

第三段signature签名值:

	04748f59f146cdcd27e1452c4cd819e5

最终返回给我们的就是一个完整的token值

	eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJNRDUifQ==.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogIlRvbSIsICJhZG1pbiI6IHRydWV9.04748f59f146cdcd27e1452c4cd819e5

我们只需要将此token返回给前端,下次访问服务端时携带token,服务端会检查前前两段经过md5算法,以及指定秘钥后是否和签名值相同。


手动验证token

比如:当客户端将token携带过来以后,我们需要对其token值是否有效进行校验

	import hashlib

	# 获取到jwt的3段值
	head,payload,signature = 'eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJNRDUifQ==.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogIlRvbSIsICJhZG1pbiI6IHRydWV9.04748f59f146cdcd27e1452c4cd819e5'.split('.')
	
	
	# 将前两段进行md5加密,得到签证
	md5 = hashlib.md5()
	md5.update(head.encode('utf-8'))
	md5.update(payload.encode('utf-8'))
	md5.update(b'wmwm') # 使用和生成签证使用的加盐秘钥相同(尤为重要)
	
	# 校验前两段值经过md5加密以及加盐后,是否和jwt第三段值相同,如果是的话,则说明客户端携带的token值合法(与我们当时响应的相同)
	if md5.hexdigest() == signature:
		print('认证成功')

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

首先Base64是python内置的模块 base64可以把字符串编码成base64的编码格式(大小写字母 包括数据与=号)

	eyJOYW1lIjogIkxpa2UiLCAiVXNlcklEIjogODgsICJhZ2UiOiAyMH0=

base64可以把base64编码的网络传输的字符串、图片解码回原来的格式
使用Base64编码

	import json
	import base64
	
	d = {'name':'jack','user_id':88,'age':20}
	user = json.dumps(d)
	print(user)  # {"name": "jack", "user_id": 88, "age": 20}
	
	res = base64.b64encode(user.encode('utf-8')) # 编码
	print(res)  # eyJuYW1lIjogImphY2siLCAidXNlcl9pZCI6IDg4LCAiYWdlIjogMjB9
	
	res1 = base64.b64decode(res)  # 解码
	print(res1)  # {"name": "jack", "user_id": 88, "age": 20}

使用Base64解码

	img = 'iVBORw0KGgoAAAANSUhEUgAAAMcAAADH' # 这里省略
	code = base64.b64decode(img)
	
	with open('code.png','wb')as f:
	    f.write(code)

在这里插入图片描述


七、jwt的开发重点

从上面几个jwt的知识点下来,我们知道jwt就是一种token机制,主要就是用于前后端登录认证的方式

	1.登录:签发token-----》登录接口
		1).用户携带用户名、密码到后端
		2).校验用户名密码是否正确
		3).如果正确,签发token,按照jwt逻辑生成三段式,返回给前端看
	2.认证token------>认证类
		1.用户访问我们需要登录的接口
		2.携带token过来------>请求头、请求地址等
		3.后端校验用户携带的token。是否被篡改、伪造
		4.如果没问题,认证通过,继续后续的逻辑
  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是使用 DjangoDjango Rest Framework(DRF)框架完成 JWT 登录接口的基本步骤: 1. 安装必要的库 首先,我们需要安装 DjangoDRFjwt 库。可以使用 pip 命令来安装它们: ``` pip install django djangorestframework jwt ``` 2. 配置 Django 项目 在 Django 项目中,我们需要配置一些设置来启用 DRFJWT 认证。打开 `settings.py` 文件,添加如下配置: ```python INSTALLED_APPS = [ # ... 'rest_framework', 'rest_framework.authtoken', ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.TokenAuthentication', 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', ), } JWT_AUTH = { 'JWT_SECRET_KEY': SECRET_KEY, 'JWT_ALGORITHM': 'HS256', 'JWT_ALLOW_REFRESH': True, 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=30), } ``` 这里添加了 `rest_framework` 和 `rest_framework.authtoken` 应用,以及 DRFJWT 认证的配置。需要注意的是,`JWT_SECRET_KEY` 的值需要设置为 Django 项目的 `SECRET_KEY`。 3. 编写登录接口 在 Django 项目中,我们可以使用视图函数或者类视图来编写 API 接口。这里我们使用类视图来编写登录接口。打开 `views.py` 文件,添加如下代码: ```python from rest_framework import views, status from rest_framework.response import Response from rest_framework_jwt.settings import api_settings from django.contrib.auth import authenticate class LoginView(views.APIView): def post(self, request): username = request.data.get('username') password = request.data.get('password') user = authenticate(request, username=username, password=password) if user is not None: jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return Response({'token': token}) else: return Response({'error': 'Invalid credentials'}, status=status.HTTP_400_BAD_REQUEST) ``` 这里我们创建了一个 `LoginView` 类视图,用来处理用户登录请求。在 `post` 方法中,我们从请求的参数中获取用户名和密码,然后使用 `authenticate` 函数来验证用户的身份。如果验证成功,我们使用 jwt 库中提供的函数来生成 JWT token,并返回给客户端。如果验证失败,则返回错误信息。 4. 配置 URL 路由 最后,我们需要在 Django 项目中配置 URL 路由,将登录接口映射到一个 URL 上。打开 `urls.py` 文件,添加如下代码: ```python from django.urls import path from .views import LoginView urlpatterns = [ path('login/', LoginView.as_view(), name='login'), ] ``` 这里我们将 `/login/` URL 映射到 `LoginView` 类视图上。 至此,我们完成了使用 DjangoDRF 框架编写 JWT 登录接口的基本步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值