JWT

目录

JWT组成

第一部分header

第二部分payload

第三部分signature

注意

JWT认证算法:签发和校验

drf使用jwt

drf项目的jwt认证开发流程

drf-jwt安装和简单使用

安装

简单使用

drf-jwt使用

jwt内置类JSONWebTokenAuthentication

控制使用jwt的登录接口返回的数据

自定制jwt认证类

使用jwt自动签发token+多方式登录

配置jwt token的过期时间


JWT组成

jwt(java web token)本质就是一个token,是一个字符串,由三段信息组成:

        header(头部) + payload(荷载) + signature(签证)

比如:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

jwt定义来源优势种类: JWT详解_baobao555#的博客-CSDN博客

第一部分header

header承载两部分信息:类型(声明是jwt)和加密算法(声明加密算法,一般都是使用HMAC SHA256)
完整头部信息就比如下面的json数据:
	{
		''type'':''JWT'',
		"alg":"HS256"
	}
然后将头部进行base64加密(该加密是可以对称解密的),就构成了第一部分header:
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

第二部分payload

存放有效信息的地方,有效信息包含三部分:
	1 标准中注册的声明
		iss: jwt签发者
		sub: jwt所面向的用户
		aud: 接收jwt的一方
		exp: jwt的过期时间,这个过期时间必须要大于签发时间
		nbf: 定义在什么时间之前,该jwt都是不可用的.
		iat: jwt的签发时间
		jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避时序攻击。
	2 公共的声明
		公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。
	3 私有的声明
		私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
比如定义一个payload
	{
	   "sub": "123456789",
	   "name": "weer",
	   "admin": true
	}
然后将其进行base64加密,得到JWT的第二部分
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

第三部分signature

signature是个签证信息,由三部分构成
	header(base64后的)+payload(base64后的)+secret
signature将经base64加密后的header和payload连接组成字符串,
然后通过header中声明的加密算法进行加盐secret组合加密,然后就形成了signature。

将header、payload、signature连接成一个字符串就形成了最终的jwt。

注意

secret是保存在服务端的,jwt的签发认证也是在服务端进行。secret就是用来进行jwt的签发和jwt的验证,所以secret就是你服务端的私钥,在任何时候都不得流露出去一旦客户端搞到了它意味着客户端就可以自己签发jwt了,那服务端的认证就没用了严重威胁数据安全。

JWT认证算法:签发和校验

jwt简言分为三段式:头、体、签名(header,payload,signature)。头和体是可逆加密,服务器可以反解出user对象;签名是不可逆加密,保证整个token安全性。

jwt组成的三部分都是采用json格式字符串进行加密,可逆加密一般用base64算法,不可逆加密一般采用hash(/md5)算法。

头中内容通常为基本信息:公司信息,项目组信息,token采用的加密方式信息等

体中内容是关键信息:用户主键、用户名、签发时客户端信息(ip,地址),过期时间等

签名内容为安全信息:头的加密结果,体的加密结果,服务器保密的安全码/私钥,不可逆加密

签发就是传数据信息给jwt,jwt自动给你返回一个token(jwt token);

# 签发:根据登录请求提交来的账号+密码+设备信息签发token
"""
(1) 用基本信息存储json字典,采用base64算法加密得到  头字符串
(2) 用关键信息存储json字典,采用base64算法加密得到  体字符串
(3) 用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到  签名字符串
"""

校验就是jwt提供的认证方法,可以用它进行用户校验、权限校验等。

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

drf使用jwt

关于签发和校验,我们可以使用drf中与jwt有关的第三方包来实现

drf项目的jwt认证开发流程

"""

1、用账号密码访问登录接口,登录接口逻辑中调用,通过签发token的相关算法得到token,返回给客户端,客户端存到自己的cookie中

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

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

drf-jwt安装和简单使用

安装

pip3 install djangorestframework-jwt

简单使用

创建超级用户weer

在url.py中配置了一个登录路由

from rest_framework_jwt.views import ObtainJSONWebToken,VerifyJSONWebToken,RefreshJSONWebToken

"""

drf-jwt内置了三种jwt视图类,三种视图类都继承了基类JSONWebTokenAPIView---看源码

基类JSONWebTokenAPIView继承了APIView

