Quarkus单会话登录问题:quarkus-hibernate-reactive会话关闭报错
问题分析与解决方案
核心问题原因
你遇到的"Session/EntityManager is closed"错误,根源在于同步过滤器与响应式操作的不兼容:
- 原实现的
ContainerRequestFilter的filter方法是同步的(返回void),手动调用Uni.subscribe()会触发异步操作,但过滤器方法会立即执行完毕,Quarkus提前销毁请求上下文(包括Hibernate Reactive的会话)。 - 后续数据库查询操作在上下文关闭后执行,自然抛出会话已关闭的异常。
正确实现方式
Quarkus支持响应式JAX-RS过滤器,只需将filter方法改为返回Uni<Void>,通过响应式链处理所有逻辑,确保请求上下文保持活跃直到验证流程完成。
修改后的完整代码
package asc.qrs.common; import asc.qrs.repository.UserSessionRepository; import io.quarkus.logging.Log; import io.smallrye.mutiny.Uni; import jakarta.annotation.Priority; import jakarta.inject.Inject; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider @Priority(1) public class SingleSessionFilter implements ContainerRequestFilter { @Inject UserSessionRepository userSessionRepository; // 关键修改:将返回类型改为Uni<Void>,实现响应式过滤器 @Override public Uni<Void> filter(ContainerRequestContext requestContext) { String path = requestContext.getUriInfo().getPath(); if (path.equals(AppConstant.USER_LOGIN)) { return Uni.createFrom().voidItem(); // 返回空Uni,跳过验证 } Log.info("SingleSessionFilter started"); String header = requestContext.getHeaderString("Authorization"); if (header == null || !header.startsWith("Bearer ")) { Log.info("Authorization header missing or invalid"); abortRequest(requestContext, Response.Status.UNAUTHORIZED, "Authorization header is missing or invalid"); return Uni.createFrom().voidItem(); } String token = header.substring(7); Log.infof("Extracted token: %s", token); // 使用响应式链处理验证,无需手动subscribe return checkTokenValidity(token) .onItem().transform(isValid -> { if (!isValid) { Log.infof("Invalid token detected: %s", token); abortRequest(requestContext, Response.Status.UNAUTHORIZED, "Invalid token"); } else { Log.infof("Token is valid: %s", token); } return null; // 转为Void类型 }) .onFailure().invoke(failure -> { Log.errorf("Error during token validation: %s", failure.getMessage()); abortRequest(requestContext, Response.Status.INTERNAL_SERVER_ERROR, "Internal error occurred"); }) .replaceWithVoid(); // 最终返回Uni<Void> } private Uni<Boolean> checkTokenValidity(String token) { return userSessionRepository.countByToken(token) .onItem().transform(count -> count > 0); } private void abortRequest(ContainerRequestContext requestContext, Response.Status status, String message) { requestContext.abortWith( Response.status(status).entity(message).build() ); } }
关键修改点说明
- 响应式方法签名:将
filter方法返回类型改为Uni<Void>,Quarkus会自动识别并处理响应式流程,保持请求上下文直到Uni流完成。 - 移除手动订阅:通过响应式链式调用(
onItem()、onFailure())处理验证逻辑,避免异步操作脱离上下文。 - 正确跳过登录接口:返回
Uni.createFrom().voidItem()代替直接return,符合响应式方法的返回要求。 - 异常处理:通过
onFailure().invoke()统一处理验证过程中的异常,确保请求被正确终止。
单会话登录的额外优化建议
- Token过期处理:在数据库会话记录中添加过期时间,验证时同时检查JWT过期时间和数据库中的过期时间。
- 登出逻辑:提供登出接口,删除数据库中对应的token记录,确保会话立即失效。
- 性能优化:会话量较大时,改用Redis等缓存存储token,替代数据库查询提升验证性能。
- JWT集成:结合Quarkus的JWT扩展,先验证JWT签名和有效性,再检查数据库会话记录,减少无效查询。
参考资料
- Quarkus响应式JAX-RS过滤器规范
- Hibernate Reactive上下文管理机制
- Quarkus JWT安全扩展使用指南
内容的提问来源于stack exchange,提问作者Dinesh kumar S
相关产品推荐
相关产品推荐

