Files
workspace/code/fms/.qoder/repowiki/zh/content/认证授权系统/会话管理.md
T
2026-08-23 21:02:41 +08:00

18 KiB
Raw Blame History

会话管理

**本文引用的文件** - [ActiveSessionRegistry.java](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/ActiveSessionRegistry.java) - [AuthService.java](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java) - [JwtUtils.java](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/JwtUtils.java) - [ClientInfo.java](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/ClientInfo.java) - [ClientInfoExtractor.java](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/ClientInfoExtractor.java) - [ActiveSessionRegistryTests.java](file://fms-api/src/test/java/cn/g3soft/fmsapi/service/ActiveSessionRegistryTests.java)

目录

  1. 简介
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能考虑
  8. 故障排查指南
  9. 结论
  10. 附录

简介

本技术文档聚焦FMS系统的会话管理,围绕ActiveSessionRegistry的设计与实现,系统阐述会话注册、状态校验、多设备登录控制策略(同账号限制与会话冲突)、以及JWT令牌与会话的绑定与失效机制。同时给出会话数据的内存存储模型、监控与管理接口建议、安全最佳实践与性能优化建议,帮助读者在理解现有实现的基础上进行扩展与维护。

项目结构

当前会话管理相关代码位于后端API模块中,主要涉及以下职责:

  • 认证与会话注册:AuthService负责登录流程,生成JWT并激活会话;ActiveSessionRegistry维护“机构+账号”维度的活跃会话映射。
  • JWT工具:JwtUtils负责令牌的签发与解析,将sessionId作为令牌ID并与orgid、userId绑定。
  • 客户端信息采集:ClientInfo与ClientInfoExtractor用于采集登录时的IP、UA、设备类型等环境信息,便于审计与风控。
graph TB
A["AuthService<br/>登录流程"] --> B["JwtUtils<br/>签发/解析JWT"]
A --> C["ActiveSessionRegistry<br/>活跃会话注册表"]
A --> D["LoginLogService<br/>登录日志记录"]
A --> E["DbUtils<br/>用户查询"]
A --> F["ClientInfoExtractor<br/>客户端信息采集"]
B --> G["AuthProperties<br/>JWT密钥/过期配置"]

图表来源

章节来源

核心组件

  • ActiveSessionRegistry:基于ConcurrentHashMap维护“机构+账号”到“最新会话ID”的映射,提供激活、存在性检查、会话有效性校验能力。
  • AuthService:封装登录流程,包含用户校验、密码比对、JWT签发、会话激活与登录日志记录。
  • JwtUtils:负责JWT的初始化、签发与解析,将sessionId写入令牌ID,并在解析时返回userId、orgId、sessionId三元组。
  • ClientInfo/ClientInfoExtractor:采集客户端IP、地理位置、浏览器/操作系统/设备类型及原始UA,用于登录审计与风控。

章节来源

架构总览

下图展示了从登录请求到会话激活与JWT签发的完整调用链,以及后续鉴权时对会话有效性的校验路径。

sequenceDiagram
participant Client as "客户端"
participant Auth as "AuthService"
participant DB as "DbUtils"
participant JWT as "JwtUtils"
participant Reg as "ActiveSessionRegistry"
participant Log as "LoginLogService"
Client->>Auth : "提交登录(orgId, userId, password)"
Auth->>DB : "查询用户信息"
DB-->>Auth : "用户数据或空"
alt "用户不存在或密码错误"
Auth->>Log : "记录失败登录(含原因)"
Auth-->>Client : "返回失败"
else "认证成功"
Auth->>JWT : "generateToken(userId, orgId, sessionId)"
JWT-->>Auth : "返回token"
Auth->>Reg : "activate(orgId, accountId, sessionId)"
Auth->>Log : "记录成功登录(含sessionId)"
Auth-->>Client : "返回{token, orgId, user}"
end

图表来源

详细组件分析

ActiveSessionRegistry:设计模式与实现原理

  • 设计要点
    • 使用线程安全的ConcurrentHashMap存储“账户键→最新会话ID”,保证并发下的正确性与高性能。
    • 账户键由orgId与userId组合而成,确保不同机构下同名用户的会话相互独立。
    • 提供activate、isActive、hasActiveSession三个核心方法,分别用于激活会话、校验指定会话是否仍为当前活跃会话、判断是否存在活跃会话。
  • 多设备登录控制
    • 同一账号在同一机构内多次登录会覆盖之前的会话ID,即“后登录优先”。旧会话ID将不再通过isActive校验,从而实现“强制下线”的效果。
    • 测试用例验证了同账号新登录替换旧会话、跨机构会话独立的语义。
  • 过期清理机制
    • 当前实现未内置定时清理逻辑。可结合JWT过期时间或应用启动/定时任务扫描,移除已过期或长时间无心跳的会话。
  • 复杂度与内存
    • 插入/查找/判空均为O(1)。随着在线用户数增长,内存占用线性增长。需关注高并发场景下的内存上限与淘汰策略。
classDiagram
class ActiveSessionRegistry {
- Map~AccountKey, String~ activeSessions
+ activate(orgId, userId, sessionId) void
+ isActive(orgId, userId, sessionId) boolean
+ hasActiveSession(orgId, userId) boolean
}
class AccountKey {
+ String orgId
+ String userId
}
ActiveSessionRegistry --> AccountKey : "组合键"

图表来源

章节来源

AuthService:登录流程与会话绑定

  • 登录流程
    • 标准化orgId与userId,查询用户信息,校验密码。
    • 成功后生成UUID作为sessionId,调用JwtUtils生成token,并将orgId、accountId、sessionId注册到ActiveSessionRegistry。
    • 记录登录日志(成功/失败),返回token、orgId与用户基本信息。
  • 多设备冲突处理
    • 通过ActiveSessionRegistry的activate覆盖旧会话,实现“同账号单设备在线”的策略。
  • 安全与健壮性
    • 使用常量时间比较避免时序攻击风险(MessageDigest.isEqual)。
    • 对缺失字段进行严格校验,抛出异常以快速失败。
flowchart TD
Start(["登录入口"]) --> Normalize["标准化输入(orgId, userId)"]
Normalize --> QueryUser["查询用户信息"]
QueryUser --> UserFound{"找到用户?"}
UserFound --> |否| LogFail["记录失败登录"] --> ReturnFail["返回失败"]
UserFound --> |是| CheckPwd["密码校验"]
CheckPwd --> PwdOk{"密码正确?"}
PwdOk --> |否| LogFail
PwdOk --> |是| GenToken["生成sessionId与JWT"]
GenToken --> Activate["注册活跃会话"]
Activate --> LogSuccess["记录成功登录"]
LogSuccess --> ReturnOK["返回token/orgId/user"]

图表来源

章节来源

JwtUtils:令牌与会话的关联机制

  • 令牌绑定
    • generateToken时将sessionId放入JWT的ID字段,subject为用户ID,orgid作为自定义声明,从而建立“令牌↔会话”的强绑定。
  • 同步更新
    • 每次登录都会生成新的sessionId并覆盖旧会话,旧令牌即使未过期也会因会话不匹配而失效。
  • 失效处理
    • 服务端可通过解析令牌获取sessionId,再与ActiveSessionRegistry中的当前会话对比,不一致则拒绝访问。
    • 令牌自身有效期由配置控制,到期后自然失效。
sequenceDiagram
participant Auth as "AuthService"
participant JWT as "JwtUtils"
participant Parser as "JwtParser"
Auth->>JWT : "generateToken(userId, orgId, sessionId)"
JWT-->>Auth : "token"
Note over Auth,JWT : "sessionId被写入令牌ID"
Auth->>JWT : "parseToken(token)"
JWT->>Parser : "解析签名与载荷"
Parser-->>JWT : "Claims(subject, orgid, id=sessionId)"
JWT-->>Auth : "TokenClaims(userId, orgId, sessionId)"

图表来源

章节来源

客户端信息采集:审计与风控基础

  • 采集内容
    • IP地址、IP地理位置、浏览器名称与版本、操作系统与版本、设备类型(桌面/移动/平板/机器人)及原始UA。
  • 用途
    • 登录日志审计、异常登录检测、设备指纹辅助风控。
  • 容错
    • UA解析失败时降级为默认值,不影响主流程。

章节来源

依赖关系分析

  • 组件耦合
    • AuthService依赖JwtUtils、ActiveSessionRegistry、DbUtils、LoginLogService与ClientInfoExtractor,承担编排职责。
    • JwtUtils依赖配置AuthProperties,负责密钥与过期时间管理。
    • ActiveSessionRegistry仅依赖JDK并发容器,低耦合、高内聚。
  • 外部依赖
    • JWT库用于令牌签发与解析。
    • uap-java用于UA解析。
  • 潜在循环依赖
    • 当前未发现循环依赖。
graph LR
AuthService --> JwtUtils
AuthService --> ActiveSessionRegistry
AuthService --> DbUtils
AuthService --> LoginLogService
AuthService --> ClientInfoExtractor
JwtUtils --> AuthProperties

图表来源

章节来源

性能考虑

  • 并发与锁
    • ActiveSessionRegistry使用ConcurrentHashMap,读写操作无锁化,适合高并发场景。
  • 内存占用
    • 每个在线会话占用一个Map条目,需评估最大在线用户规模与堆内存上限。
  • 过期清理
    • 当前未实现自动清理。建议引入定时任务或事件驱动清理,依据JWT过期时间或最近访问时间剔除无效会话,降低内存压力。
  • 令牌解析开销
    • 高频鉴权场景下,JWT解析成本可控,但应避免重复解析;可在网关或过滤器层缓存解析结果(注意安全性)。
  • 登录风暴防护
    • 建议在AuthService前增加限流与验证码机制,防止暴力破解与资源耗尽。

[本节为通用性能建议,不直接分析具体文件]

故障排查指南

  • 登录失败
    • 账号不存在或密码错误:检查DbUtils查询与密码比对逻辑,确认数据库数据与加密方式一致。
    • 登录日志记录失败:检查LoginLogService与数据库连接。
  • 会话冲突
    • 新登录后旧会话无法访问:这是预期行为(同账号单设备在线)。如需允许多设备,请修改ActiveSessionRegistry为“按设备维度”存储。
  • JWT问题
    • 密钥长度不足或过期时间为非正数:检查AuthProperties配置,确保满足最小长度与正值要求。
    • 令牌解析失败:检查签名密钥一致性与时钟同步。
  • 客户端信息异常
    • UA解析失败:查看日志告警,必要时降级处理或屏蔽UA解析以提升稳定性。

章节来源

结论

当前FMS会话管理采用“轻量级内存注册表 + JWT绑定”的方案,实现了同账号单设备在线与会话冲突的强制下线策略。该方案简单高效、易于维护,适用于中小规模在线用户场景。若需支持多设备登录、精细化会话生命周期管理与更丰富的监控能力,可在现有基础上扩展会话存储结构、引入过期清理与统一鉴权拦截器。

[本节为总结性内容,不直接分析具体文件]

附录

会话数据存储结构与内存管理策略

  • 数据结构
    • 键:AccountKey = (orgId, userId)
    • 值:sessionId(字符串)
  • 内存管理
    • 使用ConcurrentHashMap,O(1)读写。
    • 建议:结合定时任务或事件触发清理过期/空闲会话,避免无限增长。
    • 可考虑LRU或分片策略以应对超大规模在线用户。

章节来源

会话与JWT令牌的关联机制

  • 绑定方式
    • sessionId写入JWT的ID字段,subject为用户ID,orgid作为声明。
  • 同步更新
    • 每次登录生成新sessionId并覆盖旧会话,旧令牌即使未过期也会因会话不匹配而失效。
  • 失效处理
    • 鉴权时解析令牌获取sessionId,与ActiveSessionRegistry中的当前会话比对,不一致则拒绝。

章节来源

多设备登录控制策略

  • 当前策略
    • 同账号在同一机构内仅允许一个活跃会话,新登录将覆盖旧会话,实现“强制下线”。
  • 冲突检测
    • 通过ActiveSessionRegistry.isActive判断请求携带的sessionId是否为当前活跃会话。
  • 扩展建议
    • 若需多设备登录,可将键改为(orgId, userId, deviceId),并引入设备注册与去重逻辑。

章节来源

会话监控与管理接口建议

  • 监控指标
    • 在线会话数(按orgId/userId维度)、登录成功率、失败原因分布、JWT解析失败率。
  • 管理接口
    • 查询某账号当前活跃会话列表(可扩展为按设备维度)。
    • 强制下线:根据orgId与userId清除对应会话。
    • 批量清理:按过期时间或最后访问时间清理无效会话。
  • 实现建议
    • 在AuthService或新增SessionController暴露REST接口,内部调用ActiveSessionRegistry与JwtUtils完成操作。

[本节为概念性建议,不直接分析具体文件]

安全最佳实践

  • 密钥管理
    • JWT密钥长度不少于32字节,定期轮换;生产环境使用密钥管理服务。
  • 传输安全
    • 全站HTTPS,设置Cookie安全属性(如HttpOnly、Secure、SameSite)。
  • 防暴力破解
    • 登录接口限流、验证码、账号锁定策略。
  • 会话安全
    • 会话ID随机且不可预测;鉴权时校验会话与令牌一致性;及时清理过期/空闲会话。
  • 审计与合规
    • 记录登录成功/失败、设备信息、IP与位置;保留足够审计周期。

[本节为通用安全建议,不直接分析具体文件]

性能优化建议

  • 减少解析次数
    • 在网关或过滤器层缓存JWT解析结果(注意短期缓存与失效策略)。
  • 降低GC压力
    • 合理设置JVM参数,避免频繁创建大对象;会话清理定时任务分批执行。
  • 水平扩展
    • 若部署多实例,需将ActiveSessionRegistry迁移至分布式缓存(如Redis),保证跨实例一致性。

[本节为通用性能建议,不直接分析具体文件]