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

Vert.x自定义认证抛出Response head already sent异常问题求助

Vert.x Mutiny 无JWT认证端点实现方案

问题根源分析

  • java.lang.IllegalStateException: Response head already sent 异常本质是重复发送响应(比如同时调用response.end()和ctx.next()),或是在响应已发送后尝试修改响应、触发后续处理器。
  • 移除ctx.response()和ctx.next()后返回204,是因为未主动结束响应,Vert.x默认返回空内容的204状态码。

正确的Login端点实现(Mutiny风格)

基于你自定义的CustomAuthenticationProvider,用Mutiny的Uni链式调用处理,避免回调混用导致的响应发送冲突:

// 注入自定义认证提供者
@Inject
CustomAuthenticationProvider authProvider;

// 配置Login路由
router.post("/login").handler(ctx -> {
    // 解析JSON请求体,提取手机号和密码
    ctx.body().asJsonObject()
        .onItem().transform(body -> {
            String phone = body.getString("phone");
            String password = body.getString("password");
            // 构建认证凭证
            return Credentials.create()
                .put("phone", phone)
                .put("password", password);
        })
        .onItem().transformToUni(credentials -> authProvider.authenticate(credentials))
        .subscribe().with(
            user -> {
                // 认证成功:将用户信息存入会话
                ctx.session().put("user", user.principal());
                // 发送成功响应
                ctx.response()
                    .setStatusCode(200)
                    .putHeader("Content-Type", "application/json")
                    .end("{\"status\":\"success\",\"message\":\"登录成功\"}");
            },
            error -> {
                // 认证失败:发送错误响应
                ctx.response()
                    .setStatusCode(401)
                    .putHeader("Content-Type", "application/json")
                    .end("{\"status\":\"error\",\"message\":\"手机号或密码错误\"}");
            }
        );
});

注意:登录端点是终结点,不要调用ctx.next(),所有响应操作都在Uni的回调里完成,确保响应只发送一次。

无JWT的端点保护方案(基于Vert.x Session)

用Vert.x内置的Session机制实现会话认证,无需依赖JWT:

1. 启用Session支持

启动Vert.x时配置Session存储(开发环境用内存存储,生产环境可替换为Redis等分布式存储):

Vertx vertx = Vertx.vertx();
Router router = Router.router(vertx);

// 启用本地Session存储
SessionStore sessionStore = LocalSessionStore.create(vertx);
router.route().handler(SessionHandler.create(sessionStore));

2. 编写认证拦截器

创建全局拦截器,校验会话中的用户信息,未登录则拦截请求:

// 拦截所有/api前缀的请求
router.route("/api/*").handler(ctx -> {
    JsonObject user = ctx.session().get("user");
    if (user == null) {
        // 未登录,返回401
        ctx.response()
            .setStatusCode(401)
            .putHeader("Content-Type", "application/json")
            .end("{\"status\":\"error\",\"message\":\"请先登录\"}");
    } else {
        // 已登录,放行至后续处理器
        ctx.next();
    }
});

3. 受保护端点示例

router.get("/api/user/profile").handler(ctx -> {
    JsonObject user = ctx.session().get("user");
    ctx.response()
        .setStatusCode(200)
        .putHeader("Content-Type", "application/json")
        .end(user.encode());
});

关键注意事项

  • 避免Mutiny Uni与Vert.x回调API混用:不要在Uni回调中同时调用response.end()和ctx.next(),会导致响应重复发送。
  • 会话存储选型:内存存储仅适合开发环境,生产环境推荐使用RedisSessionStore实现分布式会话。
  • 登出功能实现:直接销毁会话即可:
router.post("/logout").handler(ctx -> {
    ctx.session().destroy();
    ctx.response()
        .setStatusCode(200)
        .putHeader("Content-Type", "application/json")
        .end("{\"status\":\"success\",\"message\":\"已登出\"}");
});

内容的提问来源于stack exchange,提问作者Plus Ultra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 10:25:02