云计算百科
云计算领域专业知识百科平台

你的APP要用户反复登录?密码传来传去?FastAPI+JWT实战,一个令牌全打通,安全与体验兼得,代码直接抄

文章目录

    • 你的APP要用户反复登录?密码传来传去?FastAPI+JWT实战,一个令牌全打通,安全与体验兼得,代码直接抄
      • 为什么我的 API 需要 JWT?
      • JWT 究竟是什么?它真的安全吗?
          • JWT 究竟是什么?
          • JWT 的三段式结构
          • JWT 的工作流程:一次登录,处处认证
      • 实战:基于 FastAPI 的 JWT 完整实现
          • 01、安装依赖与配置密钥
          • 02、核心方法——生成JWT令牌
          • 03、创建登录接口,返回令牌
          • 04、登录接口测试
          • 05、JWT 令牌验证,保护授权接口
      • JWT 带来的变革
      • 关于 FastAPI 的其他疑问

你的APP要用户反复登录?密码传来传去?FastAPI+JWT实战,一个令牌全打通,安全与体验兼得,代码直接抄

JWT123asasd

深夜,你又收到一条用户投诉:“我在你们App上刚买完东西,跳到客服页面为什么又要我登录一次?!”

你盯着屏幕,赶紧回复 “抱歉” 。

心里一阵苦笑:还不是因为那两个老系统互不认账,用户状态根本同步不了。更让你心虚的是,有些接口还有用基础认证,密码在请求里 “裸奔”……

这不是你一个人的问题。当应用拆分成多个服务,“如何让用户一路畅通,又能保证安全?” 成了系统架构中最磨人的痛点之一。

今天,我就带你用 FastAPI + JWT,打造一把“万能密钥”。从此,用户一次登录,一路通行。

为什么我的 API 需要 JWT?

试想一下,用户在你的App上刚登录完,当跳转到后台管理页面,又要输一次密码……。这种体验,用户会不会扭头就走?

