Search code examples
javaapache-sparkkeycloakopenid-connectservlet-filters

Using KeycloakOIDCFilter with Spark UI - cannot configure


We are attempting to use KeycloakOIDCFilter as our Apache Spark UI filter. However, we are struggling to configure the KeycloakOIDCFilter itself.

We have, in spark-defaults.conf:

spark.ui.filters=org.keycloak.adapters.servlet.KeycloakOIDCFilter

This is picked up successfully, and the Spark master logs show this filter being applied to all URL routes.

We have generated a client config file in the Keycloak Admin Console, which has spit out a keycloak-oidc.json.

But how do we tell KeycloakOIDCFilter about this information?

From the Spark docs

Filter parameters can also be specified in the configuration, by setting config entries of the form spark.<class name of filter>.param.<param name>=<value> For example:

spark.ui.filters=com.test.filter1
spark.com.test.filter1.param.name1=foo
spark.com.test.filter1.param.name2=bar

In our case that would seem to be:

spark.org.keycloak.adapters.servlet.KeycloakOIDCFilter.param.<name>=<value>

However, the KeycloakOIDCFilter Java class has only two constructors. One takes no parameters at all and one takes a KeycloakConfigResolver.

The Keycloak Java servlet filter adapter docs only talk about web.xml which isn't applicable in the case of configuring Spark.

So how can we properly configure/point to parameters for the KeycloakOIDCFilter servlet filter?


Update: We've determined that spark.org.keycloak.adapters.servlet.KeycloakOIDCFilter.param.keycloak.config.file can be used to point to a config file, but it appears that Spark does not use SessionManager, leading to a separate error that may or may not be resolvable.


Solution

  • I haven't tested the solution but, according to the Keycloak and Spark documentation you cited, and the source code of KeycloakOIDCFilter, assuming you are using a file in your filesystem, the following configuration could work:

    spark.ui.filters=org.keycloak.adapters.servlet.KeycloakOIDCFilter
    spark.org.keycloak.adapters.servlet.KeycloakOIDCFilter.param.keycloak.config.file=/path/to/keycloak-oidc.json
    

    Or this other one if your config is accesible as a web app resource, through getServletContext().getResourceAsStream(...), instead of a file:

    spark.ui.filters=org.keycloak.adapters.servlet.KeycloakOIDCFilter
    spark.org.keycloak.adapters.servlet.KeycloakOIDCFilter.param.keycloak.config.path=/WEB-INF/keycloak-oidc.json
    

    Please, note that they indicate that filters parameters can also be specified in the configuration: afaik, it doesn't mean that the filter should have any special constructor or something similar.

    This configuration is performed by the addFilters:

    /**
      * Add filters, if any, to the given ServletContextHandlers. Always adds a filter at the end
      * of the chain to perform security-related functions.
      */
    private def addFilters(handler: ServletContextHandler, securityMgr: SecurityManager): Unit = {
      conf.get(UI_FILTERS).foreach { filter =>
        logInfo(s"Adding filter to ${handler.getContextPath()}: $filter")
        val oldParams = conf.getOption(s"spark.$filter.params").toSeq
          .flatMap(Utils.stringToSeq)
          .flatMap { param =>
            val parts = param.split("=")
            if (parts.length == 2) Some(parts(0) -> parts(1)) else None
          }
          .toMap
    
    
        val newParams = conf.getAllWithPrefix(s"spark.$filter.param.").toMap
    
    
        JettyUtils.addFilter(handler, filter, oldParams ++ newParams)
    }
    

    and addFilter:

    def addFilter(
          handler: ServletContextHandler,
          filter: String,
          params: Map[String, String]): Unit = {
        val holder = new FilterHolder()
        holder.setClassName(filter)
        params.foreach { case (k, v) => holder.setInitParameter(k, v) }
        handler.addFilter(holder, "/*", EnumSet.allOf(classOf[DispatcherType]))
    }
    

    methods in the JettyUtils class in the source code of Spark UI.