认证与会话管理.md 33 KB

认证与会话管理

本文引用的文件

  • StickyRouteFilter.java
  • LoginRequestFilter.java
  • LoginResponseFilter.java
  • UserRouteService.java
  • TokenResolver.java
  • SaTokenWebConfig.java
  • LoginActivityService.java
  • SysUserService.java
  • CaseDataSourceReaper.java
  • CaseDataSourceRegistry.java
  • application.yaml
  • application-dev.yaml
  • application-pro.yaml
  • authApi.ts
  • account.ts
  • axios/index.ts
  • ai-gateway-user-sticky-routing.md

目录

  1. 引言
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能与存储特性
  8. 故障排查指南
  9. 安全建议
  10. 结论

引言

本文说明基于 Sa-Token 的认证与会话管理机制,覆盖用户登录流程、Token 生成与校验、会话状态在 Redis 中的存储方式、客户端 x-token 与服务端会话的关联关系,以及网关粘性路由与服务端鉴权的协作方式。同时解释账号被顶下线或踢下线后如何释放案件数据源,并给出 Token 泄露处理、会话超时配置、敏感接口鉴权模式和调试方法等实践建议。

项目结构

本项目中认证与会话相关代码分布在三个层次:

  • 前端 Electron/Vue:负责发起登录、缓存 token、请求时注入 x-token 头,并在登录失效时清理本地状态。
  • 网关 ai-gateway:负责按用户名绑定节点、解析 x-token 到 userId、把请求转发到指定 ai-server 节点。
  • 后端 ai-server:使用 Sa-Token 完成登录、会话校验、活跃标记、登出及案件数据源释放。

    graph TB
    Client["客户端<br/>Electron / Web"] --> Gateway["网关 ai-gateway<br/>StickyRouteFilter / LoginRequestFilter / LoginResponseFilter"]
    Gateway -->|按 x-token 解析 userId| Node1["ai-server 节点-1<br/>Sa-Token 会话"]
    Gateway -->|按 x-token 解析 userId| Node2["ai-server 节点-2<br/>Sa-Token 会话"]
    Node1 --> Redis["共享 Redis<br/>sa-token 会话 + 路由缓存"]
    Node2 --> Redis
    Node1 --> PG["共享 PostgreSQL<br/>用户与路由映射"]
    Node2 --> PG
    

图示来源

  • StickyRouteFilter.java:22-57
  • TokenResolver.java:14-34
  • SaTokenWebConfig.java:10-34
  • ai-gateway-user-sticky-routing.md:18-46

章节来源

  • ai-gateway-user-sticky-routing.md:1-46

核心组件

  • 网关登录预处理:LoginRequestFilter 仅对登录路径生效,从请求体解析 username,并把响应编码改为 identity,避免后续响应解析失败。
  • 网关粘性路由:StickyRouteFilter 根据登录用户名或 x-token 决定目标 ai-server 节点,并在节点不可用时返回明确 503,不自动切换。
  • 网关登录响应处理:LoginResponseFilter 在登录成功时把用户绑定到选定节点,并把 x-token → userId 写入网关 token 缓存。
  • 网关 token 解析:TokenResolver 优先查 gateway:token:{serviceId}:{token},未命中再调用 Sa-Token 的 getLoginIdByToken,兼容旧 token。
  • 服务端会话拦截器:SaTokenWebConfig 注册 Sa-Token 拦截器,对所有业务接口做登录校验,并在已登录请求上记录活跃度。
  • 登录占用判断:LoginActivityService 用 Redis 维护用户“半小时内是否活跃”,用于登录前提示“账号正在其他设备使用”。
  • 登录服务:SysUserService.login 实现密码校验、占用确认、强制接管、Sa-Token 登录、活跃标记和登录结果封装。
  • 案件数据源保护:CaseDataSourceRegistry 管理已打开 DuckDB 数据源;CaseDataSourceReaper 兜底回收会话过期但未触发监听器的数据源。

章节来源

  • LoginRequestFilter.java:19-116
  • StickyRouteFilter.java:22-178
  • LoginResponseFilter.java:16-174
  • TokenResolver.java:14-98
  • SaTokenWebConfig.java:10-63
  • LoginActivityService.java:13-104
  • SysUserService.java:44-132
  • CaseDataSourceRegistry.java:16-42
  • CaseDataSourceReaper.java:12-34

架构总览

