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

Quarkus对接Keycloak认证GraphQL端点如何自定义401响应

Quarkus + Keycloak + GraphQL 自定义401响应实现方案

你遇到的两个方案不生效的核心原因有两个:

  • Quarkus OIDC默认开启主动认证(Proactive Authentication),会在请求进入GraphQL引擎、JAX-RS容器之前就完成鉴权,认证失败时直接在Vert.x HTTP过滤器层返回默认401响应,根本不会走到后续的异常映射、业务代码逻辑,这也是你手动捕获异常后写响应报Response head already sent的根本原因。
  • 标准JAX-RS的ExceptionMapper只对JAX-RS REST端点生效,SmallRye GraphQL有独立的异常处理链路,不会把异常抛给JAX-RS的异常映射器。

可行实现方案

根据你需要的响应格式选对应方案即可,两种方案都不需要移除方法上的@Authenticated注解,原有认证逻辑完全保留。

方案1:返回符合GraphQL规范的自定义错误结构

如果客户端可以接受GraphQL标准的errors数组格式,只是要在里面加自定义字段,用这个方案:

  1. 首先修改application.properties关闭主动认证,让鉴权流程延后到GraphQL处理链路中执行:
# 关闭proactive认证,避免提前返回默认401
quarkus.http.auth.proactive=false
  1. 实现GraphQL专属的异常处理器,替换默认的认证错误返回:
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.schema.DataFetchingEnvironment;
import io.quarkus.security.AuthenticationFailedException;
import io.quarkus.security.UnauthorizedException;
import io.smallrye.graphql.api.DataFetcherExceptionHandler;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.Map;

@ApplicationScoped
public class CustomGraphQLAuthExceptionHandler implements DataFetcherExceptionHandler {

    @Override
    public List<GraphQLError> handleException(Throwable throwable, DataFetchingEnvironment env) {
        // 捕获认证、授权类异常
        if (throwable instanceof AuthenticationFailedException || throwable instanceof UnauthorizedException) {
            return List.of(GraphqlErrorBuilder.newError(env)
                    .message("认证失败")
                    .extensions(Map.of(
                            "code", 401,
                            "detail", "请携带有效的Keycloak访问令牌",
                            // 在这里追加客户端要求的任意自定义字段
                            "requestId", env.getExecutionId().toString()
                    ))
                    .build());
        }
        // 其余异常走默认处理逻辑
        return DataFetcherExceptionHandler.super.handleException(throwable, env);
    }
}
  1. 如果需要让HTTP响应状态码也返回401,而不是GraphQL默认的200,再加一行配置:
quarkus.smallrye-graphql.error-extension-fields=code

方案2:返回纯自定义JSON结构(非GraphQL规范格式)

如果客户端要求完全自定义的JSON结构,不需要包裹在GraphQL的errors数组里,用这个方案:

  1. 同样先在application.properties关闭主动认证:
quarkus.http.auth.proactive=false
  1. 注册高优先级的Vert.x失败路由,在OIDC默认处理器之前捕获认证异常,直接返回自定义响应:
import io.quarkus.vertx.web.Route;
import io.quarkus.vertx.web.RouteBase;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.security.AuthenticationFailedException;

@ApplicationScoped
// 路径匹配你的GraphQL端点,优先级要高于OIDC默认的失败处理器
@RouteBase(path = "/graphql", priority = 1)
public class CustomGraphQLAuthFailureHandler {

    @Route(type = Route.HandlerType.FAILURE)
    public void handleAuthError(AuthenticationFailedException e, HttpServerResponse response) {
        // 完全自定义返回的JSON结构
        JsonObject respBody = new JsonObject()
                .put("code", 401)
                .put("success", false)
                .put("message", "身份认证失败")
                .put("detail", "无效或过期的Keycloak令牌,请重新登录");

        response.setStatusCode(401)
                .putHeader("Content-Type", "application/json;charset=utf-8")
                .end(respBody.encode());
    }
}

注意事项

  • Quarkus 3.x版本依赖的包都是jakarta.*命名空间,不要误用旧的javax.*包下的注解和类,否则处理器不会被识别。
  • 以上方案都兼容@Authenticated注解、@RolesAllowed注解的原生逻辑,不需要手动写SecurityIdentity.hasRole()这类校验代码。
  • 如果配置了OIDC多租户,确保失败路由的路径匹配规则覆盖到所有租户对应的GraphQL端点。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 18:21:52