Skip to content

认证 ​

认证体系总览 ​

认证体系的职责是"识别当前是谁在访问系统",并向请求链路安全地传递操作人上下文。它建立在 app/framework/security/ 之上,为权限、数据范围等提供"当前用户"这一基础。

系统采用 JWT + Redis 会话 的混合模式:

  • JWT(access_token / refresh_token):无状态令牌,携带 session_id(即 sub)用于定位会话,由 SECRET_KEY 签名,可独立校验签名与过期时间。
  • Redis 会话(USER_SESSION / ACCESS_TOKEN / REFRESH_TOKEN):有状态支撑,保存完整的在线用户信息,支撑在线用户列表、强制下线、滑动续期与令牌吊销。

二者的关系可以用一张图说明:

核心代码分布在四个文件:

文件职责
framework/security/jwt.pyJWT 的签发与解码校验
framework/security/oauth2.pyOAuth2 Bearer 提取器、登录表单模型
framework/security/schema.pyJWT 载荷、响应、刷新、退出等数据模型
framework/security/auth.py会话解析、get_current_user 依赖、AuthSchema 上下文

JWT 令牌 ​

jwt.py 封装了两个函数,分别负责签发与验证:

  • create_access_token(payload):将 JWTPayloadSchema 转为字典,把 datetime 类型的 exp 转为时间戳,使用 settings.SECRET_KEY 与 settings.ALGORITHM 签名。
  • decode_access_token(token):校验签名与过期时间,并对各类异常返回语义化提示(无效凭证、已过期、已失效等),统一抛出 401 的 CustomException。

JWTPayloadSchema 的关键字段:

字段含义
sub会话编号 session_id,用于关联 Redis 中的会话信息
is_refresh是否为刷新令牌,认证时禁止用 refresh_token 当 access_token 使用
exp过期时间

注意:access_token 与 refresh_token 复用同一套载荷结构,仅靠 is_refresh 区分用途。刷新接口会显式校验该字段,防止把刷新令牌当访问令牌用。

OAuth2 提取器 ​

oauth2.py 在 FastAPI 原生 OAuth2PasswordBearer 之上做了定制:

  • CustomOAuth2PasswordBearer:从 Authorization 头解析 scheme 与 token,并校验前缀与 settings.TOKEN_TYPE(默认 Bearer)一致,否则抛出统一的 401 异常。
  • CustomOAuth2PasswordRequestForm:在标准用户名/密码字段之外,扩展出 captcha_key、captcha、login_type(PC端 | 移动端),供登录接口直接使用表单提交(主要用于 Swagger 的 Authorize 按钮)。
  • OAuth2Schema:全局可用的依赖实例,token_url 指向 system/auth/login-form。

会话与在线状态 ​

登录成功后,LoginService.create_token 会把会话信息写入 Redis,键结构如下:

Redis 键内容过期
USER_SESSION:{session_id}完整在线用户信息(JSON)跟随 refresh_token
ACCESS_TOKEN:{session_id}access_token 值access_token 有效期
REFRESH_TOKEN:{session_id}refresh_token 值refresh_token 有效期

这套结构的价值:

  • 在线用户:USER_SESSION 即在线用户记录,可供管理端"在线用户"模块展示与强制踢出。
  • 强制下线:删除对应用户的 Redis 键后,旧令牌在下次请求校验时即被判定为"认证已失效"。
  • 令牌吊销:logout 正是删除这三个键。

核心认证依赖 ​

auth.py 是整个认证能力的入口,关键对象如下。

AuthSchema ​

AuthSchema 是请求级的认证上下文模型,通过依赖注入链传递:

字段含义
db当前请求的数据库会话
user当前登录用户(已加载部门、角色、岗位等关联)
check_data_scope是否启用数据权限(部门数据范围)检查,默认开启
session_id会话编号
data_permission数据权限解析结果缓存,请求级复用,避免重复查部门表

get_current_user ​

get_current_user 是 HTTP 路由最常用的依赖,整合 db_getter、redis_getter 与 OAuth2Schema,最终调用内部 _authenticate 完成认证,返回 AuthSchema。

其认证流程:

要点说明:

  • 中间件协同:framework/web/middleware.py 的 RequestLogMiddleware 会预先解析 Authorization 头并填充 request.state.ctx。_authenticate 优先复用该上下文,避免重复解码。
  • 在线校验:_check_token_online 检查 ACCESS_TOKEN:{session_id} 是否存在,防止被强制下线后旧 token 仍可用。
  • 滑动续期:当 TOKEN_SLIDING_EXPIRE=True 且 token 剩余存活小于有效期一半时,自动延长 access/refresh 的 Redis TTL,实现"活跃会话自动续期"。
  • 用户校验:_load_user_from_db 会联表加载部门、角色(含菜单)、岗位,并过滤被停用的角色与岗位;用户不存在或 status==1(停用)都会抛出 401。

WebSocket 场景使用 get_current_user_ws,差异在于 token 通过查询参数传入(WebSocket 没有请求头),并通过 redis_getter_ws 获取客户端。

在路由中使用 ​

python
from app.framework.security.auth import get_current_user, AuthSchema

@SomeRouter.get("/list")
async def list_controller(auth: AuthSchema = Depends(get_current_user)):
    # auth.user 已是校验通过的当前用户
    return auth.user

登录流程 ​

账号密码登录由 module_system/auth/controller.py 与 service.py 负责,流程如下:

  • 密码算法:PwdUtil 基于 PBKDF2-HMAC-SHA256(迭代 60 万次)加盐哈希,结果与 JWT 密钥一样,都是安全存储、不可逆校验。
  • 验证码:由 settings.CAPTCHA_ENABLE 控制;从 Swagger 文档页(docs/redoc)发起的请求会跳过验证码。
  • 日志:无论成功失败都会落登录日志(账号、IP、操作系统、浏览器、登录方式),便于审计与风控。

注册接口 /system/auth/register 复用了同一套 Token 生成逻辑,注册成功后直接签发令牌并登录。

刷新与续期 ​

  • 主动刷新:POST /system/auth/refresh-token?refreshToken=xxx,后端校验刷新令牌合法且会话仍在,重新签发一对令牌,并延长 USER_SESSION 的 TTL。
  • 被动续期(滑动过期):每次请求经过 get_current_user 时,若开启 TOKEN_SLIDING_EXPIRE 且剩余时间小于一半,自动延长在线状态。前端无需额外操作即可保持长会话。

退出登录 ​

POST /system/auth/logout 由 LoginService.logout 实现:解码 token 得到 session_id,删除 ACCESS_TOKEN、REFRESH_TOKEN、USER_SESSION 三个 Redis 键,使该会话立即失效,并写入退出日志。

前端集成 ​

前端位于 apps/web-ele,认证相关 API 集中在 api/core/auth.ts,状态由 store/auth.ts(useAuthStore)与 @vben/stores 的 accessStore/userStore 协作管理。

核心流程:

要点:

  • Token 存储:accessToken 与 refreshToken 存入 accessStore,每次请求通过拦截器以 Authorization: Bearer {accessToken} 形式携带。
  • 权限信息下发:登录后调用 /system/auth/get-permission-info,返回 { user, roles, menus, permissions },其中 permissions 是按钮级权限码(定义在 type=3 的菜单上),供前端 useAccess / v-access 做按钮鉴权(详见 03-前端手册/权限集成.md)。
  • 自动刷新:请求客户端内置 authenticateResponseInterceptor,当响应为 401 时自动用 refreshToken 调用 /system/auth/refresh-token 换新令牌并重试;若刷新失败则跳转登录页。并发请求会进入队列,待刷新完成后统一重放。
  • 社交登录:socialAuthRedirect 直接返回完整 URL 并赋值给 window.location.href,因为第三方 302 重定向无法用 axios 跟随。
  • 退出:logout() 调用 /system/auth/logout 后清空所有 store 并跳回登录页。

配置项速查 ​

相关配置集中在 app/config/settings.py:

配置默认值说明
SECRET_KEY随机串JWT 签名密钥,生产务必替换
ALGORITHMHS256JWT 签名算法
ACCESS_TOKEN_EXPIRE_SECONDS43200(12h)access_token 有效期
REFRESH_TOKEN_EXPIRE_SECONDS43200(12h)refresh_token 有效期
TOKEN_TYPEBearertoken 类型前缀
TOKEN_SLIDING_EXPIRETrue滑动过期(活跃自动续期)
CAPTCHA_ENABLEFalse是否启用验证码
OAUTH_DEFAULT_ROLE_IDS[2]社交登录注册用户的默认角色
OAUTH_FRONTEND_FALLBACKhttp://127.0.0.1:5173/login社交登录前端回退地址
OAUTH_REDIRECT_ALLOWED_HOSTS127.0.0.1:5173 等回调地址白名单