整体认证与会话流程分为两段:

  1. 网关层负责“把请求送到正确的节点”和“快速判断 token 对应的用户”。
  2. 后端层负责“真正校验 token 是否有效”,并维护 Sa-Token 会话、活跃标记和业务资源。

    sequenceDiagram
    participant C as "客户端"
    participant G as "网关 StickyRouteFilter"
    participant LRF as "网关 LoginRequestFilter"
    participant LRSF as "网关 LoginResponseFilter"
    participant T as "网关 TokenResolver"
    participant S as "后端 SysUserService"
    participant A as "后端 SaTokenWebConfig"
    participant R as "Redis"
    
    C->>G: POST /js/a/auth/login {username,password}
    G->>LRF: 解析 username
    LRF-->>G: 设置 LOGIN_USERNAME
    G->>G: 选择候选节点
    G->>S: 转发登录请求
    S->>A: Sa-Token 拦截器(白名单跳过)
    S->>S: 校验密码、账号状态
    S->>S: 检查 LoginActivityService.isInUse
    S->>R: 写入活跃标记 zsjz:auth:last-active:userId
    S->>S: StpUtil.login(userId)
    S-->>G: Result{code=200, data.token, data.userId, data.username}
    G->>LRSF: 捕获登录响应
    LRSF->>T: cacheToken(serviceId, token, userId)
    LRSF->>R: 写 gateway:token:serviceId:token
    LRSF-->>C: 原样返回登录响应
    C->>G: 业务请求(携带 x-token)
    G->>T: resolveUserId(serviceId, x-token)
    T->>R: 读 gateway:token:serviceId:token
    T->>S: Sa-Token getLoginIdByToken(未命中时)
    S-->>T: userId
    G->>G: 按 userId 选节点并转发
    G->>S: 业务请求
    S->>A: checkLogin()
    S-->>G: 业务响应
    

图示来源

  • LoginRequestFilter.java:47-116
  • StickyRouteFilter.java:59-128
  • LoginResponseFilter.java:58-155
  • TokenResolver.java:27-71
  • SysUserService.java:83-132
  • SaTokenWebConfig.java:36-63
  • LoginActivityService.java:54-104

详细组件分析

用户登录流程

用户登录不是单纯的密码校验,还包含账号占用判断和会话顶号控制:

  1. 前端调用 /auth/login,携带 username、password,可选 force=true。
  2. 网关 LoginRequestFilter 读取登录路径的请求体,解析 username,并把响应编码改为 identity。
  3. 网关 StickyRouteFilter 先查该用户名是否已有节点绑定;若无则选择一个健康候选节点,但不立即写绑定。
  4. 后端 SysUserService.login 校验用户名和密码、账号状态,再调用 LoginActivityService.isInUse。
  5. 如果账号正在使用且未带 force=true,返回业务码 40901,前端弹出确认框。
  6. 确认后带 force=true 重新登录,后端先 StpUtil.kickout(userId) 踢掉旧会话,再执行 StpUtil.login(user.getId())。
  7. 登录成功后,网关 LoginResponseFilter 把用户绑定到之前选择的节点,并把 x-token → userId 写入网关 token 缓存。

    flowchart TD
    Start(["开始登录"]) --> ReadBody["网关解析登录请求体中的 username"]
    ReadBody --> ChooseNode{"用户名是否已绑定节点?"}
    ChooseNode -->|是| UseBound["使用已绑定节点"]
    ChooseNode -->|否| SelectCandidate["选择健康候选节点"]
    UseBound --> ForwardLogin["转发登录请求到后端"]
    SelectCandidate --> ForwardLogin
    ForwardLogin --> CheckPassword["后端校验用户名和密码"]
    CheckPassword --> AccountStatus{"账号是否停用?"}
    AccountStatus -->|是| Error["返回用户名或密码错误"]
    AccountStatus -->|否| CheckInUse["检查账号是否正在使用"]
    CheckInUse --> InUse{"是否正在使用?"}
    InUse -->|是| ForceCheck{"是否 force=true?"}
    ForceCheck -->|否| Return40901["返回 code=40901 提示继续登录"]
    ForceCheck -->|是| KickOld["踢掉旧会话"]
    InUse -->|否| DoLogin["执行 StpUtil.login"]
    KickOld --> DoLogin
    DoLogin --> WriteActivity["写入活跃标记 zsjz:auth:last-active:userId"]
    WriteActivity --> BuildResult["构造登录结果"]
    BuildResult --> BindNode["网关绑定用户到候选节点"]
    BindNode --> CacheToken["网关缓存 x-token → userId"]
    CacheToken --> End(["结束"])
    Return40901 --> End
    Error --> End
    

