Skip to content

鉴权与安全

OAuth2 密码流、JWT、密码哈希、安全依赖组合。

Updated View as Markdown
For humans

鉴权与安全

鉴权的标准组合拳:OAuth2 密码流拿令牌 + JWT 无状态校验 + 密码哈希存储。面试主线:令牌怎么发、怎么验、怎么保护密码。

整体流程

OAuth2 密码流 + JWT 无状态鉴权

  • 登录接口签发 JWT,客户端后续请求带 Authorization: Bearer <token>
  • 服务端无状态校验:验签名、验过期、读负载,不需要查会话表
  • JWT 三部分:header(算法)、payload(声明,含 exp)、signature(签名)

OAuth2PasswordBearer

from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")   # 声明令牌获取端点

@app.post("/token")
def login(form: OAuth2PasswordRequestForm = Depends()):
    user = authenticate(form.username, form.password)
    if not user:
        raise HTTPException(401, "用户名或密码错误")
    token = create_jwt({"sub": user.id})
    return {"access_token": token, "token_type": "bearer"}
  • OAuth2PasswordBearer:从请求头提取 Bearer 令牌,缺失返回 401
  • OAuth2PasswordRequestForm:解析表单用户名密码
  • 令牌获取端点返回标准 OAuth2 响应格式,OpenAPI 文档自动生成“Authorize”按钮

JWT 签发与校验

import jwt  # PyJWT 或 python-jose

SECRET_KEY = "..."   # 从环境变量读, 别写代码里

def create_jwt(user_id: int) -> str:
    return jwt.encode(
        {"sub": str(user_id), "exp": datetime.now(timezone.utc) + timedelta(minutes=30)},
        SECRET_KEY, algorithm="HS256")

def verify_jwt(token: str) -> int:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        return payload["sub"]
    except jwt.ExpiredSignatureError:
        raise HTTPException(401, "令牌过期")
    except jwt.InvalidTokenError:
        raise HTTPException(401, "无效令牌")

要点:过期时间放 exp(标准声明);algorithms 显式指定防算法混淆;SECRET_KEY 从环境变量读。生产可加 refresh_token 轮换(access 短、refresh 长),或用 RS256 非对称签名。

密码哈希

from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(pw: str) -> str:
    return pwd_context.hash(pw)

def verify_password(pw: str, hashed: str) -> bool:
    return pwd_context.verify(pw, hashed)
  • 绝不存明文、绝不自己写哈希。bcrypt 内置盐(salt),抗彩虹表
  • 校验用 verify,比较由库处理(恒定时间比较防时序攻击)
  • 密码哈希和 JWT 签名密钥是两回事,分开管理

安全依赖组合

def get_current_user(token: str = Depends(oauth2_scheme),
                     db: Session = Depends(get_db)) -> User:
    user_id = verify_jwt(token)
    user = db.get(User, user_id)
    if not user:
        raise HTTPException(401, "用户不存在")
    return user

@app.get("/me")
def me(user: User = Depends(get_current_user)):    # 需要登录的接口
    return user
  • 受保护接口声明 Depends(get_current_user),自动完成“取令牌、验签、查用户”
  • 角色权限:再包一层 require_role("admin") 依赖
  • 复用依赖树,鉴权逻辑写在依赖里,路由保持干净

面试追问

  1. JWT 怎么校验? 验签名(密钥)、验 exp、读负载。无状态,不需要服务端会话
  2. 密码怎么存? bcrypt 加盐哈希。passlib 处理,绝不存明文
  3. OAuth2PasswordBearer 干什么? 从 Authorization 头提取 Bearer 令牌,缺失返回 401。tokenUrl 关联文档
  4. access token 过期了怎么办? refresh token 换新;或让用户重新登录。无状态方案里撤销只能等过期或黑名单
  5. JWT 放哪安全? 前端存内存/HttpOnly Cookie(防 XSS 窃取)。localStorage 有 XSS 风险
Navigation

Type to search…

↑↓ navigate↵ selectEsc close