PAYATHON 2026

FastAPI-Login 的 optional 为何总返回 None?

数据小许

结论

manager.optional(request) 返回的是 user_loader 根据 token 解析出的用户对象,不是 JWT payload 字典。因此,不能把它当作包含 sub 的 payload 来用。你提供的代码没有展示 user_loader。如果没有注册用户加载器,或者它无法根据 token 中的 sub 找到用户,optional 通常会返回 None。

登录逻辑也要相应调整:收到用户对象后,直接判断对象是否存在,不要调用 user.get("sub")。

配置用户加载器

create_access_token(data={"sub": user.email}) 会把邮箱写入 token 的 sub。注册用户加载器,用这个邮箱查询并返回用户。下面的 SessionLocal 需要替换为项目实际使用的 SQLAlchemy session factory:

@manager.user_loader()
def load_user(email: str):
    db = SessionLocal()
    try:
        return db.query(User).filter(User.email == email).first()
    finally:
        db.close()

如果用户加载器已在其他模块注册,请检查它能否收到邮箱、查到对应用户并返回用户对象。只有在业务接口确实需要 payload 时,才返回 token payload。

调整登录接口

@router.post("/login", status_code=status.HTTP_200_OK)
async def login(request: Request, response: Response, db: Session = Depends(get_db)):
    current_user = await manager.optional(request)

    if current_user is not None:
        return {"message": "User already logged in"}

    try:
        body = await request.json()
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid request body")

    email = body.get("email", "").strip().lower()
    password = body.get("password", "")

    user = db.query(User).filter(User.email == email).first()
    if not user or not pwd.verify(password, user.password):
        raise HTTPException(status_code=401, detail="Invalid email or password")

    access_token = manager.create_access_token(
        data={"sub": user.email},
        expires=timedelta(hours=12),
    )
    manager.set_cookie(response, access_token)

    return {"message": "Login successful"}

optional 用于可选认证:有效 token 能解析到用户时,它会返回用户;没有可用用户时则返回 None,登录接口随后可以校验邮箱和密码。需要登录的受保护路由可以使用强制认证依赖,例如 Depends(manager)。

你的配置启用了 use_cookie=True。请确认浏览器向 /auth/login 发送了 manager 配置中使用的名称对应的 cookie。检查请求的 Cookie 头和其中的 token 值,并确认域名、路径和 HTTPS 设置允许浏览器发送该 cookie。如果客户端保存了 token,但请求没有携带 cookie,服务器仍会将请求视为没有 token。

排查时,可以暂时记录 request.cookies 中是否存在对应 cookie,以及用户加载器是否查到用户。当前宽泛的 except Exception: pass 会隐藏解析或数据库异常。调试期间应记录异常,以免把配置错误和无效 token 混为一谈。

备注:内容仅供参考。