图示来源

  • LoginRequestFilter.java:47-116
  • StickyRouteFilter.java:61-86
  • SysUserService.java:83-132
  • LoginActivityService.java:79-104
  • LoginResponseFilter.java:119-155

章节来源

  • SysUserService.java:83-132
  • LoginActivityService.java:54-104
  • LoginRequestFilter.java:47-116
  • LoginResponseFilter.java:58-155

Token 生成与校验

  • Token 生成由 Sa-Token 的 StpUtil.login(userId) 完成,后端返回给前端的 token 字段来自 StpUtil.getTokenValue()。
  • 前端将 token 存入本地缓存,并在每次请求时通过 axios 拦截器注入请求头 x-token。
  • 网关收到请求后:
    • 先查 gateway:token:{serviceId}:{token};
    • 未命中时调用 Sa-Token 的 getLoginIdByToken,并把结果回填到网关 token 缓存,TTL 为 24 小时;
    • 解析不出 userId 时按匿名请求处理,透传给后端,由后端返回 code=401。
  • 后端所有业务接口默认都需要登录,SaTokenWebConfig 通过 StpUtil.checkLogin() 校验;未登录时统一转成 HTTP 200 + body.code=401,前端据此跳转登录页。

    flowchart TD
    Request["业务请求携带 x-token"] --> GatewayRead["网关读取 x-token"]
    GatewayRead --> CacheHit{"gateway:token:serviceId:token 命中?"}
    CacheHit -->|是| UserIdFromCache["得到 userId"]
    CacheHit -->|否| SaTokenQuery["调用 StpUtil.getLoginIdByToken"]
    SaTokenQuery --> ValidToken{"Sa-Token 认为 token 有效?"}
    ValidToken -->|是| CacheWrite["写入网关 token 缓存 TTL 24h"]
    ValidToken -->|否| Anonymous["按匿名请求处理"]
    CacheWrite --> Route["按 userId 选节点转发"]
    UserIdFromCache --> Route
    Anonymous --> BackendAuth["后端 Sa-Token checkLogin 返回 401"]
    Route --> BackendAuth
    

图示来源

  • TokenResolver.java:27-71
  • StickyRouteFilter.java:94-128
  • SaTokenWebConfig.java:36-63
  • axios/index.ts:163-178

章节来源

  • TokenResolver.java:14-98
  • SaTokenWebConfig.java:10-63
  • axios/index.ts:163-178

会话状态在 Redis 中的存储

Sa-Token 的会话和 token 都存储在共享 Redis 中,关键行为如下:

  • 生产环境默认 is-concurrent=false,同一账号新登录会顶掉旧 token,旧 token 下次请求时会被识别为被替换。
  • 开发环境也配置 is-concurrent=false,但可通过环境变量临时开启多端。
  • 活跃标记独立于 Sa-Token 活跃超时:LoginActivityService 使用 zsjz:auth:last-active:{userId},TTL 等于判定窗口(默认 30 分钟),用于判断“账号是否正在使用”。
  • 网关额外维护 gateway:token:{serviceId}:{token},TTL 24 小时,避免每个请求都调用 Sa-Token。
  • 用户路由缓存包括 gateway:route:user:{serviceId}:{userId} 和 gateway:route:name:{serviceId}:{lower(username)},无 TTL,PostgreSQL 是事实来源。
Redis 键 值 TTL 用途
gateway:token:{serviceId}:{token} userId 24 小时 网关 token 快速解析
gateway:route:user:{serviceId}:{userId} nodeId 无 普通请求热路径
gateway:route:name:{serviceId}:{lower(username)} nodeId 无 登录请求热路径
zsjz:auth:last-active:{userId} 时间戳字符串 判定窗口分钟数 账号是否正在使用

章节来源

  • TokenResolver.java:27-71
  • UserRouteService.java:14-44
  • LoginActivityService.java:30-52
  • application-dev.yaml:62-67
  • application-pro.yaml:73-78
  • application.yaml:160-168

