首页 > 编程语言 >PyJWT 和 python-jose 在处理JWT令牌处理的时候的差异和具体使用

PyJWT 和 python-jose 在处理JWT令牌处理的时候的差异和具体使用

时间:2024-08-27 10:39:26浏览次数:10  
标签:jose python JWT token user id

PyJWTpython-jose 是两个用于处理 JSON Web Tokens (JWT) 的 Python 库。它们都有助于生成、解码、验证和管理 JWT,但它们在功能范围和设计哲学上有一些重要的区别。本篇介绍它们之间的一些差异,以及在项目中使用FastAPI+ python-jose 来处理访问令牌的生成以及一些例子代码供参考。

1、PyJWT 和 python-jose的差异

PyJWT

PyJWT 是一个专门处理 JWT 的 Python 库,它旨在简化 JWT 的创建和验证。

特点:

  • 专注于 JWT: PyJWT 专门用于 JWT 的处理,不提供其他类型的加密或签名功能。
  • 简单易用: PyJWT 提供了简单的 API,用于创建和验证 JWT。
  • 支持常见的签名算法: 包括 HMAC (HS256, HS384, HS512) 和 RSA (RS256, RS384, RS512)。
  • 轻量级: 由于 PyJWT 专注于 JWT,依赖少,安装和使用都很简便。

主要用法示例:

import jwt
import datetime

# 创建一个JWT
payload = {
    "user_id": 123,
    "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
secret = 'your-secret-key'
token = jwt.encode(payload, secret, algorithm='HS256')

# 解码JWT
decoded = jwt.decode(token, secret, algorithms=['HS256'])
print(decoded)

python-jose

python-jose 是一个更广泛的加密库,它不仅支持 JWT,还支持多种 JOSE (JSON Object Signing and Encryption) 标准,包括 JWS (JSON Web Signature)、JWE (JSON Web Encryption)、JWK (JSON Web Key)、JWA (JSON Web Algorithms) 等。

特点:

  • 全面的 JOSE 支持: 除了 JWT,python-jose 还支持其他 JOSE 标准,因此功能更强大、更灵活。
  • 多种加密与签名算法: 支持比 PyJWT 更多的算法,如直接加密 (dir)、对称密钥加密 (A256KW, A192KW, A128KW),以及 RSA 和 ECDSA 的多种模式。
  • 丰富的功能: 提供更细粒度的控制,适合需要复杂加密和签名操作的应用场景。
  • 相对复杂: 由于功能更广泛,python-jose 的使用复杂度比 PyJWT 更高。

主要用法示例:

from jose import jwt
from jose.exceptions import JWTError

# 创建一个JWT
payload = {
    "user_id": 123,
    "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
secret = 'your-secret-key'
token = jwt.encode(payload, secret, algorithm='HS256')

# 解码JWT
try:
    decoded = jwt.decode(token, secret, algorithms=['HS256'])
    print(decoded)
except JWTError as e:
    print(f"Token is invalid: {e}")

差异总结

  • 功能范围: PyJWT 专注于 JWT,适合需要简单 JWT 处理的项目;python-jose 则支持整个 JOSE 标准,适合需要更复杂加密和签名操作的项目。
  • 易用性: PyJWT API 简单,易于上手;python-jose 更强大,但同时也更复杂。
  • 算法支持: python-jose 支持的算法更广泛,尤其是在需要高级加密或签名场景时更具优势。
  • 使用场景: 如果你的项目只需要生成和验证 JWT,PyJWT 是一个不错的选择;如果你需要全面的 JOSE 支持,包括 JWS、JWE 等,或者需要复杂的加密和签名,python-jose 是更好的选择。

 

2、使用 python-jose 处理 JWT 

在使用 python-jose 处理 JWT 时,捕获和处理异常是一个重要的环节。

1) 安装 python-jose

首先,确保你已经安装了 python-jose

pip install python-jose

2)使用 python-jose 的 JWT 模块

以下是一个使用 python-jose 的 JWT 处理的示例,包括如何捕获异常:

from jose import jwt, JWTError
from jose.exceptions import ExpiredSignatureError

# 定义密钥和有效负载
secret = 'your-secret-key'
payload = {
    "user_id": 123,
    "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}

# 生成 JWT
token = jwt.encode(payload, secret, algorithm='HS256')

# 解码 JWT 并处理可能的异常
try:
    decoded = jwt.decode(token, secret, algorithms=['HS256'])
    print(decoded)
except ExpiredSignatureError:
    print("Token has expired")
except JWTError:
    print("Token is invalid")

在处理 python-jose 的 JWT 时,正确地捕获和处理异常是关键。确保你的环境和工具能够正确识别异常类型,将有助于你更好地管理 JWT 错误。

如果我们需要再JWT的playload里面承载更多的信息,可以再claim中声明键值即可,你可以使用 python-jose 和 FastAPI 来实现 JWT 令牌生成操作。如下所示代码。

使用 FastAPIpython-jose 生成 JWT 令牌:

from fastapi import FastAPI, Depends, HTTPException, status, Request
from fastapi.responses import JSONResponse
from jose import JWTError, jwt
from datetime import datetime, timedelta

app = FastAPI()

# 配置项,通常从配置文件中加载
JWT_SECRET_KEY = 'your_jwt_secret_key'
JWT_ISSUER = 'your_issuer'
JWT_AUDIENCE = 'your_audience'
JWT_EXPIRED_DAYS = 7
ALGORITHM = 'HS256'

def generate_token(user_info: dict, role_type: str):
    # 获取IP地址
    ip = user_info.get('ip', '')

    # 定义声明
    claims = {
        'id': user_info['id'],
        'email': user_info['email'],
        'name': user_info['name'],
        'nickname': user_info.get('nickname', ''),
        'phone_number': user_info.get('mobile_phone', ''),
        'gender': user_info.get('gender', ''),
        'full_name': user_info.get('full_name', ''),
        'company_id': user_info.get('company_id', ''),
        'company_name': user_info.get('company_name', ''),
        'dept_id': user_info.get('dept_id', ''),
        'dept_name': user_info.get('dept_name', ''),
        'role_type': role_type,
        'ip': ip,
        'mac_addr': '',  # 无法获得Mac地址
        'channel': ''
    }

    # 定义token过期时间
    expiration = datetime.utcnow() + timedelta(days=JWT_EXPIRED_DAYS)

    # 创建JWT token
    to_encode = {
        **claims,
        'iss': JWT_ISSUER,
        'aud': JWT_AUDIENCE,
        'exp': expiration
    }

    token = jwt.encode(to_encode, JWT_SECRET_KEY, algorithm=ALGORITHM)
    return token
@app.post('/token')
async def get_token(request: Request):
    # 模拟的用户信息
    user_info = {
        'id': 123,
        'email': '[email protected]',
        'name': 'John Doe',
        'nickname': 'Johnny',
        'mobile_phone': '123-456-7890',
        'gender': 'Male',
        'full_name': 'Johnathan Doe',
        'company_id': 'ABC123',
        'company_name': 'ABC Corp',
        'dept_id': 'Dept001',
        'dept_name': 'IT',
        'ip': request.client.host
    }
    role_type = 'Admin'

    token = generate_token(user_info, role_type)
    
    headers = {
        'access-token': token,
        'Authorization': f'Bearer {token}'
    }

    return JSONResponse(content={'token': token}, headers=headers)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)

解释

  1. FastAPI: 用于构建快速、现代的 Web API。
  2. Request: 从 FastAPI 中导入,用于获取客户端的 IP 地址。
  3. JWT 配置: 使用常量配置 JWT 密钥、发行者、受众、加密算法和过期时间。
  4. generate_token 函数:
    • 构建 JWT 的 claims,包含用户信息和额外的字段,如 IP 地址、角色类型等。
    • 设置 exp 字段定义 JWT 的过期时间。
    • 使用 jwt.encode 创建并签名 JWT。
  5. get_token 路由:
    • 模拟从请求中获取用户信息(包括 IP 地址)。
    • 调用 generate_token 生成 JWT。
    • 将 JWT 放入响应头中,返回给客户端。

 

