You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()
        );
    }
}

关键修改点说明

  1. 响应式方法签名:将filter方法返回类型改为Uni<Void>,Quarkus会自动识别并处理响应式流程,保持请求上下文直到Uni流完成。
  2. 移除手动订阅:通过响应式链式调用(onItem()、onFailure())处理验证逻辑,避免异步操作脱离上下文。
  3. 正确跳过登录接口:返回Uni.createFrom().voidItem()代替直接return,符合响应式方法的返回要求。
  4. 异常处理:通过onFailure().invoke()统一处理验证过程中的异常,确保请求被正确终止。

单会话登录的额外优化建议

  • Token过期处理:在数据库会话记录中添加过期时间,验证时同时检查JWT过期时间和数据库中的过期时间。
  • 登出逻辑:提供登出接口,删除数据库中对应的token记录,确保会话立即失效。
  • 性能优化:会话量较大时,改用Redis等缓存存储token,替代数据库查询提升验证性能。
  • JWT集成:结合Quarkus的JWT扩展,先验证JWT签名和有效性,再检查数据库会话记录,减少无效查询。

参考资料

  • Quarkus响应式JAX-RS过滤器规范
  • Hibernate Reactive上下文管理机制
  • Quarkus JWT安全扩展使用指南

内容的提问来源于stack exchange,提问作者Dinesh kumar S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:50:13