客户端 x-token 与服务端会话关联

  • 前端登录成功后把 token 写入本地认证缓存,并通过 setAuthCache(TOKEN_KEY, result.token) 持久化。
  • axios 请求拦截器从本地获取 token,并以 authenticationHeader 作为请求头名写入 x-token。
  • 网关从 x-token 解析 userId,再按 userId 查用户路由缓存或数据库,最终转发到对应 ai-server 节点。
  • 后端 Sa-Token 拦截器再次校验当前请求是否属于有效会话,从而形成“网关快速路由 + 后端真实鉴权”的双重机制。

    sequenceDiagram
    participant FE as "前端 account.ts"
    participant AX as "axios 拦截器"
    participant GW as "网关 StickyRouteFilter"
    participant TR as "网关 TokenResolver"
    participant SA as "后端 Sa-Token 拦截器"
    
    FE->>FE: login() 写入 TOKEN_KEY
    FE->>AX: 发起业务请求
    AX->>AX: getToken() 取本地 token
    AX->>GW: 发送请求头 x-token
    GW->>TR: 解析 x-token → userId
    TR-->>GW: userId
    GW->>GW: 选择节点并转发
    GW->>SA: 业务请求到达后端
    SA->>SA: checkLogin() 校验会话
    SA-->>GW: 通过或返回 401
    

图示来源

  • account.ts:56-103
  • axios/index.ts:163-178
  • StickyRouteFilter.java:94-128
  • TokenResolver.java:27-71
  • SaTokenWebConfig.java:36-63

章节来源

  • authApi.ts:1-124
  • account.ts:56-103
  • axios/index.ts:163-178

与网关 StickyRouteFilter 的协作关系

  • 网关职责:
    • 登录请求:按 username 查找绑定节点;未绑定时选择候选节点,等待登录成功后再落库。
    • 普通请求:按 x-token 解析 userId;若用户无绑定节点且没有 default-node,返回 503。
    • 节点健康:只选择启用且健康的节点;已绑定节点 DOWN 时直接失败,不自动切换。
    • 响应头:附加 X-Gateway-Node,便于定位请求落到哪个节点。
  • 后端职责:

    • 实际 Token 校验:SaTokenWebConfig 调用 checkLogin()。
    • 业务权限:SysUserService 中仅保留管理员判断,不做完整权限体系。
    • 会话副作用:登录活跃标记、顶号、踢号、登出释放资源。

      classDiagram
      class StickyRouteFilter {
      +filter(exchange, chain)
      +decideForLogin(exchange, service)
      +decideForNormal(exchange, service)
      +route(exchange, chain, service, nodeId)
      }
      class LoginRequestFilter {
      +filter(exchange, chain)
      +parseUsername(bytes)
      }
      class LoginResponseFilter {
      +filter(exchange, chain)
      +parseAndBind(serviceId, chosenNodeId, body)
      }
      class TokenResolver {
      +resolveUserId(serviceId, token)
      +cacheToken(serviceId, token, userId)
      }
      class UserRouteService {
      +findNodeIdByUserId(serviceId, userId)
      +findNodeIdByUsername(serviceId, username)
      +bindIfAbsent(serviceId, userId, username, nodeId)
      }
      StickyRouteFilter --> LoginRequestFilter : "使用 LOGIN_USERNAME"
      StickyRouteFilter --> UserRouteService : "查询用户路由"
      StickyRouteFilter --> TokenResolver : "解析 x-token"
      LoginResponseFilter --> UserRouteService : "绑定用户"
      LoginResponseFilter --> TokenResolver : "缓存 token"
      

图示来源

  • StickyRouteFilter.java:22-178
  • LoginRequestFilter.java:19-116
  • LoginResponseFilter.java:16-174
  • TokenResolver.java:14-98
  • UserRouteService.java:14-115

章节来源

  • StickyRouteFilter.java:22-178
  • ai-gateway-user-sticky-routing.md:33-120

账号被顶下线或踢下线后释放案件数据源

当账号被新登录顶下线、被管理员踢下线,或主动登出时,系统需要释放该用户打开的案件数据源,防止 DuckDB 实例长期占用内存:

  1. 主动登出:SysUserService.logout 先释放当前 token 打开的案件,再执行 StpUtil.logout()。
  2. 被顶下线或被踢下线:由 Sa-Token 的会话监听触发释放逻辑。
  3. 会话自然过期:Sa-Token 的 Redis 会话自然过期不会回调监听器,因此 CaseDataSourceReaper 定期扫描已打开案件,检查归属 token 是否仍有效;无效且没有在途任务时关闭数据源并清理缓存。
  4. 保守策略:查询会话状态失败、探针异常时一律不释放,宁可多占内存也不误关正在使用的案件。

    flowchart TD
    SessionEnd["会话结束:登出 / 被顶 / 被踢 / 过期"] --> Triggered{"是否触发表面事件?"}
    Triggered -->|登出| LogoutRelease["SysUserService.logout 释放当前 token 的案件"]
    Triggered -->|被顶或被踢| ListenerRelease["会话监听释放旧会话打开的案件"]
    Triggered -->|自然过期| ReaperScan["CaseDataSourceReaper 定期扫描"]
    ReaperScan --> CheckToken{"归属 token 是否仍有效?"}
    CheckToken -->|是| KeepOpen["保持数据源打开"]
    CheckToken -->|否| HasWork{"是否有在途任务?"}
    HasWork -->|是| SkipReap["本轮跳过回收"]
    HasWork -->|否| CloseCase["关闭数据源并清理缓存"]
    LogoutRelease --> Done["完成"]
    ListenerRelease --> Done
    KeepOpen --> Done
    SkipReap --> Done
    CloseCase --> Done
    

