Authlib中JWTClaims类型异常导致JWT exp验证失败
Authlib JWT解码后声明类型异常导致exp验证失败
我研究Authlib包好几天了,想用authlib.jose的JsonWebToken类的encode()和decode()复现标准JWT认证流程。编码没问题,能正常生成JWT,但解码验证环节卡壳了——调用validate()时一直抛出异常:
invalid_claim: Invalid claim "exp"
排查后发现,claims_options里用的validate_exp()函数会在_validate_numeric_time()中检查"exp"声明的类型,要求是int或float,但JWTClaims实例解码后所有键值全是str类型,exp这类时间相关声明的float类型没保留,直接导致了这个异常。
可复现代码
from typing import Any, List, Literal, Optional, Self, Union from datetime import datetime, timedelta, timezone from uuid import uuid4 from authlib.jose import JsonWebToken, JWTClaims from authlib.jose.errors import InvalidClaimError # 假设SECRETS是已定义的配置字典 SECRETS = { "PRIVATE_KEY_FILE_PATH": "private_key.pem", "PUBLIC_KEY_FILE_PATH": "public_key.pem" } jwt = JsonWebToken(["RS256"]) # 读取密钥文件 private_pem_file = open(SECRETS["PRIVATE_KEY_FILE_PATH"]) private_key = private_pem_file.read() private_pem_file.close() public_pem_file = open(SECRETS["PUBLIC_KEY_FILE_PATH"]) public_key = public_pem_file.read() public_pem_file.close() header = {"alg": "RS256"} # 配置JWT有效期 lifespan: timedelta = timedelta(minutes=15) current_time: datetime = datetime.now(tz=timezone(timedelta(hours=5, minutes=30))) current_time_plus_lifespan: datetime = current_time + lifespan # 生成时间声明 issued_at: float = current_time.timestamp() expiration_time: float = current_time_plus_lifespan.timestamp() not_before: float = current_time.timestamp() jwt_id: uuid4 = uuid4() # 构造payload(注意修正UUID序列化) payload: dict[str, Union[float, List[str], str]] = { "iss": "HarshitDoshi", "sub": "Harshit.Doshi@example.org", "aud": ["https://example.org", "https://authentication.example.org"], "iat": issued_at, "exp": expiration_time, "nbf": not_before, "jti": str(jwt_id), } # 编码JWT jwt_encoded = jwt.encode(header, payload, private_key, check=True).decode("utf-8") # 解码JWT(修正变量名错误) jwt_decoded = jwt.decode( s=jwt_encoded, key=public_key, claims_options={ "iss": { "essential": True, "value": "HarshitDoshi", }, "sub": { "essential": True, "value": "Harshit.Doshi@example.org", }, "aud": { "essential": True, "values": ['https://example.org', 'https://authentication.example.org'], }, "iat": { "essential": True, "validate": JWTClaims.validate_iat, }, "exp": { "essential": True, "validate": JWTClaims.validate_exp, }, "nbf": { "essential": True, "validate": JWTClaims.validate_nbf, }, "jti": { "essential": True, "value": str(jwt_id), }, }, ) # 验证声明 jwt_decoded.validate()
调试输出
我用print调试输出了JWTClaims的键值类型:
for k in jwt_decoded.keys(): print(f"\n{k}: {jwt_decoded[k]} - {type(jwt_decoded[k])}")
结果显示所有值都是字符串类型(包括本应是数值的时间声明和数组类型的aud):
iss: HarshitDoshi - <class 'str'>
sub: Harshit.Doshi@example.org - <class 'str'>
aud: ['https://example.org', 'https://authentication.example.org'] - <class 'str'>
iat: 1681022938.106472 - <class 'str'>
exp: 1681023838.106472 - <class 'str'>
nbf: 1681022938.106472 - <class 'str'>
问题原因与解决方案
核心原因
- UUID对象序列化异常:原代码中payload的
jti字段直接传入UUID实例,JSON标准序列化器无法处理该类型,导致Authlib编码时异常处理整个payload,将其强制转为字符串,最终解码后所有字段都变成字符串类型。 - 变量名笔误:解码时使用了未定义的
encoded_jwt变量,正确变量名应为jwt_encoded。
修复步骤
- 修正UUID序列化:将payload中的
jti字段改为字符串类型的UUID:"jti": str(jwt_id),确保JSON序列化正常。 - 修正解码变量名:把
jwt.decode(s=encoded_jwt, ...)改为jwt.decode(s=jwt_encoded, ...)。 - (可选)手动类型转换:若解码后部分字段仍为字符串,可在验证前手动转换类型:
# 转换时间声明为数值类型 for claim in ['iat', 'exp', 'nbf']: jwt_decoded[claim] = float(jwt_decoded[claim]) # 转换aud为列表(如果被转为字符串) import json if isinstance(jwt_decoded['aud'], str): jwt_decoded['aud'] = json.loads(jwt_decoded['aud'])
内容的提问来源于stack exchange,提问作者hash
相关产品推荐
相关产品推荐

