I am try to do some websocket authentication in quarkus. I have currently Rest end point that use the authentication enabled. I use keyclock for the authentication and vert.x bidirectional connection with websocket (Also post this question in github thread github quarkus thread)
@Path("/hello")
"@Authenticated"
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from RESTEasy Reactive";
}
}
And I have custom tenant resolver. Because this is multi tenant application.
This is my application.property file.
quarkus.oidc.tenant-enabled=false
quarkus.oidc.client-id=telemetry-service
(I use this default tenant is not enabled. because this is multi tenant application. I use custom tenant resolver. But not included here. because I don't want to complicate this problem. How ever I will add that end of the question. Thank you..)
The thing is I have Bearer token and I send it also to the websocket endpoint and then I want to authenticate that token using some kind of redirection. I read some documentation. Still hard to solve this. The thing is I want to do the thing that "@Authenticated" annotation doing.
As I understand in the documentation "HttpAuthenticationMechanism" extract that Bearer token and pass it into "IdentityProvider" to authenticate and create "SecurityIdentity". I guess I want to do is some how pass my Bearer token to IdentityProvider. Can some one help me to do this? I have to some how authenticate websocket grammatically.
This is the socket connection init class and from here i want to authorization that bearer token
import io.vertx.mutiny.core.Vertx;
import io.vertx.mutiny.ext.web.Router;
import io.vertx.mutiny.ext.web.handler.sockjs.SockJSHandler;
import io.vertx.mutiny.ext.web.handler.sockjs.SockJSSocket;
@ApplicationScoped
public class EventBusBridgeConfig {
private final Vertx vertx;
private SockJSSocket sock;
private static final String SEC_WEBSOCKET_KEY = "sec-websocket-key";
public EventBusBridgeConfig(Vertx vertxy) {
this.vertx = vertx;
}
public void init(@Observes Router router) {
SockJSBridgeOptions options = new SockJSBridgeOptions()
.addInboundPermitted(new PermittedOptions().setAddress("telemetry-subscribe"))
.addOutboundPermitted(new PermittedOptions().setAddressRegex("telemetry-feed-.*"));
SockJSHandler handler = SockJSHandler.create(vertx);
Log.info("mounting handler");
router.mountSubRouter("/event-bus", handler.bridge(options, evt -> {
sock = evt.socket();
Log.info("Bearer token: " + sock.routingContext().queryParam("token"));
if (evt.type() == BridgeEventType.REGISTER) {
Log.debug("headers: " + sock.headers());
} else if (evt.type() == BridgeEventType.SOCKET_CLOSED) {
String connectionId = sock.headers().get(SEC_WEBSOCKET_KEY);
}
evt.complete(true);
}));
}
}
Custom tenant resolver
@ApplicationScoped
public class CustomTenantResolver implements TenantConfigResolver {
OidcConfig oidcConfig;
ApplicationHttpConfig applicationHttpConfig;
private final UrlHandler urlHandler;
private static final String ORIGIN = "origin";
private static final String REFER_URL = "referer";
private static final String SEC_FETCH_SITE = "sec-Fetch-Site";
public CustomTenantResolver(ApplicationHttpConfig applicationHttpConfig, OidcConfig oidcConfig, UrlHandler urlHandler) {
super();
this.applicationHttpConfig = applicationHttpConfig;
this.oidcConfig = oidcConfig;
this.urlHandler = urlHandler;
}
@Override
public Uni<OidcTenantConfig> resolve(RoutingContext context, OidcRequestContext<OidcTenantConfig> requestContext) {
// get the path and the host
String origin = context.request().getHeader(ORIGIN);
String referUrl = context.request().getHeader(REFER_URL);
String secFetchSite = context.request().getHeader(SEC_FETCH_SITE);
Log.info("reserved origin: " + origin + ", referUrl: " + referUrl + ", sec-fetch-site: " + secFetchSite);
/*
* this is the client id for this service
* this client should create in the relevant keycloak realms and should available appropriate permissions
*/
String clientId = oidcConfig.clientId();
Log.debug("clientId: " + clientId);
if (origin == null && referUrl == null) {
Log.error("Origin and referUrl not available, cannot map to tenant");
return Uni.createFrom().nullItem();
}
String host;
String protocol;
try {
if (origin != null) {
host = urlHandler.getHostFromGivenUrl(origin);
protocol = urlHandler.getProtocolFromGivenUrl(origin);
} else {
host = urlHandler.getHostFromGivenUrl(referUrl);
protocol = urlHandler.getProtocolFromGivenUrl(referUrl);
if (!isInSameOrigin(secFetchSite, host)) {
Log.error("origin not available in the url and also not in the same origin, cannot map to tenant");
return Uni.createFrom().nullItem();
}
}
} catch (MalformedURLException e) {
Log.error("failed to decode the url");
Log.debug(e);
return Uni.createFrom().nullItem();
}
Log.debug("host: " + host + ", protocol: " + protocol);
String[] urlContent = host.split("\\.", 3);
Log.debug("urlParams: " + Arrays.toString(urlContent));
return getTheOidcTenantConfigUniBasedOnURLContent(urlContent, clientId, protocol);
}
private boolean isInSameOrigin(String secFetchSite, String host) {
return (secFetchSite != null && secFetchSite.compareTo("same-origin") == 0) || host.compareTo(applicationHttpConfig.host()) == 0;
}
private Uni<OidcTenantConfig> getTheOidcTenantConfigUniBasedOnURLContent(String[] urlContent, String clientId, String protocol) {
if (urlContent.length == 3) {
String keycloakUrl = getKeycloakUrl(protocol, urlContent[2]);
Log.debug("keycloak URL: " + keycloakUrl);
Log.info("found the matching tenant");
return getOidcTenantConfigUni(keycloakUrl, urlContent[0], clientId);
} else {
Log.info("url don't have required content to decide the realm, " +
"cannot match with given url patterns, cannot map to tenant");
return Uni.createFrom().nullItem();
}
}
private String getKeycloakUrl(String protocol, String host) {
return protocol + "://idp." + host;
}
private Uni<OidcTenantConfig> getOidcTenantConfigUni(String keycloakUrl, String tenantId, String clientId) {
OidcTenantConfig config = new OidcTenantConfig();
config.setTenantId(tenantId);
String authServerUrl = keycloakUrl + "/realms/" + tenantId;
config.setAuthServerUrl(authServerUrl);
Log.info("redirected to: " + authServerUrl);
config.setClientId(clientId);
config.setApplicationType(OidcTenantConfig.ApplicationType.SERVICE);
return Uni.createFrom().item(config);
}
}
Update:
Also, I am trying to go through the code and I found that "OidcIdentityProvider" class that implement IdentityProvider. question link I think this is responsible for this process and I am thinking about use that class or implement another custom class using IdentityProvider. Or is any one now better solution for this or how to do above process correctly?