图示来源

  • SysUserService.java:134-148
  • CaseDataSourceRegistry.java:28-42
  • CaseDataSourceReaper.java:12-123

章节来源

  • SysUserService.java:134-148
  • CaseDataSourceRegistry.java:28-42
  • CaseDataSourceReaper.java:12-123

登录失效时前端清理缓存的注意事项

  • 登录成功后,前端必须把 token 和用户信息写入本地缓存,否则后续请求无法携带 x-token。
  • 登录失败时不应误判为成功;当前 loginApi 要求 code=200 且存在 data.token。
  • 登录失效后,前端应清理本地 TOKEN_KEY 和 USER_INFO_KEY,避免残留状态导致页面误以为已登录。
  • 登出时先调用后端 /auth/logout,即使网络失败也要清理本地状态,避免卡在失效登录态。
  • 刷新页面后通过 /auth/info 恢复用户态;如果接口返回 401,axios 会跳转到登录页。

章节来源

  • authApi.ts:1-124
  • account.ts:56-114
  • axios/index.ts:44-96

依赖关系分析

  • 网关模块依赖:
    • Spring Cloud Gateway 过滤器链;
    • Redis 用于 token 缓存和用户路由缓存;
    • PostgreSQL 用于用户路由事实来源;
    • Sa-Token 用于回退解析 token。
  • 后端模块依赖:
    • Sa-Token 提供登录、会话、踢号、登出能力;
    • Redis 存储会话、活跃标记;
    • PostgreSQL 存储用户信息和路由映射;
    • DuckDB 通过 CaseDataSourceRegistry 管理进程内案件数据源。
  • 前端模块依赖:

    • Axios 拦截器注入 x-token;
    • Pinia store 管理用户状态;
    • 本地缓存存储 token 和用户信息。

      graph LR
      Frontend["前端 authApi.ts / account.ts / axios"] --> Gateway["网关 StickyRouteFilter / TokenResolver / UserRouteService"]
      Gateway --> Redis["Redis"]
      Gateway --> Postgres["PostgreSQL"]
      Gateway --> Backend["后端 SysUserService / SaTokenWebConfig"]
      Backend --> Redis
      Backend --> Postgres
      Backend --> DuckDB["DuckDB 案件数据源"]
      

图示来源

  • authApi.ts:1-124
  • account.ts:1-114
  • StickyRouteFilter.java:22-178
  • TokenResolver.java:14-98
  • UserRouteService.java:14-115
  • SysUserService.java:44-148
  • CaseDataSourceRegistry.java:16-120

章节来源

  • ai-gateway-user-sticky-routing.md:18-46
  • application.yaml:1-31

性能与存储特性

  • 网关 token 解析优先走 Redis 缓存,避免每次请求都调用 Sa-Token;未命中才回源 Sa-Token,并回填 24 小时缓存。
  • 用户路由缓存采用 Redis 两级缓存:先查 Redis,未命中查 PostgreSQL,再回填 Redis。
  • 活跃标记按用户节流:同一节点每分钟最多写一次 Redis,降低高频请求对 Redis 的压力。
  • 会话超时方面,Sa-Token 的 active-timeout 当前配置为 -1,长时间研判不会被活跃超时踢掉;timeout 为 2592000 秒,即约 30 天。
  • 案件数据源有上限:max-open-cases 默认 8,超过直接拒绝开案,避免无限增长。
  • 兜底回收器默认启动延迟 60 秒,每 5 分钟扫描一次,避免频繁扫描影响调度线程池。

