You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI中JWT验证失败:上传PDF时无法验证GET生成的Token

问题分析与解决方案

核心验证失败原因

你的generate_token函数中设置了nbf(Token生效时间)为当前时间+1分钟:

not_before = issued_at + timedelta(minutes=1)

这意味着生成的Token在1分钟后才会生效,所以刚拿到Token就调用/upload接口时,Token还未到生效时间,会被jwt.decode判定为无效,触发验证失败。

修复步骤

  1. 调整Token生效规则:移除nbf字段(或设置为当前时间),让Token生成后立即可用;若需要有效期,可添加exp字段设置过期时间。
  2. 精准捕获异常:捕获jose库的具体异常类型,避免宽泛的except导致无法定位问题。
  3. 规范异常抛出:验证失败时直接抛出HTTPException,保证响应状态码符合REST规范。
  4. 完善文件操作:确保上传目录存在,避免因目录缺失导致文件写入失败;优化文件读取和转换的异常处理。
  5. 清除无用变量:删除未实际使用的全局变量file_name。

修改后的完整代码

from fastapi import FastAPI, Depends, HTTPException, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi import File, UploadFile
from pdf2docx import Converter
import uvicorn
import uuid
from datetime import datetime, timedelta
from jose import jwt, JWTError, ExpiredSignatureError
import os

app = FastAPI()
security = HTTPBearer()
SECRET_KEY = "Adnan123"
ALGORITHM = "HS256"
# 确保上传目录存在,不存在则自动创建
UPLOAD_DIR = "./upload"
os.makedirs(UPLOAD_DIR, exist_ok=True)

def generate_token() -> str:
    issued_at = datetime.utcnow()
    payload = {
        "authorized": True,
        "jti": str(uuid.uuid4()),  # 生成唯一Token ID
        "iat": issued_at,
        "exp": issued_at + timedelta(hours=1)  # 设置Token1小时后过期
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
    return token

def verify_token(token: str) -> dict:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token已过期")
    except JWTError:
        raise HTTPException(status_code=401, detail="无效或格式错误的Token")

@app.post("/upload")
async def upload_pdf(
    file: UploadFile = File(...),
    auth: HTTPAuthorizationCredentials = Depends(security)
):
    # 验证Token,失败直接抛出异常
    verify_token(auth.credentials)
    
    # 校验文件类型
    if file.content_type != 'application/pdf':
        raise HTTPException(status_code=400, detail="仅支持PDF格式文件")
    
    # 构造文件路径
    pdf_path = os.path.join(UPLOAD_DIR, file.filename)
    docx_path = os.path.splitext(pdf_path)[0] + ".docx"
    
    # 保存上传的PDF文件
    with open(pdf_path, 'wb') as f:
        f.write(await file.read())
    
    # 执行PDF转DOCX
    try:
        cv = Converter(pdf_path)
        cv.convert(docx_path)
        cv.close()
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"转换失败:{str(e)}")
    
    return {"message": "转换成功", "docx文件名": os.path.basename(docx_path)}

@app.get("/token")
async def get_token(request: Request):
    token = generate_token()
    return {"token": token}

if __name__ == '__main__':
    uvicorn.run('main:app', host='127.0.0.1', port=8000, reload=True)

关于Local Storage的疑问

  • 若通过前端页面调用API:将Token存储到Local Storage是合理的,用户获取一次Token后,后续上传请求可直接从Local Storage读取并携带到请求头,无需重复调用/token接口。
  • 若通过**命令行工具(如curl)**或后端服务调用:无需存储到Local Storage,只需在上传前调用/token获取Token,然后在请求头中添加Authorization: Bearer <你的Token>即可。

内容的提问来源于stack exchange,提问作者Adnan Masruf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:53:13