本文主要是介绍DRF ~ day10 之 jwt原理解析、jwt开发流程、jwt介绍和快速使用、定制返回格式、自定义用户表,签发,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
jwt原理解析、jwt开发流程、jwt介绍和快速使用、定制返回格式、自定义用户表,签发
文章目录
- jwt原理解析、jwt开发流程、jwt介绍和快速使用、定制返回格式、自定义用户表,签发
- 一、jwt原理解析
- 1.1、jwt的构成和工作原理
- 1.1.1、JWT的构成
- 1.1.2、header (头部)
- 1.1.2、payload(载荷)
- 1.1.3、signature(签证)
- 二、jwt开发流程
- 三、drf - jwt的快速使用
- 3.1、jwt的快速使用的两种方法
- 3.2、jwt的安装
- 3.3、jwt的签发过程
- 3.4、jwt的认证过程
- 四、drf ~ jwt 定制返回格式
- 五、自定义用户表和签发token
- 六、drf-jwt自定义认证类
- 七、drf-jwt的签发源码分析
- 八、认证权限源码分析
- 总结
一、jwt原理解析
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
基于session的认证
基于jwt的认证
1.1、jwt的构成和工作原理
1.1.1、JWT的构成
JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
1.1.2、header (头部)
jwt的头部承载两部分信息:
- 声明类型:这里是jwt
- 声明加密的算法:通常直接使用 HMAC SHA256
完整的头部信息就像下面这样的JSON:
'''
{
'type': 'JWT',
'alg': 'HS256'
}
'''
然后将头部进行base64编码,构成了第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
1.1.2、payload(载荷)
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
- 标准中注册的声明
- 公共的声明
- 私有的声明
标准中注册的声明 (建议但不强制使用) :
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避时序攻击。
公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
定义定义一个payload(载荷):
{"exp": "1234567890","name": "John Doe","user_id":99
}
然后将其进行base64编码,得到JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
1.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开发流程
使用jwt认证的开发流程,就是两部分
- 第一部分: 签发token的过程,登录做的
用户携带用户名和密码,访问我,我们校验通过,生成token串,返回给前端 - 第二部分:token认证过程,登录认证时使用,其实就是咱们之前讲的认证类,在认证类中完成对token的认证操作
用户访问我们需要登陆后才能访问的接口,必须携带我们签发的token串(请求头)
我们取出token,验证该token是否过期,是否被篡改,是否是伪造的
如果正常,说明荷载中的数据,就是安全的,可以根据荷载中的用户id,查询出当前登录用户,放到request中即可
三、drf - jwt的快速使用
django + drf 框架中,使用jwt来做登录认证
3.1、jwt的快速使用的两种方法
-
使用第三方模块
-django-rest-framework-jwt:https://github.com/jpadilla/django-rest-framework-jwt
这个是老版本的,已经不维护了,在django框架3.x以上的版本已经不能使用了,只能3.x及以下版本使用-djangorestframework-simplejwt:https://github.com/jazzband/djangorestframework-simplejwt
新的版本 -
我们可以自己封装
参考链接: https://gitee.com/liuqingzheng/rbac_manager/tree/master/libs/lqz_jwt
3.2、jwt的安装
pip安装: pip3.9 install djangorestframework-jwt
3.3、jwt的签发过程
签发过程(是快速签发),必须是auth的user表(这个是别人已经帮您写好了)
写登录接口 -------》 基于auth的user表签发的
- 路由层代码
from django.contrib import admin
from django.urls import path# 导入jwt签发的模块
from rest_framework_jwt.views import obtain_jwt_tokenurlpatterns = [path('admin/', admin.site.urls),# 这样写签发加登录接口就有了path('login/', obtain_jwt_token),
]
- 创建超级用户
在django的tools目录栏点击Run manage.py Task 后的创建创建超级用户
createsuperuser
创建前
创建后
- 创建两个超级用户,密码一样,为什么结果不一样的分析?
补充:
- 密码明文一样,每次加密后都不一样,如何做,动态加盐,动态的盐要保存,跟密码保存在一起
- 有代码,有数据库,没有登录不进去的系统
- postman展示(获取token成功)
- 可以根据返回的token解码出用户信息
import base64# "token":
# "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE2ODUzNjEwMzksImVtYWlsIjoiIn0.JQr8OajKGCvT5o4QFca2aWKiK20D4KJPTJ6p15K_c8o"# 头部
# res = base64.b64decode('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9')
# print(res) # b'{"typ":"JWT","alg":"HS256"}'# 载荷
# res = base64.b64decode('eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE2ODUzNjEwMzksImVtYWlsIjoiIn0')
# 需要填充=
res = base64.b64decode('eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE2ODUzNjEwMzksImVtYWlsIjoiIn0=')
print(res) # b'{"user_id":1,"username":"root","exp":1685361039,"email":""}'
按上面的推导token出现了解决不了:
就是:如果token被获取,模拟发送请求,这个是解决不了的
但是:token不能篡改,不能伪造
如何保证token的安全性? 设置token的过期时间
以上代码实现的过程是使用auth的user表做登录,不需要写额外的代码了
3.4、jwt的认证过程
- 路由层代码
from django.contrib import admin
from django.urls import path
from app01 import views
# 导入jwt签发的模块
from rest_framework_jwt.views import obtain_jwt_tokenurlpatterns = [path('admin/', admin.site.urls),# 这样写签发加登录接口就有了,# 登录接口就有了,并且可以签发token,如果使用auth的user表做登录,不需要写额外的代码了path('login/', obtain_jwt_token),# 写认证的接口path('books/', views.BookView.as_view()),
]
- 认证类创建认证模块auth.py文件
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailedimport jwt
from rest_framework_jwt.settings import api_settings
from .models import Userjwt_decode_handler = api_settings.JWT_DECODE_HANDLERclass JWTAuthentication(BaseAuthentication):def authenticate(self, request):token = request.META.get('HTTP_TOKEN')# 背过# 校验token是否过期,合法,如果都通过,查询出当前用户,返回# 如果不通过,抛异常try:payload = jwt_decode_handler(token)# 如果认证通过,payload就可以认为是安全的,我们就可以使用user_id = payload.get('user_id')# 每个需要登录后,才能访问的接口,都会走这个认证类,一旦走到这个认证类,机会去数据库查询一次数据,会对数据造成压力?user = User.objects.get(pk=user_id)# 优化后的# user = User(username=payload.get('username'), id=user_id)# user = {'username':payload.get('username'), 'id':user_id}except jwt.ExpiredSignature:raise AuthenticationFailed('token过期')except jwt.DecodeError:raise AuthenticationFailed('解码失败')except jwt.InvalidTokenError:raise AuthenticationFailed('token认证异常')except Exception:raise AuthenticationFailed('token认证异常')return user, token
- 视图层代码
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response# 设置必须登录后才能访问
class BookView(APIView):authentication_classes = [JSONWebTokenAuthentication] # 认证类,drf-jwt提供的permission_classes = [IsAuthenticated] # 权限类,drf提供的def get(self, request):return Response("你看到我了")
- postman展示
总结:访问的时候,要在请求头中携带,必须叫Authorization:jwt token串
认证规定,请求必须放在请求头中headers中写入
k必须写:Authorization v写:jwt 后加token
四、drf ~ jwt 定制返回格式
设置 登录签发token的接口,要返回code, msg,username, token等信息
对返回格式进行定制
1 写个函数,函数返回字典格式,返回的格式,会被序列化,前端看到
2 写的函数配置一下
新建一个py文件 jwt_response.py 文件
def common_response(token, user=None, request=None):return {'code': '100','msg': '登录成功','username': user.username,'token': token,}
配置文件(settings.py文件中)配置
JWT_AUTH = {'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.jwt_response.common_response',
}
postman展示
五、自定义用户表和签发token
- 模型层代码
from django.db import models# Create your models here.class User(models.Model):username = models.CharField(max_length=32)password = models.CharField(max_length=64)age = models.IntegerField()email = models.CharField(max_length=64)
- 路由层代码
from django.contrib import admin
from django.urls import path
from app01 import views
# 导入jwt签发的模块
from rest_framework_jwt.views import obtain_jwt_tokenurlpatterns = [path('admin/', admin.site.urls),# 自定义用户表,写接口,做的token签发path('login/', views.UserView.as_view({'post': 'login'})),
]
- 视图层代码
# 自定义用户表,写登录接口,做token签发from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from .models import User# 导入签发模块
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLERclass UserView(ViewSet):def login(self, request):username = request.data.get('username')password = request.data.get('password')user = User.objects.filter(username=username, password=password).first()if user:# 登录成功,签发token# 签发token# 1、 通过user,获取payloadpayload = jwt_payload_handler(user)print(payload)# 2、通过payload,得到tokentoken = jwt_encode_handler(payload)return Response({'code': 100, 'msg': '登录成功', 'username': user.username, 'token': token})else:return Response({'code': 101, 'msg': '用户名或密码错误'})
*postman展示
六、drf-jwt自定义认证类
- 创建认证类的文件auth.py写自定义认证类
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailedimport jwt
from rest_framework_jwt.settings import api_settings
from .models import Userjwt_decode_handler = api_settings.JWT_DECODE_HANDLERclass JWTAuthentication(BaseAuthentication):def authenticate(self, request):token = request.META.get('HTTP_TOKEN')# 背过# 校验token是否过期,合法,如果都通过,查询出当前用户,返回# 如果不通过,抛异常try:payload = jwt_decode_handler(token)# 如果认证通过,payload就可以认为是安全的,我们就可以使用user_id = payload.get('user_id')# 每个需要登录后,才能访问的接口,都会走这个认证类,一旦走到这个认证类,机会去数据库查询一次数据,会对数据造成压力?user = User.objects.get(pk=user_id)# 优化后的# user = User(username=payload.get('username'), id=user_id)# user = {'username':payload.get('username'), 'id':user_id}except jwt.ExpiredSignature:raise AuthenticationFailed('token过期')except jwt.DecodeError:raise AuthenticationFailed('解码失败')except jwt.InvalidTokenError:raise AuthenticationFailed('token认证异常')except Exception:raise AuthenticationFailed('token认证异常')return user, token
- 视图层
from rest_framework.views import APIView
from rest_framework.response import Response
from .auth import JWTAuthenticationclass BookView(APIView):authentication_classes = [JWTAuthentication]def get(self, request):print(request.user.age)return Response("你看到我了")
- 路由层
path('books/', views.BookView.as_view()),
postman展示
这里的token是登录之后返回到前端的token
七、drf-jwt的签发源码分析
# from rest_framework_jwt.views import obtain_jwt_token
# obtain_jwt_token就是ObtainJSONWebToken.as_view()---》视图类.as_view()# 视图类
class ObtainJSONWebToken(JSONWebTokenAPIView):serializer_class = JSONWebTokenSerializer# 父类:JSONWebTokenAPIView
class JSONWebTokenAPIView(APIView):# 局部禁用掉权限和认证permission_classes = ()authentication_classes = ()def post(self, request, *args, **kwargs):# serializer=JSONWebTokenSerializer(data=request.data)serializer = self.get_serializer(data=request.data)# 调用序列化列的is_valid---》字段自己的校验规则,局部钩子和全局钩子# 读JSONWebTokenSerializer的局部或全局钩子if serializer.is_valid(): # 全局钩子在校验用户,生成token# 从序列化类中取出useruser = serializer.object.get('user') or request.user# 从序列化类中取出tokentoken = serializer.object.get('token')# 咱么定制返回格式的时候,写的就是这个函数response_data = jwt_response_payload_handler(token, user, request)response = Response(response_data)return responsereturn Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)# JSONWebTokenSerializer的全局钩子
class JSONWebTokenSerializer(Serializer):def validate(self, attrs):# attrs前端传入的,校验过后的数据 {username:lqz,password:lqz12345}credentials = {'username': attrs.get('usernme'),'password': attrs.get('password')}if all(credentials.values()): # 校验credentials中字典的value值是否都不为空# user=authenticate(username=前端传入的,password=前端传入的)# auth模块的用户名密码认证函数,可以传入用户名密码,去auth的user表中校验用户是否存在# 等同于:User.object.filter(username=username,password=加密后的密码).first()user = authenticate(**credentials)if user:if not user.is_active:msg = _('User account is disabled.')raise serializers.ValidationError(msg)payload = jwt_payload_handler(user)return {'token': jwt_encode_handler(payload),'user': user}else:msg = _('Unable to log in with provided credentials.')raise serializers.ValidationError(msg)else:msg = _('Must include "{username_field}" and "password".')msg = msg.format(username_field=self.username_field)raise serializers.ValidationError(msg)
八、认证权限源码分析
# from rest_framework_jwt.authentication import JSONWebTokenAuthentication
# JSONWebTokenAuthentication
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):def get_jwt_value(self, request):# auth=['jwt','token串']auth = get_authorization_header(request).split()auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()if not auth:if api_settings.JWT_AUTH_COOKIE:return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)return Noneif smart_text(auth[0].lower()) != auth_header_prefix:return Noneif len(auth) == 1:msg = _('Invalid Authorization header. No credentials provided.')raise exceptions.AuthenticationFailed(msg)elif len(auth) > 2:msg = _('Invalid Authorization header. Credentials string ''should not contain spaces.')raise exceptions.AuthenticationFailed(msg)return auth[1] # token串# 父类中找:BaseJSONWebTokenAuthentication---》authenticate,找到了
class BaseJSONWebTokenAuthentication(BaseAuthentication):def authenticate(self, request):# 拿到前端传入的token,前端传入的样子是 jwt token串jwt_value = self.get_jwt_value(request)# 如果前端没传,返回None,request.user中就没有当前登录用户# 如果前端没有携带token,也能进入到视图类的方法中执行,控制不住登录用户# 所以咱们才加了个权限类,来做控制if jwt_value is None:return Nonetry:payload = jwt_decode_handler(jwt_value)except jwt.ExpiredSignature:msg = _('Signature has expired.')raise exceptions.AuthenticationFailed(msg)except jwt.DecodeError:msg = _('Error decoding signature.')raise exceptions.AuthenticationFailed(msg)except jwt.InvalidTokenError:raise exceptions.AuthenticationFailed()# 通过payload拿到当前登录用户user = self.authenticate_credentials(payload)return (user, jwt_value)# 如果用户不携带token,也能认证通过
# 所以我们必须加个权限类来限制class IsAuthenticated(BasePermission):def has_permission(self, request, view):return bool(request.user and request.user.is_authenticated)
总结
以上就是今天要讲的内容,本文仅仅简单介绍了jwt的使用。
这篇关于DRF ~ day10 之 jwt原理解析、jwt开发流程、jwt介绍和快速使用、定制返回格式、自定义用户表,签发的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!