3、在api中如何实现AllowAnonymous和验证授权

在 Python 的 FastAPI 框架中,你可以通过以下方式实现类似于 ASP.NET 中的 AllowAnonymous 和授权验证功能。

1)实现 JWT 授权验证中间件

首先,你需要一个依赖项来检查请求中是否包含有效的 JWT 令牌。如果令牌无效或缺失,依赖项将拒绝请求。通过这种方式,只有标记为“允许匿名”的路由才会跳过验证。

2)安装依赖

确保安装了 python-jose 用于处理 JWT,以及 fastapi

3) 创建授权依赖

你可以创建一个名为 get_current_user 的依赖项,用于验证 JWT 令牌并提取用户信息。如果没有提供或验证失败,抛出 HTTPException

from fastapi import Depends, HTTPException, status
from jose import JWTError, jwt
from typing import Optional

JWT_SECRET_KEY = 'your_jwt_secret_key'
ALGORITHM = 'HS256'

def get_current_user(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[ALGORITHM])
        user_id: str = payload.get("id")
        if user_id is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Could not validate credentials",
                headers={"WWW-Authenticate": "Bearer"},
            )
        return payload
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )

get_current_user:用于解析和验证 JWT。如果令牌无效或缺失,会抛出 HTTPException,返回 401 状态码。

具体使用的时候,我们可能把用户信息缓存在Redis里面提高处理效率。

4)实现 AllowAnonymous 功能

FastAPI 中,你可以通过 Depends 来实现条件授权。对于需要授权的路由,只需将 get_current_user 作为依赖传递给路由函数。而无需授权的路由,可以直接定义。

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# 允许匿名访问的路由
@app.get("/public")
def read_public_data():
    return {"message": "This is a public endpoint"}

# 需要授权的路由
@app.get("/protected")
def read_protected_data(current_user: dict = Depends(get_current_user)):
    return {"message": f"Hello, {current_user['name']}"}

# 模拟登录生成 JWT 令牌的路由
@app.post("/token")
def login():
    # 这里省略了身份验证过程,只是直接生成一个 JWT 令牌
    token = jwt.encode({"id": 1, "name": "John Doe"}, JWT_SECRET_KEY, algorithm=ALGORITHM)
    return {"access_token": token, "token_type": "bearer"}
  • 公共路由 (/public):可以直接访问,不需要任何身份验证。
  • 受保护路由 (/protected):必须提供有效的 JWT 令牌才能访问。
  • 登录路由 (/token):生成一个 JWT 令牌,可以用于受保护的路

Depends(get_current_user):在需要保护的路由中,通过依赖项注入 get_current_user,确保只有通过身份验证的用户才能访问。

FastAPI 中,建议通过依赖项注入(Depends)来获取当前用户的信息,而不是直接访问 request.user。这种方式更加灵活并且与 FastAPI 的设计哲学更一致。

在具体项目中,我们为了方便,往往通过中间件的方式进行定义和处理授权的过程。

通过authentiate函数处理验证用户令牌的有效性。

 我们一般再main.py入口中加入中间件的处理即可。

    # JWT auth, required
    app.add_middleware(
        AuthenticationMiddleware,
        backend=JwtAuthMiddleware(),
        on_error=JwtAuthMiddleware.auth_exception_handler,
    )

 

4、一些错误处理

当你在解码 JWT 时遇到 "Invalid audience" 错误,通常意味着在生成或解码 JWT 时,aud (audience) 声明没有正确设置或验证。以下是解决这个问题的步骤和说明:

1) 理解 aud (Audience) 声明

  • aud 是 JWT 中的一个可选声明,通常用于指定 JWT 的接收者(受众)。在解码 JWT 时,jwt.decode 会检查这个声明是否与预期的值匹配。

