Level 10 / 阅读练习

FastAPI 登录认证和 JWT 鉴权

理解登录、签发 token、校验当前用户、保护接口、管理员权限和退出策略。

Progress

阅读中

0/4 步完成 / 阅读后完成练习才算通关

Practice

本关怎么过

  • 读完正文后确认完成阅读
  • 完成 3 道选择题练习
  • 答错时看解释,再回到正文补理解

一句话:登录认证解决“你是谁”,权限鉴权解决“你能做什么”,JWT 是前端每次请求时带给后端看的临时通行证。

本篇学完你会什么:能理解注册、登录、签发 token、校验当前用户、管理员权限、退出登录这条完整门禁流程。

1. 登录鉴权解决什么问题

真实后端不能让所有人随便调用接口。

要回答四个问题:

谁在访问?
他登录了吗?
他是不是管理员?
他退出后 token 还能不能用?

JWT 登录鉴权流程

2. 密码为什么不能明文保存

不要这样存:

password = "123456"

应该存 hash:

hashed_password = "$2b$12$..."

大白话:hash 像把密码做成不可逆指纹。后端能验证你输得对不对,但不应该知道你的原始密码。

常用依赖:

pip install passlib[bcrypt] python-jose

3. JWT 是什么

JWT 是一段字符串,里面通常包含:

{
  "sub": "1",
  "exp": 1730000000
}
字段 意思
sub 用户 id
exp 过期时间

前端请求时带上:

Authorization: Bearer eyJxxx

注意:JWT 不是加密保险箱。很多 JWT 只是“签名后可验证”,内容本身可能被解码看到,所以不要把密码、手机号、身份证号、API key 这类敏感信息放进 token。token 里通常只放用户 id、过期时间、角色等必要信息。

4. 登录流程

用户输入用户名密码

后端查用户

校验密码 hash

生成 JWT

前端保存 token

以后请求都带 Authorization

token 到底存在哪里

这一步最容易被一句“前端保存 token”带过去,但真实项目要想清楚。

保存位置 大白话 适合场景 风险
内存变量 页面刷新就没了 临时演示、很短会话 刷新后要重新登录
localStorage 浏览器长期保存 学习项目、简单后台 XSS 时可能被脚本读走
sessionStorage 当前标签页保存 不希望关页后还登录 仍然可能被脚本读走
HttpOnly Cookie JS 读不到,由浏览器自动带上 更正式的 Web 后台 要同时考虑 CSRF 防护

初学项目可以先用 localStorage 理解流程,但要记住:正式系统不能只写“保存 token”四个字就结束。你至少要决定 token 放哪里、多久过期、退出登录怎么处理、前端请求时怎么统一带上。

5. 数据表设计

最小用户表:

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    username = Column(String(32), unique=True, index=True, nullable=False)
    hashed_password = Column(String(255), nullable=False)
    is_active = Column(Boolean, default=True)
    is_admin = Column(Boolean, default=False)

注意:字段叫 hashed_password,不是 password

6. 密码加密和校验

from passlib.context import CryptContext

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

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

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

大白话:

  • 注册时,把明文密码变成 hash 再存。
  • 登录时,把用户输入和数据库 hash 做比较。

7. 创建和解析 token

from datetime import datetime, timedelta
from jose import JWTError, jwt

JWT_SECRET = "change-me"
JWT_ALGORITHM = "HS256"

def create_access_token(user_id: int) -> str:
    payload = {
        "sub": str(user_id),
        "exp": datetime.utcnow() + timedelta(days=7),
    }
    return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)

def decode_access_token(token: str) -> dict:
    return jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])

生产环境里 JWT_SECRET 必须放 .env,不能写死。

8. get_current_user

get_current_user 是很多接口共用的门卫。

from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

security = HTTPBearer()

async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security),
):
    token = credentials.credentials
    try:
        payload = decode_access_token(token)
    except JWTError:
        raise HTTPException(status_code=401, detail="登录已失效")

    user_id = int(payload["sub"])
    user = await get_user_by_id(user_id)
    if not user or not user.is_active:
        raise HTTPException(status_code=401, detail="用户不可用")
    return user

接口使用:

@router.get("/me")
async def me(current_user = Depends(get_current_user)):
    return current_user

9. require_admin

管理员接口要再加一层权限判断。

async def require_admin(current_user = Depends(get_current_user)):
    if not current_user.is_admin:
        raise HTTPException(status_code=403, detail="需要管理员权限")
    return current_user

使用:

@router.delete("/users/{user_id}")
async def delete_user(user_id: int, admin = Depends(require_admin)):
    ...

10. 退出登录和 token 失效

JWT 的特点是:签发后,在过期前默认都有效。

如果要实现“退出后立刻失效”,常见做法:

  • token 设置短过期时间
  • refresh token 单独管理
  • Redis 记录 token 黑名单

可以按项目复杂度分三档:

项目阶段 推荐做法 说明
学习 Demo 前端删除本地 token,access token 过期时间短一些 简单,但服务端不知道你已经退出
普通后台 access token 短有效期 + refresh token 兼顾体验和安全
安全要求更高 Redis 黑名单或 token 版本号 退出、改密码、封禁后可立即失效

所以“退出登录”不只是前端删 token。前端删除后,用户这台浏览器不会再带 token;但如果旧 token 被别人拿到,只要它还没过期,服务端默认仍可能认它。正式后台系统再接 Redis 黑名单或 token 版本控制。

11. 常见错误

问题 原因 解决
登录成功但访问接口 401 前端没带 Authorization 检查请求头
token 永不过期 没设置 exp 创建 token 时加过期时间
所有人都能访问管理员接口 没加 require_admin 管理接口加依赖
密码泄露风险 明文存密码 只存 hash
改了 JWT_SECRET 后全失效 签名密钥变了 这是正常现象

12. 检查清单

[ ] 密码只存 hash
[ ] 登录失败不暴露过多细节
[ ] token 有过期时间
[ ] JWT_SECRET 来自环境变量
[ ] 需要登录的接口使用 get_current_user
[ ] 管理员接口使用 require_admin
[ ] 401 和 403 区分清楚
[ ] 退出登录策略明确

总结表

名词 大白话
认证 确认你是谁
鉴权 判断你能做什么
JWT 临时通行证
hash 密码指纹
Authorization 放 token 的请求头
get_current_user 当前用户门卫
require_admin 管理员门卫
401 没登录
403 没权限

下一篇建议:大白话讲解——Redis 缓存和限流

Checkpoint

读完后做题闯关

读完后做选择题检验理解,答错会显示解释。

0/3
先完成阅读

读完正文后点击“我已读完,进入练习”,题目会变成可答状态。

第 1 题

登录鉴权文章里,最应该追踪哪条线?

第 2 题

缓存学习时,最关键的风险是什么?

第 3 题

JWT 这类 token 更接近什么?