0

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?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • Hi, re OIDC authentication, disabling the default tenant manually won't be necessary for multi-tenant setups: https://github.com/quarkusio/quarkus/pull/33472. You might also want so rely on CORS filter to deal with CORS. As far as the actual problem of making a bearer token to WebSocket channel is concerned, one would need to use a custom WS header to make the token sticky, have a look at this reproducer: https://github.com/SetoKaiba/reproduce/tree/master/src/main/java/net/kaiba – Sergey Beryozkin May 19 '23 at 12:24
  • Okey. Thank you very much. – Kavishka Madhushan Jun 06 '23 at 03:54

0 Answers0