写接口时,总怕自己的接口被随便访问?想给接口加上登录验证,却不知道怎么实现?
别慌!今天我们就用一张流程图 + FastAPI 代码,把JWT认证机制讲得明明白白,新手也能直接上手。


一、先看整体流程:一张图看懂JWT认证

先上我们整个认证流程的完整示意图,先有个整体印象👇

在这里插入图片描述

这张图把整个认证流程分成了三大核心模块,我们先搞懂每个角色的作用:

模块/角色 作用说明
用户端 前端/APP,负责发送登录请求、存储token、后续请求携带token
中间件 所有请求的「守门人」,校验token格式、签名,提取用户信息
认证系统 「物业中心」,负责生成token、校验token有效性、管理用户会话
其他应用系统 业务接口(如购物车、订单接口),只有校验通过才能访问

整个流程的核心逻辑可以用一句话概括:
登录拿token → 请求带token → 中间件验token → 认证系统确认 → 放行到业务接口


二、跟着流程图走一遍完整流程

我们跟着图里的编号,一步步拆解每个环节的作用,你会发现整个逻辑其实和“办通行证、用通行证进小区”一模一样:

1. 步骤1:用户登录(拿通行证的申请)

用户在前端输入账号密码,发送登录请求到认证系统

!!!注意:这一步不需要带token,因为你还没登录,没有通行证,所以登录请求会直接绕过中间件的校验逻辑。

2. 步骤2:认证系统生成token(给你发通行证)

认证系统验证账号密码正确后,会给用户生成一个JWT token,这就是你的“临时通行证”:

  • 通行证上会写清你的身份信息(比如用户ID、角色)
  • 自带防伪水印(签名),别人不能随便篡改
  • 有有效期,过期就失效

3. 步骤3:前端拿到token(把通行证揣兜里)

认证系统把生成的token通过response返回给用户端,前端一般会把token存在localStorage或者cookie里,方便后续请求使用。

4. 步骤4:后续请求携带token(每次进小区都要出示通行证)

用户访问业务接口时,前端会把token放在请求头的Authorization字段里,格式为:

Authorization: Bearer <你的JWT Token字符串>

这是OAuth2协议的标准格式,Bearer表示这是一个“持有者令牌”,服务器会按规则解析。

5. 步骤5:中间件校验token(保安查通行证)

请求到达中间件后,会完成三件核心校验:

  1. 格式校验:检查token是不是Bearer xxx的标准格式
  2. 签名校验:验证token有没有被篡改(防伪水印是否有效)
  3. 信息提取:从token中解析出用户ID、角色等信息

如果token无效/过期/被篡改,中间件会直接返回401 Unauthorized,拒绝访问;如果校验通过,中间件会向认证系统确认token的有效性,确认无误后,才会放行到业务系统。


三、用FastAPI实现流程图里的逻辑

1. 安装依赖

先安装JWT校验和密码哈希需要的工具:

pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt]
  • python-jose:用于生成和校验JWT token
  • passlib:用于哈希存储用户密码(永远不要明文存密码!)

2. 基础配置与工具函数

先定义全局配置和工具函数,比如密钥、加密算法、密码哈希工具:

from fastapi import FastAPI, Depends, HTTPException, status, Header
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
from typing import Optional

# 基础配置
SECRET_KEY = "你的密钥字符串(生产环境要换成复杂的随机字符串)"
ALGORITHM = "HS256"  # JWT签名算法
ACCESS_TOKEN_EXPIRE_MINUTES = 30  # token过期时间,30分钟

# 密码哈希工具
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# 模拟数据库里的用户数据
fake_users_db = {
    "zhangsan": {
        "username": "zhangsan",
        "full_name": "张三",
        "email": "zhangsan@example.com",
        "hashed_password": pwd_context.hash("123456"),  # 密码哈希存储
        "disabled": False,
    }
}

# 验证密码
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

# 生成JWT token
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

