PyJWT 和 python-jose 在处理JWT令牌处理的时候的差异和具体使用
PyJWT
和 python-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 令牌生成操作。如下所示代码。
使用 FastAPI
和 python-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': 'user@example.com',
'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)
解释
FastAPI
: 用于构建快速、现代的 Web API。Request
: 从FastAPI
中导入,用于获取客户端的 IP 地址。- JWT 配置: 使用常量配置 JWT 密钥、发行者、受众、加密算法和过期时间。
generate_token
函数:- 构建 JWT 的
claims
,包含用户信息和额外的字段,如 IP 地址、角色类型等。 - 设置
exp
字段定义 JWT 的过期时间。 - 使用
jwt.encode
创建并签名 JWT。
- 构建 JWT 的
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="user1@example.com", 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="user1@example.com", 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="user1@example.com", password="secret")
print(user.to_dict())
PyJWT 和 python-jose 在处理JWT令牌处理的时候的差异和具体使用的更多相关文章
- Spring Boot Security OAuth2 实现支持JWT令牌的授权服务器
概要 之前的两篇文章,讲述了Spring Security 结合 OAuth2 .JWT 的使用,这一节要求对 OAuth2.JWT 有了解,若不清楚,先移步到下面两篇提前了解下. Spring Bo ...
- 阶段5 3.微服务项目【学成在线】_day18 用户授权_03-方法授权-jwt令牌包含权限
修改认证服务的UserDetailServiceImpl类,下边的代码中 permissionList列表中存放了用户的权限, 并且将权限标识按照中间使用逗号分隔的语法组成一个字符串,最终提供给Spr ...
- 阶段5 3.微服务项目【学成在线】_day16 Spring Security Oauth2_13-SpringSecurityOauth2研究-JWT研究-生成JWT令牌&验证JWT令牌
生成jwt需要用私钥来签名.在Auth认证服务下创建测试类 创建密钥工厂,构造函数需要的参数 获取私钥 有了私钥就可以生成JWT令牌 使用jwtHelper是spring security里面的类 e ...
- 什么是JWT令牌认证?
当下,JWT(JSON Web Token)令牌认证已经变得越来越流行.本文主要介绍JWT令牌认证与传统的Session会话认证机制的区别. 为什么需要认证? HTTP是一种无状态协议,那就意味着当前 ...
- OAuth + Security - 3 - JWT令牌
为什么使用JWT令牌 在上面的资源服务器中,通过配置,我们了解到,当我们拿着token去获取资源时,程序会先去调用远程认证服务器的端点去验证解析token,或者在本地解析校验token,这样毫无疑问, ...
- 畅购商城(八):微服务网关和JWT令牌
好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 畅购商城(一):环境搭建 畅购商 ...
- Jwt令牌创建
添加依赖 <dependencies> <!-- jwt --> <dependency> <groupId>io.jsonwebtoken</g ...
- Spring Security OAuth2.0认证授权三:使用JWT令牌
Spring Security OAuth2.0系列文章: Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二: ...
- JWT令牌简介及demo
一.访问令牌的类型 二.JWT令牌 1.什么是JWT令牌 JWT是JSON Web Token的缩写,即JSON Web令牌,是一种自包含令牌. JWT的使用场景: 一种情况是webapi,类似之 ...
- OAuth2.0实战!使用JWT令牌认证!
大家好,我是不才陈某~ 这是<Spring Security 进阶>的第3篇文章,往期文章如下: 实战!Spring Boot Security+JWT前后端分离架构登录认证! 妹子始终没 ...
随机推荐
- .NET周刊【6月第4期 2024-06-23】
国内文章 C#.Net筑基-集合知识全解 https://www.cnblogs.com/anding/p/18229596 .Net中提供了数组.列表.字典等多种集合类型,分为泛型和非泛型集合.泛型 ...
- SNAT,DNAT以及REDIRECT转发详解
最近负责的其中一个项目的服务器集群出现了点网络方面的问题,在处理过程当中又涉及到了防火墙相关的知识和命令,想着有一段时间没有复习这部分内容了,于是借着此次机会复写了下顺便将本次复习的一些内容以博客的形 ...
- FileZilia FATAL ERROR: Network error: Software caused connection abort
使用FileZilia sftp传文件,对象服务器突然关闭,导致FileZilia传输中断. 等待对象服务器打开后,使用FileZilia想继续传输文件,结果一直显示: FATAL ERROR: Ne ...
- Java集合框架总结图
Collection 接口的接口(对象集合) ├---List 接口:元素都有索引,可以重复,有序(迭代器顺序). │------├ LinkedList 接口实现类, 双向链表, 查询慢,增删快,效 ...
- Maven pom.xml文件
pom.xml 版本依赖 <!--编译器依赖--> <properties> <project.build.sourceEncoding>UTF-8</pro ...
- P1754
球迷购票问题 题意描述 盛况空前的足球赛即将举行.球赛门票售票处排起了球迷购票长龙. 按售票处规定,每位购票者限购一张门票,且每张票售价为50元.在排成长龙的球迷中有N个人手持面值50元的钱币,另有N ...
- JavaScript高级~数组偏平化
方式一: let arr=[11,[22,[33,[44]]],[55,66,77],88,99,['00']] let arr2=arr.toString().split("," ...
- Python版WGCNA分析和蛋白质相互作用PPI分析教程
在前面的教程中,我们介绍了使用omicverse完成基本的RNA-seq的分析流程,在本节教程中,我们将介绍如何使用omicverse完成加权基因共表达网络分析WGCNA以及蛋白质相互作用PPI分析. ...
- Pulsar客户端消费模式揭秘:Go 语言实现 ZeroQueueConsumer
前段时间在 pulsar-client-go 社区里看到这么一个 issue: import "github.com/apache/pulsar-client-go/pulsar" ...
- Java---杂记
1. 当System.out.println()方法的参数是Object类型时,println()方法会自动调用Object对象的toString()方法,然后显示toString()方法返回的字符串 ...