# 会话管理 **本文引用的文件** - [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、设备类型等环境信息,便于审计与风控。 ```mermaid graph TB A["AuthService
登录流程"] --> B["JwtUtils
签发/解析JWT"] A --> C["ActiveSessionRegistry
活跃会话注册表"] A --> D["LoginLogService
登录日志记录"] A --> E["DbUtils
用户查询"] A --> F["ClientInfoExtractor
客户端信息采集"] B --> G["AuthProperties
JWT密钥/过期配置"] ``` 图表来源 - [AuthService.java:19-84](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L19-L84) - [JwtUtils.java:18-80](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/JwtUtils.java#L18-L80) - [ActiveSessionRegistry.java:8-29](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/ActiveSessionRegistry.java#L8-L29) - [ClientInfoExtractor.java:13-61](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/ClientInfoExtractor.java#L13-L61) 章节来源 - [AuthService.java:19-84](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L19-L84) - [JwtUtils.java:18-80](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/JwtUtils.java#L18-L80) - [ActiveSessionRegistry.java:8-29](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/ActiveSessionRegistry.java#L8-L29) - [ClientInfoExtractor.java:13-61](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/ClientInfoExtractor.java#L13-L61) ## 核心组件 - ActiveSessionRegistry:基于ConcurrentHashMap维护“机构+账号”到“最新会话ID”的映射,提供激活、存在性检查、会话有效性校验能力。 - AuthService:封装登录流程,包含用户校验、密码比对、JWT签发、会话激活与登录日志记录。 - JwtUtils:负责JWT的初始化、签发与解析,将sessionId写入令牌ID,并在解析时返回userId、orgId、sessionId三元组。 - ClientInfo/ClientInfoExtractor:采集客户端IP、地理位置、浏览器/操作系统/设备类型及原始UA,用于登录审计与风控。 章节来源 - [ActiveSessionRegistry.java:8-29](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/ActiveSessionRegistry.java#L8-L29) - [AuthService.java:19-84](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L19-L84) - [JwtUtils.java:18-80](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/JwtUtils.java#L18-L80) - [ClientInfo.java:1-16](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/ClientInfo.java#L1-L16) - [ClientInfoExtractor.java:13-61](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/ClientInfoExtractor.java#L13-L61) ## 架构总览 下图展示了从登录请求到会话激活与JWT签发的完整调用链,以及后续鉴权时对会话有效性的校验路径。 ```mermaid 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 ``` 图表来源 - [AuthService.java:34-84](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L34-L84) - [JwtUtils.java:45-57](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/JwtUtils.java#L45-L57) - [ActiveSessionRegistry.java:13-15](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/ActiveSessionRegistry.java#L13-L15) ## 详细组件分析 ### ActiveSessionRegistry:设计模式与实现原理 - 设计要点 - 使用线程安全的ConcurrentHashMap存储“账户键→最新会话ID”,保证并发下的正确性与高性能。 - 账户键由orgId与userId组合而成,确保不同机构下同名用户的会话相互独立。 - 提供activate、isActive、hasActiveSession三个核心方法,分别用于激活会话、校验指定会话是否仍为当前活跃会话、判断是否存在活跃会话。 - 多设备登录控制 - 同一账号在同一机构内多次登录会覆盖之前的会话ID,即“后登录优先”。旧会话ID将不再通过isActive校验,从而实现“强制下线”的效果。 - 测试用例验证了同账号新登录替换旧会话、跨机构会话独立的语义。 - 过期清理机制 - 当前实现未内置定时清理逻辑。可结合JWT过期时间或应用启动/定时任务扫描,移除已过期或长时间无心跳的会话。 - 复杂度与内存 - 插入/查找/判空均为O(1)。随着在线用户数增长,内存占用线性增长。需关注高并发场景下的内存上限与淘汰策略。 ```mermaid 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 : "组合键" ``` 图表来源 - [ActiveSessionRegistry.java:8-29](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/ActiveSessionRegistry.java#L8-L29) 章节来源 - [ActiveSessionRegistry.java:8-29](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/ActiveSessionRegistry.java#L8-L29) - [ActiveSessionRegistryTests.java:11-30](file://fms-api/src/test/java/cn/g3soft/fmsapi/service/ActiveSessionRegistryTests.java#L11-L30) ### AuthService:登录流程与会话绑定 - 登录流程 - 标准化orgId与userId,查询用户信息,校验密码。 - 成功后生成UUID作为sessionId,调用JwtUtils生成token,并将orgId、accountId、sessionId注册到ActiveSessionRegistry。 - 记录登录日志(成功/失败),返回token、orgId与用户基本信息。 - 多设备冲突处理 - 通过ActiveSessionRegistry的activate覆盖旧会话,实现“同账号单设备在线”的策略。 - 安全与健壮性 - 使用常量时间比较避免时序攻击风险(MessageDigest.isEqual)。 - 对缺失字段进行严格校验,抛出异常以快速失败。 ```mermaid 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"] ``` 图表来源 - [AuthService.java:34-84](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L34-L84) - [AuthService.java:115-124](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L115-L124) 章节来源 - [AuthService.java:34-84](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L34-L84) - [AuthService.java:115-124](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L115-L124) ### JwtUtils:令牌与会话的关联机制 - 令牌绑定 - generateToken时将sessionId放入JWT的ID字段,subject为用户ID,orgid作为自定义声明,从而建立“令牌↔会话”的强绑定。 - 同步更新 - 每次登录都会生成新的sessionId并覆盖旧会话,旧令牌即使未过期也会因会话不匹配而失效。 - 失效处理 - 服务端可通过解析令牌获取sessionId,再与ActiveSessionRegistry中的当前会话对比,不一致则拒绝访问。 - 令牌自身有效期由配置控制,到期后自然失效。 ```mermaid 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)" ``` 图表来源 - [JwtUtils.java:45-66](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/JwtUtils.java#L45-L66) 章节来源 - [JwtUtils.java:18-80](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/JwtUtils.java#L18-L80) ### 客户端信息采集:审计与风控基础 - 采集内容 - IP地址、IP地理位置、浏览器名称与版本、操作系统与版本、设备类型(桌面/移动/平板/机器人)及原始UA。 - 用途 - 登录日志审计、异常登录检测、设备指纹辅助风控。 - 容错 - UA解析失败时降级为默认值,不影响主流程。 章节来源 - [ClientInfo.java:1-16](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/ClientInfo.java#L1-L16) - [ClientInfoExtractor.java:13-61](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/ClientInfoExtractor.java#L13-L61) ## 依赖关系分析 - 组件耦合 - AuthService依赖JwtUtils、ActiveSessionRegistry、DbUtils、LoginLogService与ClientInfoExtractor,承担编排职责。 - JwtUtils依赖配置AuthProperties,负责密钥与过期时间管理。 - ActiveSessionRegistry仅依赖JDK并发容器,低耦合、高内聚。 - 外部依赖 - JWT库用于令牌签发与解析。 - uap-java用于UA解析。 - 潜在循环依赖 - 当前未发现循环依赖。 ```mermaid graph LR AuthService --> JwtUtils AuthService --> ActiveSessionRegistry AuthService --> DbUtils AuthService --> LoginLogService AuthService --> ClientInfoExtractor JwtUtils --> AuthProperties ``` 图表来源 - [AuthService.java:19-33](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L19-L33) - [JwtUtils.java:18-26](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/JwtUtils.java#L18-L26) 章节来源 - [AuthService.java:19-33](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L19-L33) - [JwtUtils.java:18-26](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/JwtUtils.java#L18-L26) ## 性能考虑 - 并发与锁 - ActiveSessionRegistry使用ConcurrentHashMap,读写操作无锁化,适合高并发场景。 - 内存占用 - 每个在线会话占用一个Map条目,需评估最大在线用户规模与堆内存上限。 - 过期清理 - 当前未实现自动清理。建议引入定时任务或事件驱动清理,依据JWT过期时间或最近访问时间剔除无效会话,降低内存压力。 - 令牌解析开销 - 高频鉴权场景下,JWT解析成本可控,但应避免重复解析;可在网关或过滤器层缓存解析结果(注意安全性)。 - 登录风暴防护 - 建议在AuthService前增加限流与验证码机制,防止暴力破解与资源耗尽。 [本节为通用性能建议,不直接分析具体文件] ## 故障排查指南 - 登录失败 - 账号不存在或密码错误:检查DbUtils查询与密码比对逻辑,确认数据库数据与加密方式一致。 - 登录日志记录失败:检查LoginLogService与数据库连接。 - 会话冲突 - 新登录后旧会话无法访问:这是预期行为(同账号单设备在线)。如需允许多设备,请修改ActiveSessionRegistry为“按设备维度”存储。 - JWT问题 - 密钥长度不足或过期时间为非正数:检查AuthProperties配置,确保满足最小长度与正值要求。 - 令牌解析失败:检查签名密钥一致性与时钟同步。 - 客户端信息异常 - UA解析失败:查看日志告警,必要时降级处理或屏蔽UA解析以提升稳定性。 章节来源 - [AuthService.java:47-61](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L47-L61) - [JwtUtils.java:28-43](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/JwtUtils.java#L28-L43) - [ClientInfoExtractor.java:41-59](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/ClientInfoExtractor.java#L41-L59) ## 结论 当前FMS会话管理采用“轻量级内存注册表 + JWT绑定”的方案,实现了同账号单设备在线与会话冲突的强制下线策略。该方案简单高效、易于维护,适用于中小规模在线用户场景。若需支持多设备登录、精细化会话生命周期管理与更丰富的监控能力,可在现有基础上扩展会话存储结构、引入过期清理与统一鉴权拦截器。 [本节为总结性内容,不直接分析具体文件] ## 附录 ### 会话数据存储结构与内存管理策略 - 数据结构 - 键:AccountKey = (orgId, userId) - 值:sessionId(字符串) - 内存管理 - 使用ConcurrentHashMap,O(1)读写。 - 建议:结合定时任务或事件触发清理过期/空闲会话,避免无限增长。 - 可考虑LRU或分片策略以应对超大规模在线用户。 章节来源 - [ActiveSessionRegistry.java:11-29](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/ActiveSessionRegistry.java#L11-L29) ### 会话与JWT令牌的关联机制 - 绑定方式 - sessionId写入JWT的ID字段,subject为用户ID,orgid作为声明。 - 同步更新 - 每次登录生成新sessionId并覆盖旧会话,旧令牌即使未过期也会因会话不匹配而失效。 - 失效处理 - 鉴权时解析令牌获取sessionId,与ActiveSessionRegistry中的当前会话比对,不一致则拒绝。 章节来源 - [JwtUtils.java:45-66](file://fms-api/src/main/java/cn/g3soft/fmsapi/utils/JwtUtils.java#L45-L66) - [AuthService.java:63-84](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/AuthService.java#L63-L84) ### 多设备登录控制策略 - 当前策略 - 同账号在同一机构内仅允许一个活跃会话,新登录将覆盖旧会话,实现“强制下线”。 - 冲突检测 - 通过ActiveSessionRegistry.isActive判断请求携带的sessionId是否为当前活跃会话。 - 扩展建议 - 若需多设备登录,可将键改为(orgId, userId, deviceId),并引入设备注册与去重逻辑。 章节来源 - [ActiveSessionRegistry.java:13-22](file://fms-api/src/main/java/cn/g3soft/fmsapi/service/ActiveSessionRegistry.java#L13-L22) - [ActiveSessionRegistryTests.java:11-30](file://fms-api/src/test/java/cn/g3soft/fmsapi/service/ActiveSessionRegistryTests.java#L11-L30) ### 会话监控与管理接口建议 - 监控指标 - 在线会话数(按orgId/userId维度)、登录成功率、失败原因分布、JWT解析失败率。 - 管理接口 - 查询某账号当前活跃会话列表(可扩展为按设备维度)。 - 强制下线:根据orgId与userId清除对应会话。 - 批量清理:按过期时间或最后访问时间清理无效会话。 - 实现建议 - 在AuthService或新增SessionController暴露REST接口,内部调用ActiveSessionRegistry与JwtUtils完成操作。 [本节为概念性建议,不直接分析具体文件] ### 安全最佳实践 - 密钥管理 - JWT密钥长度不少于32字节,定期轮换;生产环境使用密钥管理服务。 - 传输安全 - 全站HTTPS,设置Cookie安全属性(如HttpOnly、Secure、SameSite)。 - 防暴力破解 - 登录接口限流、验证码、账号锁定策略。 - 会话安全 - 会话ID随机且不可预测;鉴权时校验会话与令牌一致性;及时清理过期/空闲会话。 - 审计与合规 - 记录登录成功/失败、设备信息、IP与位置;保留足够审计周期。 [本节为通用安全建议,不直接分析具体文件] ### 性能优化建议 - 减少解析次数 - 在网关或过滤器层缓存JWT解析结果(注意短期缓存与失效策略)。 - 降低GC压力 - 合理设置JVM参数,避免频繁创建大对象;会话清理定时任务分批执行。 - 水平扩展 - 若部署多实例,需将ActiveSessionRegistry迁移至分布式缓存(如Redis),保证跨实例一致性。 [本节为通用性能建议,不直接分析具体文件]