这就是传统身份验证的痛点:

  • 状态难以跨系统维持;
  • 敏感信息(如密码)反复传输
  • 安全性与便利性难以兼得。
  • JWT 的救赎之道

    而 JWT 的出现,正是为了解决这一核心冲突。它就像一张数字通行证,用户只需登录一次,即可在多个关联服务间畅通无阻,系统既能识别用户身份,又无需保存会话状态。

    JWT 究竟是什么?它真的安全吗?

    参考:https://www.jwt.io/introduction

    JWT 究竟是什么?

    JWT(JSON Web Token)就是用户登录成功后,后端返回的 “加密电子身份证”—— 前端存起来,后续请求核心接口时带上,后端校验 token 有效就放行。

    JWT 的三段式结构

    先来看一个 JWT 令牌示例:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJpQV30

    这个 JWT 令牌看着像一串天书,它实际上由三部分组成,用点(.)分隔:

    {Header}.{Payload}.{Signature}

    Header(头部):指明令牌类型和签名算法。

    {
    "alg": "HS256",
    "typ": "JWT"
    }

    然后,这个 JSON 经Base64编码为 Base64Url,形成JWT的第一部分。

    Payload(载荷):包含声明数据。声明有三种类型:注册声明、公共声明、私有声明。

    注册声明:预定义字段,非强制但推荐。如iss(签发者)、exp(过期时间)

    公共声明:自定义字段,需避免冲突

    私有声明:双方约定的自定义字段

    示例:

    {
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
    }

    Signature(签名):签名是JWT的安全核心。

    使用 HMAC SHA256 算法,签名方式如下:

    # 伪代码演示签名过程
    signature = HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
    )

    签名确保令牌未被篡改,因为只有服务器拥有密钥,客户端无法伪造有效签名。

    JWT 的工作流程:一次登录,处处认证
  • 登录:用户提供用户名密码,服务器验证通过后,生成一个包含用户身份(如sub: "user_id")和过期时间的 JWT,将其返回给客户端(通常放在Authorization: Bearer <token>头中)。
  • 访问资源:客户端在后续请求中携带此 JWT。
  • 验证权限:服务器验证JWT签名和有效期,从Payload中提取用户信息,即可完成身份认证和授权,无需查询数据库会话。
  • 实战:基于 FastAPI 的 JWT 完整实现

    01、安装依赖与配置密钥

    安装依赖包

    pip install "python-jose[cryptography]" -i https://pypi.tuna.tsinghua.edu.cn/simple

    生成一个高强度的密钥(切勿使用简单字符串):

    (.venv) wangerge_notes: TodoApp$ openssl rand -hex 32
    eec7f9b96330e64eaac29ad7c95154cff3addaea90239c262b3f9287e678f6d3

    在配置文件中设置:

    # auth.py
    SECRET_KEY = "eec7f9b96330e64eaac29ad7c95154cff3addaea90239c262b3f9287e678f6d3"
    ALGORITHM = "HS256"

    02、核心方法——生成JWT令牌

    # auth.py

    def create_access_token(
    username: str, user_id: int, expires_delta: timedelta):
    # 载荷:仅存非敏感信息
    encode = {"sub": username, "id": user_id}
    # 过期时间(修正:时区正确,避免过期异常)
    expire = datetime.now(timezone.utc) + expires_delta
    encode.update({"exp": expire})
    # 编码生成token
    return jwt.encode(encode, SECRET_KEY, algorithm=ALGORITHM)

    03、创建登录接口,返回令牌

    # 定义响应模型(确保返回格式统一,避免验证错误)
    class Token(BaseModel):
    access_token: str
    token_type: str

    # 验证用户
    def authenticate_user(username: str, password: str, db):
    user = db.query(Users).filter(Users.username == username).first()
    if not user:
    return False
    if not bcrypt_context.verify(password, user.hashed_password):
    return False
    return user

    @router.post("/token", response_model=Token)
    async def login_for_access_token(
    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
    db: Annotated[Session, Depends(get_db)]
    ):
    # 校验用户名和密码
    user = authenticate_user(form_data.username, form_data.password, db)
    # 检验失败,抛出异常
    if not user:
    raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
    detail="Could not validate user.")
    # 生成token
    token = create_access_token(user.username, user.id, timedelta(minutes=20))
    return {"access_token": access_token, "token_type": "bearer"}

    04、登录接口测试

    切回浏览器 Swagger UI 页面,刷新页面,找到 POST /token 接口,给传入 username=wangerge, password=test1234。这个用户名和密码是数据库里面的。

    执行请求,返回响应码 200,响应体如下:

    {
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3YW5nZXJnZSIsImlkIjoxLCJleHAiOjE3NjgxMzI0NTJ9.eiUqiVl8GHQx_bG61UvAzvAuuODInk5Szz5yPT5zkZs",
    "token_type": "bearer"
    }

    此时,客户端传入的用户名和密码验证通过,返回加密 token。

    再次切回浏览器 Swagger UI 页面,刷新页面,找到 POST /token 接口,给传入 username=wangerge, password=test。(传入一个错误的密码)

    执行请求,返回响应码 401,响应体如下:

    {
    "detail": "Could not validate user."
    }

    此时,客户端传入的用户名和密码验证失败,返回异常信息。

    至此,当客户端通过浏览器给服务器传入一个正确的用户名和密码后,服务器能正确的生成 JWT 令牌信息,并返回给客户端。

    05、JWT 令牌验证,保护授权接口

    添加函数 get_current_user,解码并验证 JWT 令牌。

    from datetime import timedelta, datetime, timezone
    from typing import Annotated
    from fastapi import APIRouter, Depends, HTTPException
    from fastapi.security import OAuth2PasswordBearer
    from jose import jwt, JWTError

    auth2_bearer = OAuth2PasswordBearer(tokenUrl="token")

    async def get_current_user(
    token: Annotated[str, Depends(auth2_bearer)]):

    try:
    # 检查令牌的真实性。
    payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
    username: str = payload.get("sub")
    user_id: int = payload.get("id")
    if username is None or user_id is None:
    raise HTTPException(
    status_code=status.HTTP_401_UNAUTHORIZED,
    detail="Could not validate user.")
    return {"username": username, "id": user_id}

    except JWTError:
    raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
    detail="Could not validate user.")

    在上面代码中,我们解码 token ,得出用户名和用户 ID,并结构化返回。

    下面给授权接口添加用户验证。

    来到 todos.py 文件,导入 get_current_user 函数

    from .auth import get_current_user

    添加 user_dependency 依赖注入

    user_dependency = Annotated[dict, Depends(get_current_user)]

    来到 GET /todo/ 接口处,现在,要求这个接口必须要获取到用户登录信息后,才可以正常请求。

    给 read_all 方法,添加 user: user_dependency 参数,并添加 user 判断。代码如下:

    @router.get("/todo")
    async def read_all(user: user_dependency, db: db_dependency):
    if not user:
    raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
    detail="Failed Authentication")

    return db.query(models.Todos).all()

    切回浏览器 Swagger UI 页面,刷新页面,找到 GET /todo/ 接口,执行请求。

    返回响应码 401,响应体:"detail": "Not authenticated"

    这表示需要一个用户认证,才能执行 API 请求,这就是用户验证。

    我们找到 Swagger UI 页面 POST /todo 接口,在列表栏最右边,可以找到一个小锁的图标。如下图:

    image-20260113150121905

    点击打开它,提示我们需要输入用户名和密码。

    输入 username=“wangerge” password=“test1234” 点击 “Authorize” 按钮提交。

    认证成功,结果如下图:

    image-20260113150514048

    现在,用户已经登录成功了。重新执行 GET /todo/ 接口请求。

    服务器返回响应码 200。接口请求成功了。

    JWT 带来的变革

    通过以上实验,我们解决了开篇提到的痛点:

  • 单点登录(SSO)体验:用户登录一次,令牌在多服务间通用。
  • 无状态架构:服务器无需保存会话,轻松扩展,完美契合微服务。
  • 安全可控:基于签名的防篡改、可设置过期时间、Payload 携带基础信息。
  • 避坑指南:

  • 保管好你的SECRET_KEY:它是安全的基石。
  • 合理设置令牌过期时间:根据场景合理设置,平衡安全与体验。
  • 敏感数据不上令牌:JWT 的 Payload 是可解码的。
  • JWT 在 API 身份验证与授权的道路上,无疑是一把锋利而优雅的瑞士军刀。

    想要获取本章完整代码,请在评论区回复 【FastAPI】,代码直接复制就能跑。

    关于 FastAPI 的其他疑问

    FastAPI极速上手:从API到全栈,高薪后端必备技能

    10分钟用Python搭个接口,还能自动生成文档?

    3分钟搞定Python虚拟环境和FastAPI安装

    FastAPI实战:3步搞定增删改查,你的第一个完整API来了!

    别再堆if-else验参数了!FastAPI自带的参数验证器,至少省一半调试时间

    3分钟搞定FastAPI的数据库设置,把数据存储玩明白,复制代码就能用

    10分钟搞定FastAPI中“数据库连接管理、参数校验、文档维护”三大核心难题,让新手也能轻松地写出可落地的API

    “警惕!FastAPI接口一夜「消失」” 95%程序员靠这招自救:我的路由分离血泪史

    相关内容我都给大家做好了,感兴趣的朋友来「我的主页」找一找,直接就可以看到。

    关注我,每天分享「Python」、「职场」有趣干货,千万不要错过!

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 你的APP要用户反复登录?密码传来传去?FastAPI+JWT实战,一个令牌全打通,安全与体验兼得,代码直接抄
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!