当前位置:网站首页>jwt (json web token)
jwt (json web token)
2022-08-02 14:21:00 【Spaghetti Mixed with No. 42 Concrete】
Python之jwt(json web token)
一、什么是jwt
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证.我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制.
jwt全称json web token,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准
((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景.
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,To facilitate serving from resources
to obtain the resource,也可以增加一些额外的其它业务逻辑所必须的声明信息,该tokenIt can also be used directly for identification
证,也可被加密.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LJqSn4jc-1636029379226)(007S8ZIlgy1ggprivsr69j31i50u0tao.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aESAO1IM-1636029379228)(007S8ZIlgy1ggpriyyay5j31i80u03zl.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HvqAz1fJ-1636029379229)(007S8ZIlgy1ggprj27cflj31fq0u0abx.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MincRGuj-1636029379231)(007S8ZIlgy1ggprj5moolj31ob0u0q4w.jpg)]
二、jwt构成和工作原理
1、jwt的构成
jwtA message is a string,There are three pieces of information,Use these three pieces of information as text,Connected together constitutesjwt字符串
例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
All three parts.分割开来
The first part is called the head(header),The second part is called the load(payload),The third part is called the visa(signatrue)
header
jwt的头部承载两部分信息:
- 声明类型:这是jwt
- 声明加密的算法:通常直接使用HMAC SHA256
The complete header is below
{
'typ': 'JWT',
'alg': 'HS256'
}
payload
载荷就是存放有效信息的地方,这些有效信息包含三个部分:
- 标准中注册的声明
- 公共的声明
- 私有的声明
标准中注册的声明 (建议但不强制使用):
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避时序攻击.
公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
定义一个payload:
{
"user_id":456,
"username":"zhang",
"admin":true
}
将其用base64编码:
eyJ1c2VyX2lkIjogNDU2LCAidXNlcm5hbWUiOiAiemhhbmciLCAiYWRtaW4iOiB0cnVlfQ==
signature
JWT的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去.一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了.
关于签发和核验JWT,我们可以使用Django REST framework JWT扩展来完成.
2、工作原理
签发
根据登录请求提交来的 账号 + 密码 + 设备信息 签发 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就能访问登录的用户 注:登录接口需要做 认证 + 权限 两个局部禁用 """
三、drf-jwt的安装和使用
安装
pip install djangorestframework-jwt
# 还有一个
djangorestframework-simplejwt
使用
签发:
# 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.pyConfigure authentication usage in jwt提供的jsonwebtoken
# 5 postman发送访问请求(必须带jwt空格)
认证:
from rest_framework_jwt.authentication import JSONWebTokenAuthentication # drf_jwt内置的认证类
from rest_framework.permissions import IsAuthenticated
class BookView(APIView):
# 使用drf_jwt内置的认证,必须加这两个
authentication_classes = [JSONWebTokenAuthentication, ]
permission_classes = [IsAuthenticated]
四、drf-jwt源码解析
1、obtain_jwt_token源码解析
from django.contrib import admin
from django.urls import path
from app01 import views
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', views.BookAPIView.as_view()),
path('login/', obtain_jwt_token),
]
Issue source code analysis
1、obtain_jwt_token————>到jwt下的views下的obtain_jwt_token = ObtainJSONWebToken.as_view(),
当看到ObtainJSONWebToken.as_view()时就可以断定,It must be inherited by its parent classAPIView
2、进入ObtainJSONWebToken类
class ObtainJSONWebToken(JSONWebTokenAPIView):# 继承JSONWebTokenAPIView
# 设置序列化类VerifyJSONWebTokenSerializer
serializer_class = JSONWebTokenSerializer
# Because it is used as a login submitusername、password和签发tokenSo there must be in its parent classPOST方法
3、找到其父类JSONWebTokenAPIView中的————>POST方法:
def post(self, request, *args, **kwargs):
# 得到JSONWebTokenSerializer序列化类的对象(因为在JSONWebTokenAPIView源码中重写了get_serializer和get_serializer_class方法)
{
Here we are going to skip to the first4step by step}
serializer = self.get_serializer(data=request.data)
# After coming over from the fourth step,序列化类对象is_valid,会执行字段自己的校验规则,局部钩子、全局钩子
if serializer.is_valid():
# 取出user对象和token值
user = serializer.object.get('user') or request.user
token = serializer.object.get('token')
""" 重点: jwt_response_payload_handler只有一个token返回值,这个jwt_response_payload_handlerThe method is to control what data is returned to the front end,We can override its method to customize the return content """
response_data = jwt_response_payload_handler(token, user, request)
response = Response(response_data)
if api_settings.JWT_AUTH_COOKIE:
expiration = (datetime.utcnow() +
api_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(api_settings.JWT_AUTH_COOKIE,
token,
expires=expiration,
httponly=True)
# 至此,jwt结束,将token返回至前端
return response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
4、序列化类JSONWebTokenSerializer分析:
class JSONWebTokenSerializer(Serializer):
""" 在上面第3步中,JSONWebTokenAPIView重写了get_serializermethod and its return value is return serializer_class(*args, **kwargs),这里的(*args, **kwargs)就是request.data 也就是对JSONWebTokenSerializer类进行实例化,Once a class is called within parentheses,Then it will definitely execute its internal__init__魔法方法 """
def __init__(self, *args, **kwargs):
super(JSONWebTokenSerializer, self).__init__(*args, **kwargs)
""" It can also be understood here,Why must the username and password fields passed in by its front end beusername和passowrd了 """
self.fields[self.username_field] = serializers.CharField()
self.fields['password'] = PasswordField(write_only=True) # PasswordField方法返回"password"字段
@property
def username_field(self):# 此方法(属性)返回字段"username"
return get_username_field()
# Override its parent classSerializer下的validate方法
def validate(self, attrs):
# credentials得到含有username、passwordA dictionary of fields and their values
credentials = {
self.username_field: attrs.get(self.username_field),
'password': attrs.get('password')
}
# The following are some logical judgments
if all(credentials.values()):
user = authenticate(**credentials) # 通过authenticate方法将credentials字典打散,by username and passwordauthcertified in the form
if user:
# Determine if it is active
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
""" 重点: 此处调用了jwt_payload_handler方法,jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER It can be known from this sentence,在rest_framework_jwtThere must be onesettings文件 settings————>IMPORT_STRINGS—————>JWT_PAYLOAD_HANDLER 在utils————>jwt_payload_handler方法里: payload = { 'user_id': user.pk, 'username': username, 'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA } if hasattr(user, 'email'): payload['email'] = user.email if isinstance(user.pk, uuid.UUID): payload['user_id'] = str(user.pk) payload[username_field] = username 就是为payload添加user_id、username、emailand so on for other data 然后将其payloadLoad returns """
payload = jwt_payload_handler(user)
# 此处的jwt_encode_handler就是经过settings————>IMPORT_STRINGS—————>JWT_ENCODE_HANDLER
# 就是utils下的jwt_encode_handler方法,
# 该方法会将payloadload becomestoken串返回出去(Serialization has been completed)
return {
'token': jwt_encode_handler(payload),
'user': user
}
# From here, go back to the third stepserializer.is_valid()中去
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)
认证源码解析
# 2、使用jwtexpedited issuance and certification(登录后才能访问)
from rest_framework_jwt.authentication import JSONWebTokenAuthentication # jwt内置认证类
from rest_framework.permissions import IsAuthenticated
class LoginAPIView(APIView):
# 使用drf_jwt内置的认证,必须加这两个
authentication_classes = [JSONWebTokenAuthentication,]
permission_classes = [IsAuthenticated,]
# 继承于BaseJSONWebTokenAuthentication
1、class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
www_authenticate_realm = 'api'
def get_jwt_value(self, request):
""" def get_authorization_header(request): auth = request.META.get('HTTP_AUTHORIZATION', b'') if isinstance(auth, str): # Work around django test client oddness auth = auth.encode(HTTP_HEADER_ENCODING) return auth get_authorization_headerThe method is actually passed in the front end of the acquisitionAUTHORIZATION字段,以及token值 然后用auth返回出去 """
# 对AUTHORIZATIONThe value is divided into this'jwt','cbweubeibvuiesgvebiv.cvbeuibvom=.dihwio'
auth = get_authorization_header(request).split()
# 这一句就是auth_header_prefix=JWT
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 None
# 将tokenthe previous string of the stringjwt与api_settings的比较
if smart_text(auth[0].lower()) != auth_header_prefix:
return None
if 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)
# 将token串返回出去
return auth[1]
2、JSONWebTokenAuthentication是继承于BaseJSONWebTokenAuthentication
BaseJSONWebTokenAuthentication重写了authenticate方法:
def authenticate(self, request):
# 获取到token值
jwt_value = self.get_jwt_value(request)
if jwt_value is None:
return None
try:
# 对token值进行校验,Verify whether it has been tampered with and expiration time, etc
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_id---->auth的userGet the current user from the table
user = self.authenticate_credentials(payload)
# Return it to the view layer
return (user, jwt_value)
2、jwt内置签发方法修改返回格式
# 从上面的源码中可知,To change the way it was issued to return,Is to rewrite its source codejwt_response_payload_handler方法
# And the configuration goes in the project's configuration file.查找顺序:类自己——>项目的配置文件——>其本身的settings文件
def jwt_response_payload_handler(token,user,request)
dic = {
'user_id':user.pk,
'username':user.username,
'token':token,
}
return dic
settings.py
REST_FRAMEWORK = {
'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.utils.jwt_response_payload_handler'
}
五、自定义userForm to achieve issuance and certification
1、自定义userForms are issued
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.utils import jwt_payload_handler
from rest_framework_jwt.utils import jwt_encode_handler
from .response_payload_handler import jwt_response_payload_handler
from .models import User
# 签发token
class LoginAPIView(APIView):
def get(self,request):
return Response('ok')
def post(self,request):
dic = {
'code':200,'msg':'登录成功','result':''}
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username,password=password).first()
if not user:
dic = {
'code':999,
'msg':'用户名或者密码有误'
}
return Response(dic)
# 将user对象添加至jwt_payload_handler
payload = jwt_payload_handler(user)
# 将payload变成token串返回出去
token = jwt_encode_handler(payload)
response = jwt_response_payload_handler(token,user,request)
dic['result'] = response
return Response(dic)
# response_payload_handler.py
def jwt_response_payload_handler(token, user, request):
dic = {
'username':user.username,
'createtime':user.create_time,
'token':token
}
return dic
# settings.py
REST_FRAMEWORK = {
'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.response_payload_handler.jwt_response_payload_handler'
}
2、自定义authenticateRealize verification
# views.py
from .jwt_authenticate import getBaseAuthentication
# 核验token
class getAPIView(APIView):
authentication_classes = [getBaseAuthentication, ]
def get(self,request):
return Response('ok')
# jwt_authenticate.py
from rest_framework_jwt.authentication import BaseAuthentication
from rest_framework import exceptions
import jwt
from rest_framework_jwt.utils import jwt_decode_handler
from .models import User
class getBaseAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_TOKEN')
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
msg = '签名已经过期.'
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = '错误token签名.'
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
# user = User.objects.filter(pk=payload['user_id']).first()# This has to query the database every time 降低效率
user = {
'id':payload['user_id'],'username':payload['username']}
user = User(pk=payload['user_id'],username=payload['username'])
# Return it to the view layer
return user, token
except jwt.DecodeError:
msg = '错误token签名.'
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
# user = User.objects.filter(pk=payload['user_id']).first()# This has to query the database every time 降低效率
user = {'id':payload['user_id'],'username':payload['username']}
user = User(pk=payload['user_id'],username=payload['username'])
# Return it to the view layer
return user, token
边栏推荐
猜你喜欢
随机推荐
深度学习框架pytorch快速开发与实战chapter3
The IDEA of packaged jar package
8576 Basic operations of sequential linear tables
期货具体是如何开户的?
Flask framework in-depth
[ROS] (06) ROS Communication - Topic Communication
Unit 13 Mixing in View Base Classes
Flask项目的完整创建 七牛云与容联云
jwt(json web token)
8581 Linear linked list inversion
The specific operation process of cloud GPU (Hengyuan cloud) training
【VCU】详解S19文件(S-record)
Creating seven NiuYun Flask project complete and let cloud
LayoutParams的详解
Verilog Learning Series
What are the file encryption software?Keep your files safe
yolov5,yolov4,yolov3乱七八糟的
Some impressions of the 519 plummet 2021-05-21
paddleocr window10 first experience
Sentinel源码(二)入口方法分析