I have a Wildfly Server running an EJB Application, if i make a REST client call to the Application the EJB Security contex has being automatically filled up with a valid Principal and I can cast it to Keycloak Security Context and get Claim informations. The Application also accept gRCP calls, in this case the the Security context has Anonymous Principal which, I suppose, is because it has not being filled up when gRCP calls come in.
Do I have to implement a global gRPC Server Interceptor?
Is there an Example that I can follow?
This is probably happening because, unlike REST endpoints, gRPC doesn’t automatically integrate with Keycloak on WildFly.
To solve this, you’ll likely need a global ServerInterceptor
for gRPC to manage the authentication and authorization. This interceptor will let you extract and verify tokens from the metadata of incoming calls. Normally, you'd look for a Bearer token in the metadata headers and validate it against Keycloak.
You may want to implement somewhat like this:
import io.grpc.*;
import org.keycloak.adapters.jboss.KeycloakPrincipal;
import org.keycloak.representations.AccessToken;
public class KeycloakAuthInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
// Retrieve the Authorization header
String token = headers.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER));
if (token == null || !token.startsWith("Bearer ")) {
call.close(Status.UNAUTHENTICATED.withDescription("Authorization token missing"), headers);
return new ServerCall.Listener<ReqT>() {};
}
token = token.substring("Bearer ".length());
try {
// Verify token and populate security context
AccessToken accessToken = verifyTokenWithKeycloak(token);
if (accessToken == null) {
call.close(Status.UNAUTHENTICATED.withDescription("Invalid token"), headers);
return new ServerCall.Listener<ReqT>() {};
}
// Populate the security context (example uses WildFly's SecurityContext)
SecurityContext context = createSecurityContext(accessToken);
SecurityContextAssociation.setSecurityContext(context);
// Continue the gRPC call
return Contexts.interceptCall(Context.current(), call, headers, next);
} catch (Exception e) {
call.close(Status.UNAUTHENTICATED.withDescription("Failed to authenticate"), headers);
return new ServerCall.Listener<ReqT>() {};
}
}
private AccessToken verifyTokenWithKeycloak(String token) {
// Implement token verification with Keycloak here
return null; // Replace with actual token verification logic
}
private SecurityContext createSecurityContext(AccessToken accessToken) {
// Convert AccessToken to WildFly SecurityContext with KeycloakPrincipal
KeycloakPrincipal<?> principal = new KeycloakPrincipal<>("principal-name");
// Set up the security context and return it
return new SecurityContext(principal);
}
}