当前位置:   article > 正文

Python用户认证JWT——PyJWT

pyjwt

简介

PyJWT 是一款 Python 编解码 JWT 的库

JWTJSON Web Token,基于 RFC7519,用于跨域认证 ,便于服务器集群认证

服务器认证后,生成一个 JWT 返回客户端,之后客户端与服务器通信都要发回这个 JSON,用于身份认证

客户端收到服务器返回的 JWT,可以存在 Cookie 里,也可以存在 localStorage

客户端通信时,可以放在 Cookie 里,也可以放在 Headers 的 Authorization 里,后一种可以解决跨域问题

JWT. 分隔,分别为:

  • Header(头部)
    • 描述 JWT 的元数据,如 {"alg": "HS256", "typ": "JWT"}
    • alg :生成签名算法,默认为 HS256
    • typ:令牌类型,默认为 JWT
  • Payload(负载)
    • 存放实际数据,官方字段如下
    • exp (Expiration Time):过期时间
    • nbf (Not Before Time):生效时间
    • iss (Issuer):签发人
    • aud (Audience):受众
    • iat (Issued At):签发时间
    • sub (Subject):主题
    • jti (JWT ID):编号
  • Verify Signature(签名)

推荐阅读:




安装

pip install PyJWT
  • 1




初试

import jwt

encoded_jwt = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')  # 编码
decoded_jwt = jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])  # 解码
print(encoded_jwt)  # eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U
print(decoded_jwt)  # {'some': 'payload'}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6




HS256编解码

多数 JWT 使用对称算法 HS256 生成

import jwt

encoded_jwt = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')  # 编码
decoded_jwt = jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])  # 解码
print(encoded_jwt)  # eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U
print(decoded_jwt)  # {'some': 'payload'}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6




RS256编解码

RS256 是非对称加密算法

安装

pip install cryptography
  • 1

代码

import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa

private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()
private_key = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.TraditionalOpenSSL,
    encryption_algorithm=serialization.NoEncryption()
)
public_key = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

encoded_jwt = jwt.encode({'some': 'payload'}, private_key, algorithm='RS256')  # 编码
decoded_jwt = jwt.decode(encoded_jwt, public_key, algorithms=['RS256'])  # 解码
print(encoded_jwt)
print(decoded_jwt)  # {'some': 'payload'}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20




自定义头部

import jwt

encoded_jwt = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256',
                         headers={'kid': '230498151c214b788dd97f22b85410a5'})  # 编码
decoded_jwt = jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])  # 解码
print(encoded_jwt)
print(decoded_jwt)  # {'some': 'payload'}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7




不进行签名校验

参数 options={'verify_signature': False} 不进行签名校验

import jwt

encoded_jwt = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')  # 编码
decoded_jwt = jwt.decode(encoded_jwt, options={'verify_signature': False})  # 解码
print(encoded_jwt)
print(decoded_jwt)  # {'some': 'payload'}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6




读取头部不进行签名校验

import jwt

encoded_jwt = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')  # 编码
unverified_header = jwt.get_unverified_header(encoded_jwt)  # 读取头部
print(unverified_header)  # {'typ': 'JWT', 'alg': 'HS256'}
  • 1
  • 2
  • 3
  • 4
  • 5




声明负载字段

支持以下字段

过期时间

exp (Expiration Time):过期时间,可用 UTC 时间戳或 datetime

解析超时报错 jwt.ExpiredSignatureError

参数 options={'verify_exp': False} 不进行过期时间校验

import datetime

import jwt

timestamp = int(datetime.datetime(2022, 1, 1).timestamp())  # 2022-01-01的时间戳,类型为int
after_a_week = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(days=7)  # 一周后,类型为datetime

# 超时报错
encoded_jwt = jwt.encode({'exp': timestamp}, 'secret', algorithm='HS256')
try:
    jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])
except jwt.ExpiredSignatureError as e:
    print(e)  # Signature has expired
decoded_jwt = jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'], options={'verify_exp': False})  # 不进行过期时间校验
print(decoded_jwt)  # {'exp': 1640966400}

# 正常解析
encoded_jwt = jwt.encode({'exp': after_a_week}, 'secret', algorithm='HS256')
decoded_jwt = jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])
print(decoded_jwt)  # {'exp': 1663252132}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20



生效时间

