I’m trying to integrate a push notification module in Keycloak and I’m having problems using the FCM service in quarkus. I want to use the FCM service for sending messages to mobile devices. I have the following code:
package org.keycloak.pushNotification.interfaces;
import com.google.auth.oauth2.GoogleCredentials;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
@ApplicationScoped
public class FirebaseInitializer {
private String accessToken;
@PostConstruct
public void initialize() {
try {
InputStream serviceAccount = Thread.currentThread().getContextClassLoader().getResourceAsStream("/fcm.json");
if (serviceAccount == null) {
throw new FileNotFoundException("Could not find 'fcm.json' in the classpath");
}
GoogleCredentials googleCredentials = GoogleCredentials.fromStream(serviceAccount)
.createScoped(List.of("https://www.googleapis.com/auth/firebase.messaging"));
googleCredentials.refreshIfExpired();
accessToken = googleCredentials.getAccessToken().getTokenValue();
;
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(googleCredentials)
.build();
if (FirebaseApp.getApps().isEmpty()) {
System.out.println("S-a realizat initializarea");
FirebaseApp.initializeApp(options);
}
} catch (IOException e) {
throw new RuntimeException("Failed to initialize Firebase", e);
}
}
public String getAccessToken() {
return accessToken;
}
}
And here I have a specific Keycloak interface where I want to send messages to users:
package org.keycloak.pushNotification.interfaces;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.ws.rs.core.Response;
import org.keycloak.authentication.*;
import org.keycloak.credential.CredentialProvider;
import org.keycloak.models.UserModel;
import org.keycloak.pushNotification.model.PNCredentialModel;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Random;
public class PNRequiredAction implements RequiredActionProvider, CredentialRegistrator {
FirebaseInitializer firebaseInitializer = new FirebaseInitializer();
public static final String PROVIDER_ID = "push_notification_config";
@Override
public void evaluateTriggers(RequiredActionContext context) {
}
@Override
public void requiredActionChallenge(RequiredActionContext context) {
UserModel user = context.getUser();
List<String> tokensFCM = user.getAttributes().get("tokenFCM");
String tokenFCM = tokensFCM.isEmpty() ? null : tokensFCM.get(0);
String code = generateCode();
if (tokensFCM != null) {
try {
firebaseInitializer.initialize();
String accessToken = firebaseInitializer.getAccessToken();
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://fcm.googleapis.com/fcm/send"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + accessToken)
.POST(HttpRequest.BodyPublishers.ofString(buildMessage(tokenFCM, "Title", "Code: " + code)))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Access token: " + accessToken);
System.out.println("Response status: " + response.statusCode());
System.out.println("Response body: " + response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
// if (tokenFCM != null) {
// Message message = Message.builder()
// .setToken(tokenFCM)
// .setNotification(Notification.builder()
// .setTitle(“Autentificare noua”)
// .setBody("Aici este codul tau: " + code)
// .build())
// .build();
//
// // LOGGER HERE
// try {
// String response = FirebaseMessaging.getInstance().send(message);
// System.out.println("Successfully sent message: " + response);
// } catch (Exception e) {
// System.err.println("Failed to send message: " + e.getMessage());
// return;
// }
// }
Response challenge = context.form()
.createForm("push-notification-form.ftl");
context.challenge(challenge);
}
private String buildMessage(String token, String title, String body) {
JsonObject message = Json.createObjectBuilder()
.add("to", token)
.add("notification", Json.createObjectBuilder()
.add("title", title)
.add("body", body))
.build();
return message.toString();
}
@Override
public void processAction(RequiredActionContext context) {
String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("code_access"));
PNCredentialProvider pn = (PNCredentialProvider) context.getSession().getProvider(CredentialProvider.class, "push-notification");
pn.createCredential(context.getRealm(), context.getUser(), PNCredentialModel.createPN());
context.success();
}
@Override
public void close() {
}
private String generateCode() {
Random random = new Random();
int code = 100000 + random.nextInt(900000);
return String.valueOf(code);
}
}
pom.xml:
<?xml version="1.0"?>
4.0.0
org.keycloak.pushNotification
Push-Notification
1.0-SNAPSHOT
Push Notification
<firebase-admin-sdk.version>9.2.0</firebase-admin-sdk.version>
<java.version>17</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<microprofile-jwt-auth-api.version>2.1</microprofile-jwt-auth-api.version>
<quarkus.version>3.8.2</quarkus.version>
io.quarkus
quarkus-bom
${quarkus.version}
pom
import
io.quarkus.platform
quarkus-google-cloud-services-bom
3.8.2
pom
import
io.quarkus
quarkus-universe-bom
${quarkus.version}
pom
import
org.keycloak
keycloak-core
24.0.1
org.keycloak
keycloak-server-spi-private
24.0.1
org.keycloak
keycloak-server-spi
24.0.1
org.keycloak
keycloak-services
24.0.1
org.infinispan
infinispan-core
15.0.0.Final
org.keycloak
keycloak-model-infinispan
24.0.1
io.quarkus
quarkus-arc
io.quarkiverse.googlecloudservices
quarkus-google-cloud-firebase-admin
2.7.0
com.google.firebase
firebase-admin
${firebase-admin-sdk.version}
com.google.auth
google-auth-library-credentials
1.23.0
io.quarkus
quarkus-maven-plugin
${quarkus.version}
build
And it keeps giving me this error at runtime:
2024-03-24 09:54:49,039 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-1) Uncaught server error: java.lang.NoClassDefFoundError: com/google/auth/oauth2/GoogleCredentials
at org.keycloak.pushNotification.interfaces.FirebaseInitializer.initialize(FirebaseInitializer.java:25)
at org.keycloak.pushNotification.interfaces.PNRequiredAction.requiredActionChallenge(PNRequiredAction.java:37)
at org.keycloak.services.managers.AuthenticationManager.executeAction(AuthenticationManager.java:1275)
at org.keycloak.services.managers.AuthenticationManager.lambda$executionActions$15(AuthenticationManager.java:1222)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:400)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:528)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647)
at org.keycloak.services.managers.AuthenticationManager.executionActions(AuthenticationManager.java:1223)
at org.keycloak.services.managers.AuthenticationManager.actionRequired(AuthenticationManager.java:1111)
at org.keycloak.services.managers.AuthenticationManager.nextActionAfterAuthentication(AuthenticationManager.java:958)
at org.keycloak.services.resources.LoginActionsService.processRequireAction(LoginActionsService.java:1079)
at org.keycloak.services.resources.LoginActionsService.requiredActionGET(LoginActionsService.java:1061)
at org.keycloak.services.resources.LoginActionsService$quarkusrestinvoker$requiredActionGET_900f1400af417d7ade6b5fdd106784903c8de34e.invoke(Unknown Source)
at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:582)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: java.lang.ClassNotFoundException: com.google.auth.oauth2.GoogleCredentials
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at io.quarkus.bootstrap.runner.RunnerClassLoader.loadClass(RunnerClassLoader.java:115)
at io.quarkus.bootstrap
If someone can help me, please explain what I’m doing wrong that I can’t solve it.