20260823210241
This commit is contained in:
1 parent
5ff4583aeb
commit
f4c7413dc6
2766 files changed
+1836409
-94689
No files matched your search
@@ -0,0 +1,349 @@
|
||||
# 会话管理
|
||||
|
||||
<cite>
|
||||
**本文引用的文件**
|
||||
- [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)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
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<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密钥/过期配置"]
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [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),保证跨实例一致性。
|
||||
|
||||
[本节为通用性能建议,不直接分析具体文件]
|
||||
Reference in new issue
Block a user