设置和检查 aud 声明,生成 Token 时设置 aud

 

如果你希望 JWT 包含 aud 声明,可以在生成 token 时传递它。例如:

to_encode = {
    "sub": "user_id",
    "aud": "your_audience",  # 设置aud声明
    "exp": datetime.utcnow() + timedelta(minutes=30)
}

token = jwt.encode(to_encode, settings.TOKEN_SECRET_KEY, algorithm=settings.TOKEN_ALGORITHM)

解码 Token 时验证 aud

在解码 JWT 时,指定 audience 参数以匹配生成时的 aud

try:
    payload = jwt.decode(
        token,
        settings.TOKEN_SECRET_KEY,
        algorithms=[settings.TOKEN_ALGORITHM],
        audience="your_audience"  # 验证aud声明
    )
    print(f"Decoded payload: {payload}")
except JWTError as e:
    print(f"Token decode failed: {e}")

2)Token decode failed: Subject must be a string.

"Token decode failed: Subject must be a string" 错误通常是由于 sub (subject) 声明的值不是字符串引起的。sub 是 JWT 中常用的一个声明,用来标识 token 的主体,比如用户 ID 或用户名。JWT 标准要求 sub 的值必须是字符串。

检查 sub 声明的值

首先,确保在生成 token 时,sub 声明的值是一个字符串:

to_encode = {
    "sub": str(user_id),  # 确保 user_id 是字符串
    "aud": "your_audience",  # 其他字段
    "exp": datetime.utcnow() + timedelta(minutes=30)
}

token = jwt.encode(to_encode, settings.TOKEN_SECRET_KEY, algorithm=settings.TOKEN_ALGORITHM)

如果 sub 的值是一个非字符串类型(如整数或其他对象),请将其转换为字符串:

user_id = 123  # 假设 user_id 是一个整数
to_encode = {
    "sub": str(user_id),  # 将 user_id 转换为字符串
    "aud": "your_audience",
    "exp": datetime.utcnow() + timedelta(minutes=30)
}

token = jwt.encode(to_encode, settings.TOKEN_SECRET_KEY, algorithm=settings.TOKEN_ALGORITHM)

 

3)schema如何移除敏感字段

在处理数据模型和模式(schema)时,特别是当涉及到敏感信息(如密码、身份信息等)时,有时需要从输出或序列化结果中移除这些敏感字段。根据你使用的库或框架,处理敏感字段的方法会有所不同。以下是一些常见的方法来移除敏感字段:

使用 Pydantic 的 exclude 参数

如果你在使用 Pydantic(例如在 FastAPI 中),你可以使用模型的 dict 方法的 exclude 参数来排除敏感字段。

from pydantic import BaseModel

class User(BaseModel):
    username: str
    email: str
    password: str  # 敏感字段

user = User(username="user1", email="[email protected]", password="secret")

# 创建一个不包含敏感字段的字典
user_dict = user.dict(exclude={"password"})

print(user_dict)

使用 SQLAlchemy 的 __mapper_args__

如果你使用 SQLAlchemy 并且希望从序列化结果中排除敏感字段,可以使用模型的 __mapper_args__ 进行配置。例如:

from sqlalchemy import Column, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column(String, primary_key=True)
    username = Column(String)
    email = Column(String)
    password = Column(String)  # 敏感字段

    def to_dict(self):
        # 移除敏感字段
        return {c.name: getattr(self, c.name) for c in self.__table__.columns if c.name != 'password'}

user = User(id="1", username="user1", email="[email protected]", password="secret")
print(user.to_dict())

自定义序列化方法

如果你使用自定义模型或类,并且没有使用特定的库,你可以实现自定义序列化方法来排除敏感字段。

class User:
    def __init__(self, username, email, password):
        self.username = username
        self.email = email
        self.password = password  # 敏感字段

    def to_dict(self):
        # 移除敏感字段
        return {
            "username": self.username,
            "email": self.email
        }

user = User(username="user1", email="[email protected]", password="secret")
print(user.to_dict())

 

