simplejwt双token
pyJwt 安装
pip PyJWT
JWT参数
token comment"># JWT官网的三个加密参数为
# 1.header(type,algorithm)
# {
# "alg": "HS256",
# "typ": "JWT"
# }# 2.playload(iss,sub,aud,exp,nbf,lat,jti)
# iss: jwt签发者
# sub: jwt所面向的用户
# aud: 接收jwt的一方
# exp: jwt的过期时间,这个过期时间必须要大于签发时间
# nbf: 定义在什么时间之前,该jwt都是不可用的.
# iat: jwt的签发时间
# jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。# 3.signature
# jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
# header (base64后的)
# payload (base64后的)
# secret# PyJwt官网的三个加密参数为
# jwt.encode(playload, key, algorithm='HS256')
# playload 同上,key为SECRET_KEY,algorithm 为加密算法
使用
import jwt
from jwt import ExpiredSignatureError
from pytz import timezonesecret = "f7aa59c6-fd5d-407b-862b-f4d2d2ecf0de"
issuer = "General_zy"
audience = "user"def encode_token():import datetime# 这里需要指明时区为Asia/Shanghai,否则token会+8:00now = datetime.datetime.now(tz=timezone('Asia/Shanghai'))dic = {# 过期时间'exp': now + datetime.timedelta(seconds=5),# 签发时间'iat': now,# 签发方'iss': issuer,# 接收人"aud": audience,}token_bytes = jwt.encode(dic, key=secret, algorithm='HS256')return token_bytes.decode("utf-8")def decode_token(token):if isinstance(token, str):token = token.encode("utf-8")# 解密,校验签名try:payload = jwt.decode(token, key=secret, issuer=issuer, audience=audience, algorithms=['HS256'])return payloadexcept ExpiredSignatureError as e:print(f"token过期:{str(e)}")return Noneexcept Exception as e:print(f"其他错误:{str(e)}")if __name__ == '__main__':# 1. 生成token# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzQyMDU4NzcsImlhdCI6MTY3NDIwNTg3MiwiaXNzIjoiR2VuZXJhbF96eSIsImF1ZCI6InVzZXIifQ.O_LUfOP4fXnFsRgGZrCEjxW8nD5WB3s8WBXNsUsIqToprint(encode_token())# 2. 解码tokendata = decode_token("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzQyMDU4NzcsImlhdCI6MTY3NDIwNTg3MiwiaXNzIjoiR2VuZXJhbF96eSIsImF1ZCI6InVzZXIifQ.O_LUfOP4fXnFsRgGZrCEjxW8nD5WB3s8WBXNsUsIqTo")print(data)
参数详解
exp
exp指过期时间,在生成token时,可以设置该token的有效时间,如果设置1天过期,1天后再解析此token会抛出
jwt.exceptions.ExpiredSignatureError: Signature has expirednbf
nbf类似于token的 lat ,它指的是该token的生效时间,如果使用但是没到生效时间则抛出
jwt.exceptions.ImmatureSignatureError: The token is not yet valid (nbf)iss
iss指的是该token的签发者,可以给一个字符串。iss 在接收时如果不检验也没有问题,如果接收时需要检验但是又签名不一致,则会抛出
jwt.exceptions.InvalidIssuerError: Invalid issueraud
aud指定了接收者,接收者在接收时必须提供与token要求的一致的接收者(字符串),如果没写接收者或者接收者不一致会抛出
jwt.exceptions.InvalidAudienceError: Invalid audienceiat
iat指的是token的开始时间,如果当前时间在开始时间之前则抛出
jwt.exceptions.InvalidIssuedAtError: Issued At claim (iat) cannot be in the future.
双token
双token即两个token,首次登陆时服务端返回两个token ,和,过期时间比较短,时间较长,且每次使用后会刷新,每次刷新后的都是不同的。
解决的问题
用户正在app或者应用中操作 token突然过期,此时用户不得不返回登陆界面,重新进行一次登录,这种体验性不好,于是引入双token校验机制。
流程
的存在,保证了登录态的正常验证,因其过期时间的短暂也保证了帐号的安全性
的存在,保证了用户无需在短时间内进行反复的登陆操作来保证登录态的有效性,同时也保证了活跃用户的登录态可以一直存续而不需要进行重新登录,反复刷新也防止攻击者获取后对用户帐号进行破坏操作。
登录操作,在后台服务器验证账号密码成功之后返回2个token:和。
在进行服务器请求的时候携带双token,如果有效,则正常返回请求结果;如果无效,则验证。
此时如果有效则返回请求结果和新的和新的。如果无效,则提示用户进行重新登陆操作。
无效的Token的处理
对于频繁更换的Token,如何处理旧的未过期的而又无效的.
从浏览器的存储中删除黑名单(-中有维护的黑名单)token的过期时间设置的足够短 - 安装
pip install djangorestframework-simplejwt
的配置文件 :用于token失效时,刷新获得新的token。:就是jwt里面的token。
# 需要在django配置的关键字为"SIMPLE_JWT"
USER_SETTINGS = getattr(settings, "SIMPLE_JWT", None)# 默认配置
DEFAULTS = {# 访问令牌的有效时间"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),# 刷新令牌的有效时间"REFRESH_TOKEN_LIFETIME": timedelta(days=1),# 若为True,则刷新后新的refresh_token有更新的有效时间"ROTATE_REFRESH_TOKENS": False,# 若为True,刷新后的token将添加到黑名单中# When True,'rest_framework_simplejwt.token_blacklist',should add to INSTALLED_APP"BLACKLIST_AFTER_ROTATION": False,"UPDATE_LAST_LOGIN": False,# 加密算法"ALGORITHM": "HS256",# 加密密钥"SIGNING_KEY": settings.SECRET_KEY,"VERIFYING_KEY": "","AUDIENCE": None,"ISSUER": None,"JSON_ENCODER": None,"JWK_URL": None,"LEEWAY": 0,"AUTH_HEADER_TYPES": ("Bearer",),"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION","USER_ID_FIELD": "id","USER_ID_CLAIM": "user_id","USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule","AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),"TOKEN_TYPE_CLAIM": "token_type","JTI_CLAIM": "jti","TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser","SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp","SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer","TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer","TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer","TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer","SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer","SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}
配置drf和
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles',# 自己的应用...'rest_framework', # 注册DRF应用
]# simplejwt配置, 需要导入datetime模块
SIMPLE_JWT = {# token有效时长'ACCESS_TOKEN_LIFETIME': datetime.timedelta(minutes=30),# token刷新后的有效时间'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1),
}
默认视图验证类
# settings.py
REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework_simplejwt.authentication.JWTAuthentication', # 使用rest_framework_simplejwt验证身份'rest_framework.authentication.SessionAuthentication','rest_framework.authentication.BasicAuthentication'],'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated' # 默认权限为验证用户],
}# urls.py
from django.contrib import admin
from django.urls import path, include# 导入 simplejwt 提供的几个验证视图类
from rest_framework_simplejwt.views import (TokenObtainPairView,TokenRefreshView,TokenVerifyView
)urlpatterns = [# Django 后台path('admin/', admin.site.urls),# DRF 提供的一系列身份认证的接口,用于在页面中认证身份。path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),# 获取Token的接口path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),# 刷新Token有效期的接口path('api/refresh/', TokenRefreshView.as_view(), name='token_refresh'),# 验证Token的有效性path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]
自定义校验
class Student(models.Model):username = models.CharField(max_length=32)password = models.CharField(max_length=32)card = models.CharField(max_length=8)name = models.CharField(max_length=32)
class StudentLoginSerializer(serializers.Serializer):username = serializers.CharField(required=True, label="用户名")password = serializers.CharField(required=True, label="密码")def validate(self, attrs):username = attrs.get("username")password = attrs.get("password")user = models.Student.objects.filter(username=username, password=password).first()if user:refresh = RefreshToken.for_user(user)self.context["token"] = refreshreturn attrselse:raise ValidationError("用户名或密码错误")
# 登录签发token
class LoginViewSet(ViewSet):@action(methods=["POST"], detail=False)def login(self, request):login_ser = ser.StudentLoginSerializer(data=self.request.data)if login_ser.is_valid():token_obj = login_ser.context["token"]return Response(data={"err": "", "code": 0,"access_token": str(token_obj.access_token),"refresh_token": str(token_obj)})else:return Response(data={"err": login_ser.errors.get("non_field_errors")[0],"code": 1,})
tags:
token