
当使用 Pydantic 模型 TokenData 定义 id: Optional[str] 时,若 JWT payload 中的 "user_id" 是整数(如 14),直接赋值会触发 ValidationError: input should be a valid string。根本原因是 Pydantic 严格校验类型,需显式转换为字符串。
当使用 pydantic 模型 `tokendata` 定义 `id: optional[str]` 时,若 jwt payload 中的 `"user_id"` 是整数(如 `14`),直接赋值会触发 `validationerror: input should be a valid string`。根本原因是 pydantic 严格校验类型,需显式转换为字符串。
在 FastAPI + JWT 的认证流程中,verify_access_token 函数负责解析并校验令牌,然后将其中的用户标识(如 user_id)构造成 TokenData 实例。虽然你在 schemas.py 中已正确定义:
class TokenData(BaseModel):
id: Optional[str]
但问题出在 oauth2.py 的 verify_access_token 函数中——它从 JWT payload 提取的 payload.get("user_id") 实际可能是一个整数(例如数据库主键 14),而 Pydantic 在实例化 TokenData(id=id) 时不会自动类型转换,而是严格按 str 校验,从而抛出:
pydantic_core._pydantic_core.ValidationError: 1 validation error for TokenData id Input should be a valid string [type=string_type, input_value=14, input_type=int]
✅ 正确做法是:在构造 TokenData 前,强制将 id 转为字符串。修改 verify_access_token 如下:
def verify_access_token(token: str, credentials_exception):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id = payload.get("user_id")
if user_id is None:
raise credentials_exception
# ✅ 关键修复:确保传入 TokenData 的 id 是字符串
token_data = schemas.TokenData(id=str(user_id))
except JWTError:
raise credentials_exception
return token_data
⚠️ 注意事项:
- 不要仅依赖类型注解(如 id: str = payload.get("user_id")),Pydantic 构造时仍会校验原始值;
- 若 user_id 可能为 None,str(None) 会变成 "None",因此务必先判空(如上所示);
- 更健壮的做法可结合 field(default_factory=str) 或自定义验证器,但对本场景,str(user_id) 已足够安全且简洁;
- 确保生成 token 时也保持一致:create_access_token(data={"user_id": str(user.id)})(推荐),或至少接受 int 并在验证端统一转 str。
最后,在路由中使用 user_id: int = Depends(oauth2.get_current_user) 时,请注意:当前 get_current_user 返回的是 TokenData 对象,其 id 属性是 str 类型。若后续需要整型 ID(如查询数据库),应在视图函数内显式转换:
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=schemas.Post)
def create_post(
post: schemas.PostCreate,
db: Session = Depends(get_db),
current_user: schemas.TokenData = Depends(oauth2.get_current_user) # ← 接收 TokenData 对象
):
user_id = int(current_user.id) # ✅ 显式转 int(确保安全前提下)
print(f"Authenticated user ID (int): {user_id}")
new_post = models.Post(owner_id=user_id, **post.model_dump())
db.add(new_post)
db.commit()
db.refresh(new_post)
return new_post
这样既保证了 Pydantic 校验通过,又维持了业务逻辑对整型 ID 的需求,实现类型安全与工程实用性的平衡。