标签:jose,python,JWT,token,user,id
From: https://www.cnblogs.com/wuhuacong/p/18382179

相关文章

  • Python - Anti-Patterns
    Thesearecommonprogrammingpractices that,whilenotnecessarilywrong,oftenleadtolessefficient,lessreadable,andlessmaintainablecode. Byunderstandingthesepitfalls,youcanwritecleaner,moreefficientcodeforyourPythonapplications. ......
  • PEP 508:为不同版本Python指定不同依赖
    问题背景有些常用的第三方包,在Python中支持的版本是不一致的。例如常用的numpy,在Python3.7中仅支持到1.21版本,其他Python版本可以支持到1.22版本以上,甚至现在的2.0版本以上。有一个问题是,在有些Cython的使用场景中,numpy==1.22的版本有可能出现报错ImportError:numpy.core.multia......
  • 10 个令人惊叹的 Python 自动化脚本!
                                                                            /01/剪贴板管理器你是否曾发现自己忙于处理多个文本片段,而忘记了自己复制了什么?有没有想过有一个工具可以记录你一天中......
  • 3.Python操控Excel之写
    1.openpyxl读取excel的格式流程2.openpyxl修改sheet标题并生成新excel保存3..create_sheet方法添加sheet,.remove方法删除sheet4.excel数据填充的四种方式5.1.更新excel内一列数据中的部分数据_代码部分5.2.更新excel内一列数据中的部分数据_执行结果......
  • python如何通过Json路径返回Json响应对应的值例子解析
    在Python中,处理JSON数据通常使用标准库中的json模块来解析和操作JSON格式的数据。如果你想要通过JSON路径来获取JSON响应中的值,你可以使用第三方库,如jsonpath-ng,它允许你使用类似XPath的语法来查询JSON对象。首先,你需要安装jsonpath-ng库,可以使用pip来安装:pipinstalljs......
  • python aiohttp创建很多线程的问题及解决例子解析
    在使用aiohttp进行异步HTTP请求时,创建大量线程可能会导致性能问题。根据搜索结果,这个问题通常与DNS查询有关,因为默认情况下,每次发送请求时aiohttp.ClientSession都会进行DNS查询,这是一个阻塞操作,会为每次查询创建一个新线程。为了解决这个问题,可以通过指定一个AsyncR......
  • 零基础学习人工智能—Python—Pytorch学习(九)
    前言本文主要介绍卷积神经网络的使用的下半部分。另外,上篇文章增加了一点代码注释,主要是解释(w-f+2p)/s+1这个公式的使用。所以,要是这篇文章的代码看不太懂,可以翻一下上篇文章。代码实现之前,我们已经学习了概念,在结合我们以前学习的知识,我们可以直接阅读下面代码了。代码里使......
  • 【python计算机视觉编程——1.基本的图像操作和处理】
    python计算机视觉编程——1.基本的图像操作和处理1.基本的图像操作和处理1.1PIL:Python图像处理类库1.1.1转换图像格式1.1.2创建缩略图1.1.3复制和粘贴图像区域1.1.4调整尺寸和旋转1.2Matplotlib1.2.1绘制图像、点和线1.2.2图像轮廓和直方图1.3Numpy1.3.1图像......
  • 战争密码 Python实现二战德军恩格玛机(Enigma)
    战争密码Python实现二战德军恩格玛机(Enigma)恩格玛机原理介绍转轮类的实现恩格玛机类的实现加解密类的实现加密解密完整代码加密代码解密代码注意恩格玛机原理介绍恩格玛机,在密码学史中通常被称为恩尼格玛密码机(Enigma),是一种用于加密与解密文件的密码机。它最早......
  • python基础(10异常处理)
    python系列文章目录python基础(01变量&数据类型&运算符)python基础(02序列共性)python基础(03列表和元组)python基础(04字符串&字典)python基础(05集合set)python基础(06控制语句)python基础(07函数)python基础(08类和对象)python基础(09闭包&装饰器)文章目录python系列文章目......