Keycloak自定义User Storage SPI连接PostgreSQL驱动报错排查
问题描述
此前已实现基于属性文件的Keycloak自定义用户联邦提供程序且运行正常,修改代码连接外部PostgreSQL用户数据库后,出现初始化错误:
Failed to init CustomProviderFactory: java.sql.SQLException: No suitable driver found for jdbc:postgresql://postgres:5432/users_db
部署环境
- 用Docker Compose部署PostgreSQL容器,主机名为
postgres,同一网络下其他容器可通过该主机名正常连接(排除网络问题)。 - Keycloak使用自定义镜像,Dockerfile如下:
FROM quay.io/keycloak/keycloak:latest as builder # Install custom providers COPY target/custom-user-provider.jar /opt/keycloak/providers RUN /opt/keycloak/bin/kc.sh build FROM quay.io/keycloak/keycloak:latest COPY --from=builder /opt/keycloak/ /opt/keycloak/ WORKDIR /opt/keycloak ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
相关代码片段
CustomUserProviderFactory.java
public class CustomUserProviderFactory implements UserStorageProviderFactory<CustomUserProvider> { public static final String PROVIDER_NAME = "custom-user-storage"; private static final Logger logger = Logger.getLogger(CustomUserProviderFactory.class); protected UserRepository userRepository; @Override public void init(Config.Scope config) { try { userRepository = new UserRepository(); logger.error("Loaded users database"); } catch (SQLException ex) { logger.error("Failed to init CustomProviderFactory", ex); } } @Override public CustomUserProvider create(KeycloakSession session, ComponentModel model) { return new CustomUserProvider(session, model, userRepository); } @Override public String getId() { return PROVIDER_NAME; } }
UserRepository.java
public class UserRepository { private static final Logger logger = Logger.getLogger(UserRepository.class); private final static String url = "jdbc:postgresql://postgres:5432/users_db"; private final static String username = "user"; private final static String password = "password"; private final Connection conn; public UserRepository() throws SQLException { // Error is thrown here conn = DriverManager.getConnection(url, username, password); } public User getByEmail(String email) throws SQLException { // Query user from DB } }
问题症结:怀疑pom.xml中org.postgresql:postgresql:42.4.2依赖配置或JAR打包方式有误,尝试过多种打包方式但不确定正确性。
解决方案
1. 用Maven Shade插件构建含依赖的胖JAR
Keycloak的Provider需要JDBC驱动存在于类路径中,通过Shade插件将PostgreSQL驱动打包进自定义Provider JAR:
修改pom.xml添加插件配置:
<build> <plugins> <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> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <!-- SPI无需主类,留空即可 --> </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> </plugins> </build>
同时确保PostgreSQL依赖为compile scope(默认即为该值):
<dependencies> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.4.2</version> </dependency> <!-- Keycloak SPI依赖设为provided,避免重复打包 --> <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-core</artifactId> <version>${keycloak.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-server-spi</artifactId> <version>${keycloak.version}</version> <scope>provided</scope> </dependency> </dependencies>
2. 显式加载PostgreSQL驱动
在UserRepository构造方法中手动加载驱动类,避免DriverManager自动发现失败:
public UserRepository() throws SQLException, ClassNotFoundException { // 显式加载PostgreSQL驱动 Class.forName("org.postgresql.Driver"); conn = DriverManager.getConnection(url, username, password); }
3. 验证JAR包内容
构建完成后,解压target/custom-user-provider.jar,检查是否包含org/postgresql目录下的驱动类,确认驱动已被正确打包。
4. 替代方案:直接将驱动放入Keycloak lib目录
若打包胖JAR有问题,可直接将PostgreSQL驱动JAR复制到Keycloak容器的/opt/keycloak/lib目录,修改Dockerfile:
FROM quay.io/keycloak/keycloak:latest as builder # 复制自定义Provider和PostgreSQL驱动 COPY target/custom-user-provider.jar /opt/keycloak/providers COPY path/to/postgresql-42.4.2.jar /opt/keycloak/lib RUN /opt/keycloak/bin/kc.sh build FROM quay.io/keycloak/keycloak:latest COPY --from=builder /opt/keycloak/ /opt/keycloak/ WORKDIR /opt/keycloak ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
额外注意事项
- 确保Keycloak版本与SPI依赖版本一致,避免兼容性问题。
- 建议使用连接池(如HikariCP)管理数据库连接,不要在
init方法中提前创建连接,避免资源泄漏。
内容的提问来源于stack exchange,提问作者nboisnea

