Uploaded image for project: 'Keycloak'
  1. Keycloak
  2. KEYCLOAK-13345

Keycloak Tomcat Adapter doesn't work with Tomcat 9.0.30+

    XMLWordPrintable

Details

    • Bug
    • Status: Resolved
    • Major
    • Resolution: Resolved at Apache
    • 8.0.0, 8.0.1, 8.0.2
    • Backlog
    • Adapter - JEE
    • NEW
    • NEW

    Description

      A Keycloak customer of mine reports the following to me:

      The current version of Keycloak Tomcat Adapter (keycloak-tomcat-adapter-dist-8.0.2) is incompatible with Tomcat 9.0.30+. The reason for this imcompatibility is a change in Tomcat 9.0.30, see

      https://github.com/apache/tomcat/commit/15c72df0ca36a0b05a3dded50971c216125704f8

      The following is my understanding of the issue along with a potential solution. You might want to discuss this with someone who has a deeper understanding of the internals of Tomcat, though.

      The default configuration of Tomcat uses a LockoutRealm "to provide a user lock out mechanism if there are too many failed authentication attempts in a given period of time." LockoutRealm extends CombinedRealm that wraps a number of actual application realms. And CombinedRealm extends the base class RealmBase. So you get the following inheritance structure:

      LockoutRealm --> CombinedRealm --> RealmBase

      The current implementation of Keycloak Tomcat Adapter does not include an implementation of a Tomcat Realm. It was not required, as an empty LockoutRealm could be used, i.e. a LockoutRealm that does not wrap any application realms. Example:

      <Realm className="org.apache.catalina.realm.LockOutRealm"/>
      

      In Tomcat versions prior to 9.0.30, LockoutRealm used the inherited implementation of method hasRole(...) from RealmBase to check if a given principal has the required role for permitting access to the requested resource.

      With the change mentioned above this behaviour was modified. LockOutRealm now overrides hasRole(...) with it's own implementation that checks with all wrapped realms whether a given request is permitted or not:

      @Override
      public boolean hasRole(Wrapper wrapper, Principal principal, String role) {
       for (Realm realm : realms) {
         if (realm.hasRole(wrapper, principal, role)) {
           return true;
         }
       }
       return false;
      }
      

      This means we now need a wrapped realm in LockoutRealm. Because if there are no wrapped realms, the method above will always return false, resulting in status 403 for all requests. For this reason the Keycloak Tomcat Adapter should provide something like a KeycloakRealm that can be used in Tomcat's configuration and that just delegates to hasRole(...) in RealmBase (by inheritance).

      It could be as simple as this example:

      public class KeycloakRealm extends RealmBase {
        @Override
        protected String getPassword(String s) {
          throw new UnsupportedOperationException("KeycloakRealm.getPassword");
        }
        @Override
        protected Principal getPrincipal(String s) {
          throw new UnsupportedOperationException("KeycloakRealm.getPrincipal");
        }
      } 
      

      This KeycloakRealm would then have to be added to Tomcat's configuration as the realm wrapped by LockoutRealm:

      <Realm className="org.apache.catalina.realm.LockOutRealm" >
       <Realm className="org.keycloak.adapters.tomcat.KeycloakRealm"/>
      </Realm>
      

      A final thought: Even if you don't want to use LockOutRealm or CombinedRealm, you would still need another Realm to be used for your Tomcat configuration, which also requires Keycloak Tomcat Adapter to include an implmentation of Realm.

      Attachments

        Issue Links

          Activity

            People

              Unassigned Unassigned
              nkoebler Niko Köbler (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: