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

Keycloak自定义Provider调用gRPC服务失败问题求助

Keycloak自定义Provider调用gRPC服务出现UNAVAILABLE错误的解决方案

问题场景

在为Keycloak开发自定义Provider时,需要调用外部gRPC服务。本地使用独立客户端测试gRPC调用完全正常,但将Provider打包部署到Keycloak后,通过自定义REST端点调用gRPC服务时抛出以下错误:

2023-03-07 17:06:29,101 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (grpc-default-executor-0) Uncaught server error: io.grpc.StatusRuntimeException: UNAVAILABLE: io exception
   at io.grpc.Status.asRuntimeException(Status.java:539)
   at io.grpc.stub.ClientCalls$StreamObserverToCallListenerAdapter.onClose(ClientCalls.java:487)
   at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:576)
   at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java:70)
   at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:757)
   at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:736)
   at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
   at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133)
   at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
   at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
   at java.base/java.lang.Thread.run(Thread.java:833)
 Caused by: java.io.FileNotFoundException
   at io.grpc.netty.shaded.io.netty.channel.unix.Errors.newConnectException0(Errors.java:153)
   at io.grpc.netty.shaded.io.netty.channel.unix.Errors.handleConnectErrno(Errors.java:128)
   at io.grpc.netty.shaded.io.netty.channel.unix.Socket.connect(Socket.java:313)
   at io.grpc.netty.shaded.io.netty.channel.epoll.AbstractEpollChannel.doConnect0(AbstractEpollChannel.java:773)
   at io.grpc.netty.shaded.io.netty.channel.epoll.AbstractEpollChannel.doConnect(AbstractEpollChannel.java:758)
   at io.grpc.netty.shaded.io.netty.channel.epoll.EpollDomainSocketChannel.doConnect(EpollDomainSocketChannel.java:87)
   at io.grpc.netty.shaded.io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.connect(AbstractEpollChannel.java:600)
   at io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$HeadContext.connect(DefaultChannelPipeline.java:1342)
   at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeConnect(AbstractChannelHandlerContext.java:548)
   at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.connect(AbstractChannelHandlerContext.java:533)
   at io.grpc.netty.shaded.io.netty.channel.ChannelDuplexHandler.connect(ChannelDuplexHandler.java:54)
   at io.grpc.netty.shaded.io.grpc.netty.WriteBufferingAndExceptionHandler.connect(WriteBufferingAndExceptionHandler.java:157)
   at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeConnect(AbstractChannelHandlerContext.java:548)
   at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.access$1000(AbstractChannelHandlerContext.java:61)
   at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext$9.run(AbstractChannelHandlerContext.java:538)
   at io.grpc.netty.shaded.io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174)
   at io.grpc.netty.shaded.io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167)
   at io.grpc.netty.shaded.io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
   at io.grpc.netty.shaded.io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:391)
   at io.grpc.netty.shaded.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
   at io.grpc.netty.shaded.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
   at io.grpc.netty.shaded.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
   ... 1 more

由于Provider依赖Keycloak默认类路径中没有的外部库,最初使用Maven Assembly插件打包了包含所有依赖的JAR包。排查发现问题根源在于打包后JAR中的META-INF/services/io.grpc.NameResolverProvider文件内容为io.grpc.netty.shaded.io.grpc.netty.UdsNameResolverProvider,这是针对Unix域套接字的解析器,不符合常规TCP gRPC服务的调用需求;但删除该文件后,又会出现找不到io.grpc.NameResolverProvider实现的错误。

问题分析

Maven Assembly插件在打包时会直接合并所有依赖的META-INF/services文件,若多个依赖包中存在同名的服务配置文件,会导致内容被覆盖或混淆。此处是grpc-netty-shaded包中的UDS解析器配置覆盖了TCP解析器的配置,导致gRPC客户端尝试用UDS方式连接常规TCP服务,最终抛出连接异常。

解决方案

1. 替换正确的NameResolverProvider实现

手动修改打包后JAR中的META-INF/services/io.grpc.NameResolverProvider文件,将内容替换为TCP地址解析器的类名:

io.grpc.netty.shaded.io.grpc.netty.NettyNameResolverProvider

该类是grpc-netty-shaded包中负责TCP地址解析的标准实现,适用于绝大多数常规gRPC服务调用场景。

2. 改用Maven Shade插件打包(推荐)

相比Assembly插件,Shade插件能更精细地控制META-INF/services文件的合并逻辑,避免配置覆盖问题。在项目的pom.xml中添加以下Shade插件配置:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.4.1</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <!-- 合并NameResolverProvider服务配置,保留正确的实现 -->
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer">
                        <services>
                            <service>io.grpc.NameResolverProvider</service>
                        </services>
                        <mergeStrategy>append</mergeStrategy>
                    </transformer>
                    <!-- 若Provider有主类,可配置此项,否则可删除 -->
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>your.provider.main.class</mainClass>
                    </transformer>
                </transformers>
                <!-- 排除签名文件,避免打包后类验证错误 -->
                <filters>
                    <filter>
                        <artifact>*:*</artifact>
                        <excludes>
                            <exclude>META-INF/*.SF</exclude>
                            <exclude>META-INF/*.DSA</exclude>
                            <exclude>META-INF/*.RSA</exclude>
                        </excludes>
                    </filter>
                </filters>
            </configuration>
        </execution>
    </executions>
</plugin>

配置完成后,执行mvn clean package即可生成正确合并服务配置的JAR包。

3. 验证Keycloak部署

将打包好的JAR包放入Keycloak的providers目录,执行以下命令重新构建Keycloak,确保依赖被正确加载:

# Linux/macOS
./kc.sh build

# Windows
kc.bat build

启动Keycloak后,测试自定义REST端点的gRPC调用即可恢复正常。

内容的提问来源于stack exchange,提问作者Koppa Péter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:37:04