Keycloak自定义Provider调用gRPC服务失败问题求助
问题场景
在为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

