pyjwt和djangorestframework-simplejwt双token

pyJwt

安装

pip install PyJWT

JWT参数

# 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 timezone

secret = "f7aa59c6-fd5d-407b-862b-f4d2d2ecf0de"
issuer = "General_zy"
audience = "user"


def encode_token():
    import datetime
    # 这里需要指明时区为Asia/Shanghai,否则token会+8:00
    now = 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 payload
    except ExpiredSignatureError as e:
        print(f"token过期:{str(e)}")
        return None
    except Exception as e:
        print(f"其他错误:{str(e)}")


if __name__ == '__main__':
    # 1. 生成token
    # eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzQyMDU4NzcsImlhdCI6MTY3NDIwNTg3MiwiaXNzIjoiR2VuZXJhbF96eSIsImF1ZCI6InVzZXIifQ.O_LUfOP4fXnFsRgGZrCEjxW8nD5WB3s8WBXNsUsIqTo
    print(encode_token())

    # 2. 解码token
    data = decode_token("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzQyMDU4NzcsImlhdCI6MTY3NDIwNTg3MiwiaXNzIjoiR2VuZXJhbF96eSIsImF1ZCI6InVzZXIifQ.O_LUfOP4fXnFsRgGZrCEjxW8nD5WB3s8WBXNsUsIqTo")
    print(data)

在这里插入图片描述

参数详解

exp
exp指过期时间,在生成token时,可以设置该token的有效时间,如果设置1天过期,1天后再解析此token会抛出
jwt.exceptions.ExpiredSignatureError: Signature has expired

nbf
nbf类似于token的 lat ,它指的是该token的生效时间,如果使用但是没到生效时间则抛出
jwt.exceptions.ImmatureSignatureError: The token is not yet valid (nbf)

iss
iss指的是该token的签发者,可以给一个字符串。iss 在接收时如果不检验也没有问题,如果接收时需要检验但是又签名不一致,则会抛出
jwt.exceptions.InvalidIssuerError: Invalid issuer

aud
aud指定了接收者,接收者在接收时必须提供与token要求的一致的接收者(字符串),如果没写接收者或者接收者不一致会抛出
jwt.exceptions.InvalidAudienceError: Invalid audience

iat
iat指的是token的开始时间,如果当前时间在开始时间之前则抛出
jwt.exceptions.InvalidIssuedAtError: Issued At claim (iat) cannot be in the future.

双token

双token即两个token,首次登陆时服务端返回两个token ,accessToken和refreshToken,accessToken过期时间比较短,refreshToken时间较长,且每次使用后会刷新,每次刷新后的refreshToken都是不同的。

解决的问题

用户正在app或者应用中操作 token突然过期,此时用户不得不返回登陆界面,重新进行一次登录,这种体验性不好,于是引入双token校验机制。

流程

accessToken的存在,保证了登录态的正常验证,因其过期时间的短暂也保证了帐号的安全性

​refreshToekn的存在,保证了用户无需在短时间内进行反复的登陆操作来保证登录态的有效性,同时也保证了活跃用户的登录态可以一直存续而不需要进行重新登录,反复刷新也防止攻击者获取refreshToken后对用户帐号进行破坏操作。

  1. 登录操作,在后台服务器验证账号密码成功之后返回2个token:accessToken和refreshToken。

  2. 在进行服务器请求的时候携带双token,如果accessToken有效,则正常返回请求结果;如果accessToken无效,则验证refreshToken。

  3. 此时如果refreshToken有效则返回请求结果和新的accessToken和新的refreshToken。如果refreshToken无效,则提示用户进行重新登陆操作。

在这里插入图片描述

无效的Token的处理

对于频繁更换的Token,如何处理旧的未过期的而又无效的refreshToken.

  1. 从浏览器的存储中删除
  2. 黑名单(djangorestframework-simplejwt中有维护的黑名单)
  3. token的过期时间设置的足够短

djangorestframework-simplejwt

安装

pip install djangorestframework-simplejwt

simplejwt的配置文件

  1. refresh_token:用于token失效时,刷新获得新的token。
  2. access_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和simplejwt

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"] = refresh
            return attrs
        else:
            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,
            })

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Generalzy

倍感荣幸文章对您有帮助

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

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

打赏作者

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

抵扣说明:

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

余额充值