app = FastAPI()

3. 实现登录接口(对应流程图步骤1-3)

登录接口负责接收账号密码,验证用户身份,生成并返回token:

@app.post("/login")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    # 1. 从模拟数据库中查询用户
    user = fake_users_db.get(form_data.username)
    if not user:
        raise HTTPException(status_code=400, detail="用户名不存在")
    
    # 2. 验证密码是否正确
    if not verify_password(form_data.password, user["hashed_password"]):
        raise HTTPException(status_code=400, detail="密码错误")
    
    # 3. 生成token,设置过期时间
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user["username"]}, expires_delta=access_token_expires
    )
    
    # 4. 返回token给前端,和流程图里步骤3对应
    return {"access_token": access_token, "token_type": "bearer"}

4. 实现token校验依赖(对应流程图中间件的作用)

这里我们用FastAPI的Depends依赖注入,实现和流程图里中间件一样的校验逻辑:

# 依赖项:从请求头提取并校验token
def get_token(authorization: str = Header(...)):
    # 1. 格式校验:必须以Bearer开头
    if not authorization.startswith("Bearer "):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token format"
        )
    # 2. 剥离Bearer前缀,拿到纯token
    token = authorization[7:]
    return token

# 依赖项:校验token有效性,解析用户信息
def verify_token(token: str = Depends(get_token)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
    )
    try:
        # 1. 校验token签名和过期时间,解析payload
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        return username
    except JWTError:
        raise credentials_exception

5. 实现受保护的业务接口(对应流程图里的其他应用系统)

只有token校验通过的用户,才能访问这些接口:

# 受保护的接口:获取用户信息
@app.get("/users/me")
def read_users_me(username: str = Depends(verify_token)):
    user = fake_users_db.get(username)
    if not user:
        raise HTTPException(status_code=404, detail="用户不存在")
    return {
        "username": user["username"],
        "full_name": user["full_name"],
        "email": user["email"]
    }

# 受保护的接口:模拟购物车接口
@app.get("/cart")
def get_cart(username: str = Depends(verify_token)):
    return {"username": username, "cart_items": ["商品1", "商品2"]}

四、为什么JWT能保证安全?

JWT之所以能防篡改、防伪造,核心在于它的结构和签名机制:
JWT由三部分组成,用.分隔:header.payload.signature

  1. header:记录加密算法(如HS256),是明文的
  2. payload:存储用户信息(如user_idexp过期时间),也是明文的,所以不要存密码等敏感信息!
  3. signature:用我们的SECRET_KEYheaderpayload加密生成的签名,相当于通行证上的防伪水印。

如果有人篡改了payload里的用户信息,signature就会失效,中间件就能直接检测到token被篡改,拒绝访问。


五、流程图里的设计思想:为什么要把中间件和认证系统分开?

这其实是微服务架构中非常常见的解耦设计:

  • 中间件:负责「网关层」的通用校验,比如格式校验、签名解析,所有业务系统都可以共用,不用每个服务都写一遍校验逻辑
  • 认证系统:负责「服务层」的核心管理,比如token生成、有效性校验、会话管理(如token黑名单、用户权限管理)

这样即使你有多个业务系统(购物车、订单、用户中心),都可以共用同一个中间件和认证系统,不用重复开发,维护起来也更方便。


六、新手常见避坑指南

  1. 不要把敏感信息存在JWT的payload里!比如密码、银行卡号,payload是明文的,任何人拿到token都能解码看到。
  2. 一定要设置token过期时间!不然token永久有效,泄露了就会有安全风险。
  3. 密码一定要哈希存储!永远不要明文存用户密码,用passlib生成哈希值,验证时只比对哈希值即可。
  4. 前端一定要走HTTPS!不然token会被中间人劫持,等于通行证被偷了。

流程总结

用户登录拿token → 后续请求带token → 中间件验token格式和签名 → 认证系统确认有效性 → 放行到业务接口

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