"""

from rest_framework_jwt.views import obtain_jwt_token
	"""
	源码中自动将三种jwt视图类调用as_view()方法生成对象,所以两种方式等价
	obtain_jwt_token = ObtainJSONWebToken.as_view()
	refresh_jwt_token = RefreshJSONWebToken.as_view()
	verify_jwt_token = VerifyJSONWebToken.as_view()
	"""
urlpatterns = [
    # jwt简单使用
    # path('login/', ObtainJSONWebToken.as_view()), # 与下等价
    path('login/', obtain_jwt_token),
]

在postman中发post请求,带上用户名密码数据,即可得drf-jwt自动生成的jwt token:

{"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJlbWFpbCI6IiIsInVzZXJuYW1lIjoid2VlciIsImVcCI6MTY2NDQ1MzU5NX0.L1Y9IAqc40X-AGcPvbvI1DDo-BD6iqYmg2BQN5zWYSU"}

drf-jwt使用

jwt内置类JSONWebTokenAuthentication

views.py

# jwt内置认证类使用——限制只有登录过的用户才能访问
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
class OnlyLoginUserCanSeeAPIView(APIView):
    # 限制登录用户才能访问需要配以下两个内置类
    authentication_classes = [JSONWebTokenAuthentication,]
       # 权限
    permission_classes = [IsAuthenticated,]
    def get(self, request):
        return Response('login user')
class VisitorCanSeeAPIView(APIView):
    # 游客/用户不登录也能访问只需将permission_classes去掉即可
    authentication_classes = [JSONWebTokenAuthentication]
    def get(self, request):
        return Response('anonymous user')

urls.py

	path('loginusersee/', views.OnlyLoginUserCanSeeAPIView.as_view()),
	path('visitorusersee/', views.VisitorCanSeeAPIView.as_view()),

此时使用postman get请求访问接口/loginusersee/必须携带登录后生成的jwt token(keyAuthorizationvalueJWT空格+token)才能访问到该视图函数下的信息("login user")

而访问/visitorusersee/则可以不携带jwt token信息即可访问到"anonymous user"数据

控制使用jwt的登录接口返回的数据

正常使用jwt的登录接口登录成功后只会返回一个token信息,可以配置返回其它信息
查看restframework-jwt配置文件settings.py源码:
	USER_SETTINGS = getattr(settings, 'JWT_AUTH', None)
	说明我们要让jwt使用我们项目的配置,则需配置JWT_AUTH={…}
	'JWT_RESPONSE_PAYLOAD_HANDLER':
	    'rest_framework_jwt.utils.jwt_response_payload_handler',
	此源码为jwt内部配置的使用jwt_response_payload_handler方法返回登录成功后的信息,
	点进去看该方法源码可得:内部只返回了一个token信息,且该方法会自动传user和request
∴我们可以重写jwt_response_payload_handler方法并在settings.py中配置来控制登录成功后返回的数据
	utls.my_jwt_response_payload_handler.py
	def my_jwt_response_payload_handler(token, user=None, request=None):
	    return {
	        'token': token,
	        'msg':'登录成功',
	        'name':user.username,
	        'method':request.method
	    }
	settings.py
	JWT_AUTH = {
	 'JWT_RESPONSE_PAYLOAD_HANDLER':
	'utils.my_jwt_response_payload_handler.my_jwt_response_payload_handler',
	}

之后登录jwt的接口成功后返回的就是我们控制的数据啦

自定制jwt认证类

发现jwt的内置认证类JSONWebTokenAuthentication是继承BaseJSONWebTokenAuthentication类的

我们要重写认证类,需继承BaseJSONWebTokenAuthentication,然后重写authenticate方法。但其实重写authenticate方法思路和逻辑和原方法差不多

-utls.MyJSONWebTokenAuthentication.py

from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework_jwt.utils import jwt_decode_handler
import jwt # 内置模块,内含许多jwt异常/错误
from rest_framework.exceptions import AuthenticationFailed

class MyJSONWebTokenAuthentication(BaseJSONWebTokenAuthentication): # 继承基类
    def authenticate(self, request):
        jwt_value = request.META.get('HTTP_AUTHORIZATION')
        # 这样就直接带token就行了,不用JWT空格token
        if jwt_value:
            try:
                payload = jwt_decode_handler(jwt_value)
                # 还是使用的jwt的通过jwt token反解出payload的方法,需提前导入
                # print(payload) #{'user_id': 1, 'email': '', 'username': 'weer',}
                # payload就是用户信息的字典
            except jwt.ExpiredSignature:
                raise AuthenticationFailed('签证过期')
            except jwt.DecodeError:
                raise AuthenticationFailed('签证错误')
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('用户非法')
            except Exception as e:
                raise AuthenticationFailed(str(e))
        else:
            raise AuthenticationFailed('未携带认证信息')
        # 调用BaseJSONWebTokenAuthentication提供的通过payload解出user对象的方法-源码
        user = self.authenticate_credentials(payload)
        # 也可自己设置方法-两种
            # 第一种:查数据库
        # from app01 import models
        # user = models.User.objects.filter(pk=payload.get('user_id'))
            # 第二种:不走数据库,创建临时对象
        # from app01 import models
        # user = models.User(id=payload.get('user_id'), username= payload.get('username'))

        return user, jwt_value

-views.py

# jwt自定义认证类
from utils.MyJSONWebTokenAuthentication import MyJSONWebTokenAuthentication
class OnlyLoginUserCanSeeAPIView2(APIView):
    # 限制登录用户才能访问需要配以下两个内置类
    authentication_classes = [MyJSONWebTokenAuthentication,]
    permission_classes = [IsAuthenticated,]
    def get(self, request):
        return Response('login user2')

-urls.py

# jwt自定制认证类
path('loginusersee2/', views.OnlyLoginUserCanSeeAPIView2.as_view()),

也可继承BaseAuthentication写

使用jwt自动签发token+多方式登录

这里我们设置登录时不仅可由用户名密码登录,也可由电话或邮箱、密码登录,并对不同的登录方式自动签发token返回

utls.myser1.py
	from rest_framework import serializers
	from app01 import models
	import re
	from rest_framework.exceptions import ValidationError
	from rest_framework_jwt.utils import jwt_payload_handler,jwt_encode_handler

	class ManyWaysToLoginSerializers(serializers.ModelSerializer):
	    username = serializers.CharField() # 重覆盖原来的username字段,否则原来的校验都过不了,会报原用户已存在的错,因为原username字段设置了unique=True,post请求认为你是保存数据就会报错          或命名为其它名字
	    class Meta:
	        model = models.User
	        fields = ['username', 'password']
	
	    # 在这写多种方式登录的校验逻辑
	       # 要校验两个,用全局钩子
	    def validate(self, attrs):
	        # print(self.context) #{'test': 'test'}由视图传来,检验context这个交互媒介
	
	        # 获取登录数据
	        username = attrs.get('username')
	        password = attrs.get('password')
	        # 判断登录方式是哪种分别处理,用正则匹配,获取用户对象
	        if re.match('^1[3-9][0-9]{9}$',username):
	            user_obj = models.User.objects.filter(phone=username).first()
	        elif re.match('[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+',username):
	            user_obj = models.User.objects.filter(email=username).first()
	        else:
	            user_obj = models.User.objects.filter(username=username).first()
	        # 校验用户是否存在及密码
	        if user_obj:
	            # 校验密码
	            if user_obj.check_password(password): # 密码是密文,用check_password
	                # 签发token
	                payload = jwt_payload_handler(user_obj)
	                token = jwt_encode_handler(payload)
	                self.context['username'] = user_obj.username
	                # 如何把token传到视图函数中呢?→
	                self.context['token'] = token
	                # 全局钩子校验完后必须返回所有数据
	                return attrs
	            else:
	                raise ValidationError('密码错误')
	        else:
	            raise ValidationError('用户不存在')
views.py
	# from rest_framework.viewsets import ViewSetMixin
	# from rest_framework.views import APIView
	from rest_framework.viewsets import ViewSet
	from utils import myser1
	# class ManyWaysToLogin(ViewSetMixin,APIView):等价
	class ManyWaysToLogin(ViewSet):
	    def login(self, request):
	        # 1 需要一个序列化类,生成序列化对象
	        ser_obj = myser1.ManyWaysToLoginSerializers(data=request.data, context={'test':'test'})
	        # 2 在序列化类中写多方式登录校验逻辑
	        # 3 调用序列化累is_validated方法,校验过了才往下执行
	        ser_obj.is_valid(raise_exception=True)
	        # 4 获取token
	        token = ser_obj.context.get('token')
	        # 5 返回数据
	        return Response({'status':100, 'user':ser_obj.context.get('username'), 'msg':'登录成功', 'token':token})
urls.py
    path('manywayslogin/', views.ManyWaysToLogin.as_view({'post':'login'})),

此时利用post发送post请求,用三种方式都能登录了

查看序列化基类源码(BaseSerializer),里面初始化__init__时就有一参数

    def __init__(self, instance=None, data=empty, **kwargs):

        self.instance = instance

        if data is not empty:

            self.initial_data = data

        self.partial = kwargs.pop('partial', False)

        self._context = kwargs.pop('context', {})

        kwargs.pop('many', None)

        super().__init__(**kwargs)

就是它提供了context,我们可以利用它作为媒介在里面放参数供序列化类和视图之间交互数据

如myser1.py中打印的{'test':'test'}就是由views.py中传来,views.py中的token就是myser1.py中传来

配置jwt token的过期时间

drf-jwt的settings.py中配置的过期时间是300s,要想更改,在我们项目的settings.py中配置如下:
import datetime
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 配置jwt token过期时间为7天
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weer-wmq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值