首页 > 解决方案 > (MissingClaimError) 使用 Praetorian 生成 JWT 令牌的 Python 烧瓶!str(uuid.uuid4()) 返回 null

问题描述

我正在执行一项检查由 Python Praetorian 创建的 JWT 令牌的有效性的任务。我使用 encode_jwt_token() (来自 Praetorian)在用户登录时生成令牌(定义如下):

def encode_jwt_token(
        self, user,
        override_access_lifespan=None, override_refresh_lifespan=None,
        bypass_user_check=False, is_registration_token=False,
        is_reset_token=False,
        **custom_claims
):
    """
    Encodes user data into a jwt token that can be used for authorization
    at protected endpoints

    :param: override_access_lifespan:  Override's the instance's access
                                       lifespan to set a custom duration
                                       after which the new token's
                                       accessability will expire. May not
                                       exceed the refresh_lifespan
    :param: override_refresh_lifespan: Override's the instance's refresh
                                       lifespan to set a custom duration
                                       after which the new token's
                                       refreshability will expire.
    :param: bypass_user_check:         Override checking the user for
                                       being real/active.  Used for
                                       registration token generation.
    :param: is_registration_token:     Indicates that the token will be
                                       used only for email-based
                                       registration
    :param: custom_claims:             Additional claims that should
                                       be packed in the payload. Note that
                                       any claims supplied here must be
                                       JSON compatible types
    """
    ClaimCollisionError.require_condition(
        set(custom_claims.keys()).isdisjoint(RESERVED_CLAIMS),
        "The custom claims collide with required claims",
    )
    if not bypass_user_check:
        self._check_user(user)

    moment = pendulum.now('UTC')

    if override_refresh_lifespan is None:
        refresh_lifespan = self.refresh_lifespan
    else:
        refresh_lifespan = override_refresh_lifespan
    refresh_expiration = (moment + refresh_lifespan).int_timestamp

    if override_access_lifespan is None:
        access_lifespan = self.access_lifespan
    else:
        access_lifespan = override_access_lifespan
    access_expiration = min(
        (moment + access_lifespan).int_timestamp,
        refresh_expiration,
    )

    payload_parts = {
        'iat': moment.int_timestamp,
        'exp': access_expiration,
        'jti': str(uuid.uuid4()),
        'id': user.identity,
        'rls': ','.join(user.rolenames),
        REFRESH_EXPIRATION_CLAIM: refresh_expiration,
    }
    if is_registration_token:
        payload_parts[IS_REGISTRATION_TOKEN_CLAIM] = True
    if is_reset_token:
        payload_parts[IS_RESET_TOKEN_CLAIM] = True
    flask.current_app.logger.debug(
        "Attaching custom claims: {}".format(custom_claims),
    )
    payload_parts.update(custom_claims)

    if self.encode_jwt_token_hook:
        self.encode_jwt_token_hook(**payload_parts)
    return jwt.encode(
        payload_parts, self.encode_key, self.encode_algorithm,
    )

为了检查令牌是否过期,我调用 _validate_jwt_data() (来自 Praetorian)来检查是否抛出了 ExpiredAccessError。问题是我总是遇到 MissingClaimError(Token is missing jti claim)。这是验证的定义:

def _validate_jwt_data(self, data, access_type):
    """
    Validates that the data for a jwt token is valid
    """
    MissingClaimError.require_condition(
        'jti' in data,
        'Token is missing jti claim',
    )
    BlacklistedError.require_condition(
        not self.is_blacklisted(data['jti']),
        'Token has a blacklisted jti',
    )
    MissingClaimError.require_condition(
        'id' in data,
        'Token is missing id field',
    )
    MissingClaimError.require_condition(
        'exp' in data,
        'Token is missing exp claim',
    )
    MissingClaimError.require_condition(
        REFRESH_EXPIRATION_CLAIM in data,
        'Token is missing {} claim'.format(REFRESH_EXPIRATION_CLAIM),
    )
    moment = pendulum.now('UTC').int_timestamp
    if access_type == AccessType.access:
        MisusedRegistrationToken.require_condition(
            IS_REGISTRATION_TOKEN_CLAIM not in data,
            "registration token used for access"
        )
        MisusedResetToken.require_condition(
            IS_RESET_TOKEN_CLAIM not in data,
            "password reset token used for access"
        )
        ExpiredAccessError.require_condition(
            moment <= data['exp'],
            'access permission has expired',
        )
    elif access_type == AccessType.refresh:
        MisusedRegistrationToken.require_condition(
            IS_REGISTRATION_TOKEN_CLAIM not in data,
            "registration token used for refresh"
        )
        MisusedResetToken.require_condition(
            IS_RESET_TOKEN_CLAIM not in data,
            "password reset token used for refresh"
        )
        EarlyRefreshError.require_condition(
            moment > data['exp'],
            'access permission for token has not expired. may not refresh',
        )
        ExpiredRefreshError.require_condition(
            moment <= data[REFRESH_EXPIRATION_CLAIM],
            'refresh permission for token has expired',
        )
    elif access_type == AccessType.register:
        ExpiredAccessError.require_condition(
            moment <= data['exp'],
            'register permission has expired',
        )
        InvalidRegistrationToken.require_condition(
            IS_REGISTRATION_TOKEN_CLAIM in data,
            "invalid registration token used for verification"
        )
        MisusedResetToken.require_condition(
            IS_RESET_TOKEN_CLAIM not in data,
            "password reset token used for registration"
        )
    elif access_type == AccessType.reset:
        MisusedRegistrationToken.require_condition(
            IS_REGISTRATION_TOKEN_CLAIM not in data,
            "registration token used for reset"
        )
        ExpiredAccessError.require_condition(
            moment <= data['exp'],
            'reset permission has expired',
        )
        InvalidResetToken.require_condition(
            IS_RESET_TOKEN_CLAIM in data,
            "invalid reset token used for verification"
        )

在查看了 encode_jwt_token() 的工作原理后,我看到使用了“jti : str(uuid.uuid4())”。这将为我的令牌创建一个唯一 ID。

总结一下: encode_jwt_token() 在我创建令牌时自动设置“jti”。它使用 str(uuid.uuid4()) 返回“jti: null”。看来我无法从 Praetorian 更改此定义的功能。

我需要: 找到一种方法,使“jti”被签署一个有效值。或者弄清楚为什么 str(uuid.uuid4()) 返回 null。

问题: 我认为忽略我的令牌上的这个空 ID 不是最好的主意。是这样吗?为什么?

这是我调用来检查令牌并在需要时刷新它的函数。它使用from ..extensions import guard

@auth_blueprint.route("/refresh", methods=['POST'])
def refresh():
 data = request.json
 print(data)
 curent_token = data['token']

 try:
     guard._validate_jwt_data(curent_token, access_type=  AccessType.access )
 except MissingClaimError as e:
     ret = {'token' : curent_token, 'status' : 'MissingClaimError'}
     print(e)
 except ExpiredAccessError:
     print('This is the expired token access: ' + curent_token )
     print('We caught an error ExpiredAcessError! ')
     print("Begin refresh request")
     try:
         new_token = guard.refresh_jwt_token(curent_token) 
         print("Token was refreshed!")
         ret = {'token': new_token , 'status': 'Refreshed access'}
     except ExpiredRefreshError: 
         print("ExpiredRefreshError, request login!")
         ret = {'token' : 'LogInYouDonkey' , 'status': 'could not refresh '}
         return ret, 403

 return ret, 200

标签: pythonauthenticationjwtaccess-tokenflask-praetorian

解决方案


推荐阅读