FastAPI 系列
鉴权怎么接到 Depends 上(当前篇)
鉴权不要散落在每个路由里拆 Header。FastAPI 的做法是:OAuth2PasswordBearer 取出 Bearer Token,get_current_user 校验并返回用户,需要登录的路径只声明这个依赖。下面只讲防御侧实现:密码哈希、短时 JWT、失败时的 401。

密码怎么存
官方示例现在用 pwdlib 做哈希,不要自己写 SHA256 加盐。校验失败时仍应对「不存在的用户」走一遍哈希,避免用响应时间暴露账号是否存在。
from pwdlib import PasswordHash
password_hash = PasswordHash.recommended()
def verify_password(plain: str, hashed: str) -> bool:
return password_hash.verify(plain, hashed)
签发短时 JWT
from datetime import datetime, timedelta, timezone
import jwt
SECRET_KEY = settings.secret_key # 来自环境变量
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
def create_access_token(subject: str) -> str:
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
return jwt.encode({"sub": subject, "exp": expire}, SECRET_KEY, algorithm=ALGORITHM)
密钥用 openssl rand -hex 32 生成,放环境变量。过期时间写进 exp,校验时用库函数解码,不要自己解析 payload 字符串。
登录与受保护路由
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt.exceptions import InvalidTokenError
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
return {"access_token": create_access_token(user.username), "token_type": "bearer"}
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username = payload.get("sub")
if username is None:
raise credentials_exception
except InvalidTokenError:
raise credentials_exception
user = get_user(username)
if user is None:
raise credentials_exception
return user
@app.get("/users/me")
async def read_me(current_user: Annotated[User, Depends(get_current_user)]):
return current_user
tokenUrl="token" 会让 Swagger UI 出现授权按钮,指向同一个登录接口。权限组合继续套依赖:get_current_active_user 检查 disabled,管理员再包一层。
常见误区
把 JWT 当会话存储
Access Token 应短时。需要吊销时,用服务端会话或黑名单,不要发一周有效的 HS256 Token 还不做刷新策略。
密钥写进仓库
示例里的 hex 字符串只能出现在文档,不能出现在真实部署。
在中间件里自己解析 Authorization
除非做网关。应用内用 OAuth2PasswordBearer,OpenAPI 才会正确标记安全方案。
小结
登录换短时 Token,受保护路由只依赖 get_current_user。密码哈希、过期、401 头缺一不可。下一篇分清中间件、CORS 和后台任务,避免把鉴权塞进错误的一层。
参考资料
鉴权怎么接到 Depends 上:OAuth2 Password 与 Bearer JWT
https://lautung.com/archives/fastapi-09-auth
评论