nbf (Not Before Time):生效时间,可用 UTC 时间戳或 datetime

解析未生效报错 jwt.ExpiredSignatureError

参数 options={'verify_nbf': False} 不进行过期时间校验

import datetime

import jwt

timestamp = int(datetime.datetime(2022, 1, 1).timestamp())  # 2022-01-01的时间戳,类型为int
after_a_week = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(days=7)  # 一周后,类型为datetime

# 未生效报错
encoded_jwt = jwt.encode({'nbf': after_a_week}, 'secret', algorithm='HS256')
try:
    jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])
except jwt.ImmatureSignatureError as e:
    print(e)  # The token is not yet valid (nbf)
decoded_jwt = jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'], options={'verify_nbf': False})  # 不进行生效时间校验
print(decoded_jwt)  # {'nbf': 1663252132}

# 正常解析
encoded_jwt = jwt.encode({'nbf': timestamp}, 'secret', algorithm='HS256')
decoded_jwt = jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])
print(decoded_jwt)  # {'nbf': 1640966400}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20



签发人

iss (Issuer):签发人

签发人不匹配报错 jwt.InvalidIssuerError

import jwt

payload = {'some': 'payload', 'iss': 'urn:foo'}

encoded_jwt = jwt.encode(payload, 'secret', algorithm='HS256')

# 未生效报错
try:
    jwt.decode(encoded_jwt, 'secret', issuer='tli:foo', algorithms=['HS256'])
except jwt.InvalidIssuerError as e:
    print(e)  # Invalid issuer

# 正常解析
decoded_jwt = jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])
print(decoded_jwt)  # {'some': 'payload', 'iss': 'urn:foo'}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15



受众

aud (Audience):受众

受众不匹配报错 jwt.InvalidAudienceError

import jwt

payload = {'some': 'payload', 'aud': ['urn:foo', 'urn:bar']}

encoded_jwt = jwt.encode(payload, 'secret', algorithm='HS256')

# 未生效报错
try:
    jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])
except jwt.InvalidAudienceError as e:
    print(e)  # Invalid audience

# 正常解析
decoded_jwt = jwt.decode(encoded_jwt, 'secret', audience='urn:foo', algorithms=['HS256'])
print(decoded_jwt)  # {'some': 'payload', 'aud': ['urn:foo', 'urn:bar']}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15



签发时间

iat (Issued At):签发时间

签发时间非数字或 datetime 报错 jwt.InvalidIssuedAtError

import datetime

import jwt

now = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
payload = {'some': 'payload', 'iat': now}

encoded_jwt = jwt.encode(payload, 'secret', algorithm='HS256')
decoded_jwt = jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])
print(decoded_jwt)  # {'some': 'payload', 'iat': 1662620247}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10




必填负载字段

必填 expisssub

import datetime

import jwt

after_a_week = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(days=7)  # 一周后,类型为datetime
payload = {'some': 'payload', 'exp': after_a_week, 'iss': 'urn:foo', 'sub': 'foo'}

encoded_jwt = jwt.encode(payload, 'secret', algorithm='HS256')
decoded_jwt = jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'], options={'require': ['exp', 'iss', 'sub']})
print(decoded_jwt)  # {'some': 'payload', 'exp': 1663225440, 'iss': 'urn:foo', 'sub': 'foo'}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10




从JWKS端点检索RSA签名密钥

JWKS,JSON Web Key Set,指多个 JWK 组合在一起

import jwt
from jwt import PyJWKClient

token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA'
kid = 'NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw'
url = 'https://dev-87evx9ru.auth0.com/.well-known/jwks.json'
jwks_client = PyJWKClient(url)
signing_key = jwks_client.get_signing_key_from_jwt(token)
data = jwt.decode(
    token,
    signing_key.key,
    algorithms=['RS256'],
    audience='https://expenses-api',
    options={'verify_exp': False},
)
print(data)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16




常见问题




数字签名算法




参考文献

  1. PyJWT Documentation
  2. Cryptography Documentation
  3. JSON Web Token 入门教程 - 阮一峰的网络日志
  4. Python JWT使用
  5. RS256 vs HS256 What’s the difference?
  6. Python日期和时间库datetime
  7. Python时间访问和转换库time
  8. JWT实现登陆认证及Token自动续期
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/508538
推荐阅读
相关标签
  

闽ICP备14008679号