章节来源

  • TokenResolver.java:14-34
  • UserRouteService.java:14-44
  • LoginActivityService.java:43-52
  • application-dev.yaml:62-67
  • application-pro.yaml:73-78
  • application.yaml:72-82
  • CaseDataSourceRegistry.java:90-120
  • CaseDataSourceReaper.java:40-54

故障排查指南

  • 登录后仍提示未登录:
    • 检查前端是否正确写入 TOKEN_KEY;
    • 检查 axios 拦截器是否正确注入 x-token;
    • 检查网关 gateway:token:{serviceId}:{token} 是否存在;
    • 检查后端 Sa-Token 会话是否有效。
  • 登录后请求总是跳到另一个节点:
    • 检查 gateway:route:user:{serviceId}:{userId} 是否已写入;
    • 检查 gateway:route:name:{serviceId}:{lower(username)} 是否已写入;
    • 检查是否首次登录尚未绑定成功。
  • 登录后请求报 503 用户未绑定节点:
    • 检查服务是否配置了 default-node-id;
    • 检查是否登录成功但没有触发 LoginResponseFilter 的绑定逻辑;
    • 检查登录响应是否满足 code=200 且包含 userId、username。
  • 账号被顶下线后案件数据源未释放:
    • 检查是否在登出时被顶下线;
    • 检查是否有在途任务使回收器跳过;
    • 检查归属 token 是否仍被 Sa-Token 认为有效;
    • 查看兜底扫描日志,确认是否因会话查询失败而保守不释放。
  • 登录提示“账号正在其他设备使用”:
    • 检查 Redis 中 zsjz:auth:last-active:{userId} 是否存在;
    • 检查 Sa-Token 是否仍有该用户的 token;
    • 确认是否需要带 force=true 重新登录。

章节来源

  • StickyRouteFilter.java:94-178
  • LoginResponseFilter.java:58-174
  • TokenResolver.java:27-98
  • CaseDataSourceReaper.java:40-123
  • LoginActivityService.java:54-104

安全建议

  • Token 泄露处理:
    • 一旦怀疑 token 泄露,应尽快让原用户登出或强制踢下线,使旧 token 失效;
    • 由于网关 token 缓存 TTL 为 24 小时,建议配合后端登出或会话失效机制,不要只依赖缓存过期;
    • 检查是否有人通过代理或日志获取了 x-token,收敛日志中敏感头输出。
  • 会话超时配置:
    • 当前 Sa-Token active-timeout=-1,长时间研判不会被活跃超时踢掉,适合长任务场景;
    • 如需限制会话有效期,应调整 Sa-Token 的 timeout,并结合前端在超时后主动清理缓存;
    • 注意 active-timeout 与 zsjz.auth.in-use-window-minutes 语义不同:前者是会话活跃超时,后者只是“账号是否正在使用”的判断窗口。
  • 敏感接口鉴权模式:
    • 当前 SaTokenWebConfig 只做登录校验,不做角色或权限注解;
    • 管理员接口如 /user/** 由 SysUserService.assertAdmin() 通过 is_admin 字段保护;
    • 新增敏感接口应显式检查管理员身份或扩展 Sa-Token 权限模型,不建议仅凭 URL 白名单。
  • 调试方法:
    • 查看网关响应头 X-Gateway-Node,确认请求落到哪个节点;
    • 检查 Redis 中 gateway:token:*、gateway:route:user:*、gateway:route:name:*、zsjz:auth:last-active:*;
    • 联调时可暂时开启 Sa-Token 多端登录,但生产应保持 is-concurrent=false;
    • 前端登录接口使用 isTransformResponse:false,以便拿到 code=40901 等业务码。

章节来源

  • TokenResolver.java:14-34
  • application-dev.yaml:62-67
  • application-pro.yaml:73-78
  • application.yaml:160-168
  • SaTokenWebConfig.java:10-63
  • SysUserService.java:150-166
  • authApi.ts:39-54

结论

本系统的认证与会话管理采用“网关粘性路由 + 后端 Sa-Token 鉴权”的分层设计:网关负责按用户名和 x-token 把请求稳定转发到用户所属的 ai-server 节点,后端负责真正的会话有效性、活跃标记和资源释放。Redis 同时承担 Sa-Token 会话、网关 token 缓存、用户路由缓存和账号活跃标记的作用。为保障数据安全与内存安全,系统在顶下线、踢下线、登出和会话过期四种场景下释放案件数据源,并通过兜底回收器处理自然过期的孤儿会话。未来应重点关注 Token 泄露处置、会话超时策略、敏感接口权限扩展以及多节点部署下的缓存一致性。